aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2010-06-30 23:01:30 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2010-06-30 23:01:30 +0100
commitd2c633ba0bfb7baacdee89a46d7d036d24c68817 (patch)
tree8f0974852b51597652e6ae73da26f3eb80fe878b
parent92c0f17d6d2a958d3a6285b0e5408e9e0e7122e1 (diff)
parentc63cf7bf0db708fe46a929cf57649ab5a92034af (diff)
downloadrails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.tar.gz
rails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.tar.bz2
rails-d2c633ba0bfb7baacdee89a46d7d036d24c68817.zip
Merge branch 'master' of http://github.com/rails/rails
-rw-r--r--Gemfile1
-rw-r--r--actionmailer/lib/action_mailer/base.rb9
-rw-r--r--actionmailer/lib/action_mailer/deprecated_api.rb2
-rw-r--r--actionmailer/lib/action_mailer/log_subscriber.rb22
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb3
-rw-r--r--actionmailer/lib/action_mailer/railties/log_subscriber.rb22
-rw-r--r--actionmailer/test/log_subscriber_test.rb8
-rw-r--r--actionmailer/test/old_base/mail_service_test.rb6
-rw-r--r--actionpack/CHANGELOG18
-rw-r--r--actionpack/README18
-rw-r--r--actionpack/actionpack.gemspec4
-rw-r--r--actionpack/lib/abstract_controller/base.rb13
-rw-r--r--actionpack/lib/action_controller/base.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb56
-rw-r--r--actionpack/lib/action_controller/metal.rb13
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb2
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb18
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb3
-rw-r--r--actionpack/lib/action_controller/railtie.rb3
-rw-r--r--actionpack/lib/action_controller/railties/log_subscriber.rb56
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb14
-rw-r--r--actionpack/lib/action_controller/test_case.rb22
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/node.rb1
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb33
-rw-r--r--[-rwxr-xr-x]actionpack/lib/action_dispatch/http/request.rb5
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/callbacks.rb8
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb149
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb43
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb2
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/deprecated_mapper.rb386
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb312
-rw-r--r--actionpack/lib/action_dispatch/routing/route.rb13
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb43
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb1
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb44
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb4
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb32
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb60
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb74
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb159
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb113
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb210
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb53
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb36
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb69
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb130
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb409
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb68
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb160
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y34
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb83
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex12
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb148
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb569
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb185
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb158
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb45
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb40
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb4
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb59
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb56
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb83
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb126
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb90
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb415
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb213
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb3
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb5
-rw-r--r--actionpack/lib/action_view/base.rb17
-rw-r--r--actionpack/lib/action_view/context.rb21
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb19
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb48
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/csrf_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/debug_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb38
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb20
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/raw_output_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/record_identification_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb1
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb16
-rw-r--r--actionpack/lib/action_view/helpers/scriptaculous_helper.rb11
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb27
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb53
-rw-r--r--actionpack/lib/action_view/log_subscriber.rb28
-rw-r--r--actionpack/lib/action_view/lookup_context.rb2
-rw-r--r--actionpack/lib/action_view/paths.rb1
-rw-r--r--actionpack/lib/action_view/railtie.rb18
-rw-r--r--actionpack/lib/action_view/railties/log_subscriber.rb24
-rw-r--r--actionpack/lib/action_view/render/layouts.rb3
-rw-r--r--actionpack/lib/action_view/render/partials.rb4
-rw-r--r--actionpack/lib/action_view/render/rendering.rb1
-rw-r--r--actionpack/lib/action_view/template.rb20
-rw-r--r--actionpack/lib/action_view/template/error.rb1
-rw-r--r--actionpack/lib/action_view/template/handlers.rb1
-rw-r--r--actionpack/lib/action_view/template/resolver.rb3
-rw-r--r--actionpack/lib/action_view/template/text.rb1
-rw-r--r--actionpack/lib/action_view/test_case.rb50
-rw-r--r--actionpack/test/abstract/callbacks_test.rb14
-rw-r--r--actionpack/test/abstract_unit.rb26
-rw-r--r--actionpack/test/activerecord/active_record_store_test.rb47
-rw-r--r--actionpack/test/activerecord/controller_runtime_test.rb10
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb31
-rw-r--r--actionpack/test/controller/base_test.rb3
-rw-r--r--actionpack/test/controller/flash_test.rb20
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb10
-rw-r--r--actionpack/test/controller/new_base/base_test.rb38
-rw-r--r--actionpack/test/controller/record_identifier_test.rb23
-rw-r--r--actionpack/test/controller/render_test.rb11
-rw-r--r--actionpack/test/controller/url_for_test.rb12
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb22
-rw-r--r--actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb23
-rw-r--r--actionpack/test/dispatch/routing_test.rb383
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb43
-rw-r--r--actionpack/test/dispatch/session/mem_cache_store_test.rb67
-rw-r--r--actionpack/test/fixtures/fun/games/_form.erb1
-rw-r--r--actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb10
-rw-r--r--actionpack/test/template/active_model_helper_test.rb7
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb22
-rw-r--r--actionpack/test/template/date_helper_test.rb27
-rw-r--r--actionpack/test/template/erb/form_for_test.rb2
-rw-r--r--actionpack/test/template/erb/tag_helper_test.rb4
-rw-r--r--actionpack/test/template/form_helper_test.rb412
-rw-r--r--actionpack/test/template/form_options_helper_test.rb30
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb56
-rw-r--r--actionpack/test/template/html-scanner/sanitizer_test.rb4
-rw-r--r--actionpack/test/template/log_subscriber_test.rb10
-rw-r--r--actionpack/test/template/tag_helper_test.rb4
-rw-r--r--actionpack/test/template/test_case_test.rb49
-rw-r--r--actionpack/test/template/text_helper_test.rb1
-rw-r--r--actionpack/test/template/url_helper_test.rb55
-rw-r--r--activemodel/README3
-rw-r--r--[-rwxr-xr-x]activemodel/Rakefile0
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb16
-rw-r--r--activemodel/lib/active_model/deprecated_error_methods.rb2
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb76
-rw-r--r--activemodel/lib/active_model/lint.rb4
-rw-r--r--activemodel/lib/active_model/observing.rb1
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb15
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb49
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb57
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb46
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb7
-rw-r--r--activemodel/lib/active_model/validations/format.rb8
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb7
-rw-r--r--activemodel/lib/active_model/validations/length.rb10
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb15
-rw-r--r--activemodel/lib/active_model/validations/presence.rb4
-rw-r--r--activemodel/lib/active_model/validations/validates.rb4
-rw-r--r--activemodel/lib/active_model/validations/with.rb1
-rw-r--r--activemodel/lib/active_model/validator.rb4
-rw-r--r--activemodel/test/cases/helper.rb1
-rw-r--r--activemodel/test/cases/validations/callbacks_test.rb78
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb82
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb507
-rw-r--r--activemodel/test/cases/validations/numericality_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations_test.rb42
-rw-r--r--activerecord/CHANGELOG516
-rw-r--r--activerecord/Rakefile9
-rw-r--r--[-rwxr-xr-x]activerecord/examples/performance.rb6
-rw-r--r--activerecord/lib/active_record/aggregations.rb7
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/associations.rb28
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb33
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb2
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb1
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb1
-rw-r--r--activerecord/lib/active_record/autosave_association.rb8
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/base.rb119
-rw-r--r--activerecord/lib/active_record/callbacks.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb54
-rw-r--r--[-rwxr-xr-x]activerecord/lib/active_record/connection_adapters/abstract_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb27
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb20
-rw-r--r--activerecord/lib/active_record/counter_cache.rb1
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb5
-rw-r--r--activerecord/lib/active_record/dynamic_scope_match.rb7
-rw-r--r--activerecord/lib/active_record/errors.rb3
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/migration.rb99
-rw-r--r--activerecord/lib/active_record/named_scope.rb46
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb30
-rw-r--r--activerecord/lib/active_record/observer.rb5
-rw-r--r--activerecord/lib/active_record/persistence.rb31
-rw-r--r--activerecord/lib/active_record/query_cache.rb1
-rw-r--r--activerecord/lib/active_record/railtie.rb5
-rw-r--r--activerecord/lib/active_record/railties/databases.rake44
-rw-r--r--activerecord/lib/active_record/railties/log_subscriber.rb32
-rw-r--r--activerecord/lib/active_record/reflection.rb53
-rw-r--r--activerecord/lib/active_record/relation.rb48
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb249
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb27
-rw-r--r--activerecord/lib/active_record/serialization.rb2
-rw-r--r--activerecord/lib/active_record/session_store.rb16
-rw-r--r--activerecord/lib/active_record/test_case.rb3
-rw-r--r--activerecord/lib/active_record/timestamp.rb18
-rw-r--r--activerecord/lib/active_record/transactions.rb7
-rw-r--r--activerecord/lib/active_record/validations.rb7
-rw-r--r--activerecord/lib/active_record/validations/associated.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb18
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb2
-rw-r--r--activerecord/test/cases/adapter_test.rb15
-rw-r--r--activerecord/test/cases/adapters/firebird/connection_test.rb (renamed from activerecord/test/cases/connection_test_firebird.rb)0
-rw-r--r--activerecord/test/cases/adapters/firebird/default_test.rb (renamed from activerecord/test/cases/default_test_firebird.rb)0
-rw-r--r--activerecord/test/cases/adapters/firebird/migration_test.rb (renamed from activerecord/test/cases/migration_test_firebird.rb)0
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb (renamed from activerecord/test/cases/active_schema_test_mysql.rb)6
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb (renamed from activerecord/test/cases/connection_test_mysql.rb)0
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb (renamed from activerecord/test/cases/reserved_word_test_mysql.rb)0
-rw-r--r--activerecord/test/cases/adapters/oracle/synonym_test.rb (renamed from activerecord/test/cases/synonym_test_oracle.rb)0
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb (renamed from activerecord/test/cases/active_schema_test_postgresql.rb)5
-rw-r--r--activerecord/test/cases/adapters/postgresql/datatype_test.rb (renamed from activerecord/test/cases/datatype_test_postgresql.rb)2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb (renamed from activerecord/test/cases/schema_authorization_test_postgresql.rb)2
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb (renamed from activerecord/test/cases/schema_test_postgresql.rb)4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/copy_table_test.rb (renamed from activerecord/test/cases/copy_table_test_sqlite.rb)0
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb56
-rw-r--r--activerecord/test/cases/aggregations_test.rb28
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb62
-rw-r--r--activerecord/test/cases/autosave_association_test.rb1
-rw-r--r--[-rwxr-xr-x]activerecord/test/cases/base_test.rb33
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--[-rwxr-xr-x]activerecord/test/cases/counter_cache_test.rb0
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb9
-rw-r--r--activerecord/test/cases/method_scoping_test.rb221
-rw-r--r--activerecord/test/cases/migration_test.rb129
-rw-r--r--activerecord/test/cases/named_scope_test.rb10
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb49
-rw-r--r--activerecord/test/cases/reflection_test.rb10
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb396
-rw-r--r--activerecord/test/cases/relations_test.rb56
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb14
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb12
-rw-r--r--activerecord/test/cases/validations/i18n_validation_test.rb50
-rw-r--r--activerecord/test/fixtures/topics.yml2
-rw-r--r--activerecord/test/models/developer.rb8
-rw-r--r--activerecord/test/models/pirate.rb4
-rw-r--r--activerecord/test/models/without_table.rb3
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activeresource/lib/active_resource/base.rb5
-rw-r--r--activeresource/lib/active_resource/http_mock.rb16
-rw-r--r--activeresource/lib/active_resource/log_subscriber.rb15
-rw-r--r--activeresource/lib/active_resource/railtie.rb3
-rw-r--r--activeresource/lib/active_resource/railties/log_subscriber.rb15
-rw-r--r--activeresource/lib/active_resource/validations.rb2
-rw-r--r--activeresource/test/cases/base_errors_test.rb13
-rw-r--r--activeresource/test/cases/http_mock_test.rb71
-rw-r--r--activeresource/test/cases/log_subscriber_test.rb9
-rw-r--r--activesupport/CHANGELOG12
-rw-r--r--[-rwxr-xr-x]activesupport/bin/generate_tables0
-rw-r--r--activesupport/lib/active_support.rb6
-rw-r--r--activesupport/lib/active_support/all.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb67
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb4
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/random_access.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/logger.rb38
-rw-r--r--[-rwxr-xr-x]activesupport/lib/active_support/core_ext/object/to_param.rb0
-rw-r--r--activesupport/lib/active_support/core_ext/range/overlaps.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb11
-rw-r--r--activesupport/lib/active_support/dependencies.rb47
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb21
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb39
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb37
-rw-r--r--activesupport/lib/active_support/i18n.rb1
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb80
-rw-r--r--activesupport/lib/active_support/json/backends/yajl.rb2
-rw-r--r--activesupport/lib/active_support/json/encoding.rb19
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb (renamed from railties/lib/rails/log_subscriber.rb)83
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb (renamed from railties/lib/rails/log_subscriber/test_helper.rb)25
-rw-r--r--activesupport/lib/active_support/multibyte.rb2
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb122
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb2
-rw-r--r--activesupport/lib/active_support/notifications.rb3
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb6
-rw-r--r--activesupport/lib/active_support/railtie.rb102
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb19
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb302
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin710743 -> 813343 bytes
-rw-r--r--activesupport/test/abstract_unit.rb4
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb8
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb4
-rw-r--r--activesupport/test/dependencies_test.rb36
-rw-r--r--activesupport/test/descendants_tracker_test.rb75
-rw-r--r--activesupport/test/file_update_checker_test.rb56
-rw-r--r--activesupport/test/log_subscriber_test.rb (renamed from railties/test/log_subscriber_test.rb)34
-rw-r--r--activesupport/test/multibyte_chars_test.rb29
-rw-r--r--activesupport/test/ordered_hash_test.rb19
-rw-r--r--activesupport/test/time_zone_test.rb7
-rwxr-xr-xbin/rails2
-rw-r--r--railties/CHANGELOG7
-rw-r--r--railties/Rakefile4
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/javascripts/code_highlighter.js0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/javascripts/guides.js0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/print.css0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/reset.css0
-rw-r--r--[-rwxr-xr-x]railties/guides/assets/stylesheets/style.css0
-rw-r--r--railties/guides/source/action_controller_overview.textile19
-rw-r--r--railties/guides/source/active_record_basics.textile48
-rw-r--r--railties/guides/source/active_record_querying.textile4
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile (renamed from railties/guides/source/activerecord_validations_callbacks.textile)30
-rw-r--r--railties/guides/source/active_support_core_extensions.textile131
-rw-r--r--railties/guides/source/form_helpers.textile2
-rw-r--r--railties/guides/source/generators.textile74
-rw-r--r--railties/guides/source/getting_started.textile2
-rw-r--r--railties/guides/source/index.html.erb2
-rw-r--r--railties/guides/source/initialization.textile2
-rw-r--r--railties/guides/source/layout.html.erb2
-rw-r--r--railties/guides/source/rails_application_templates.textile4
-rw-r--r--railties/guides/source/routing.textile31
-rw-r--r--railties/guides/w3c_validator.rb36
-rw-r--r--railties/lib/rails.rb1
-rw-r--r--railties/lib/rails/application.rb42
-rw-r--r--railties/lib/rails/application/bootstrap.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb6
-rw-r--r--railties/lib/rails/application/finisher.rb12
-rw-r--r--railties/lib/rails/application/routes_reloader.rb46
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/commands/generate.rb0
-rw-r--r--railties/lib/rails/commands/runner.rb1
-rw-r--r--railties/lib/rails/commands/server.rb5
-rw-r--r--railties/lib/rails/console/app.rb2
-rw-r--r--railties/lib/rails/engine.rb29
-rw-r--r--railties/lib/rails/engine/configuration.rb10
-rw-r--r--railties/lib/rails/generators.rb3
-rw-r--r--railties/lib/rails/generators/actions.rb2
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb7
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb8
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb6
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/index.html.erb14
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/new.html.erb4
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb6
-rw-r--r--railties/lib/rails/generators/named_base.rb24
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile7
-rw-r--r--railties/lib/rails/generators/rails/app/templates/README8
-rw-r--r--[-rwxr-xr-x]railties/lib/rails/generators/rails/app/templates/Rakefile0
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml60
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt3
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt3
-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/inflections.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/routes.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js171
-rw-r--r--railties/lib/rails/generators/rails/model/USAGE2
-rw-r--r--railties/lib/rails/generators/rails/resource/resource_generator.rb18
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb58
-rw-r--r--railties/lib/rails/generators/resource_helpers.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/model/model_generator.rb2
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb1
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb28
-rw-r--r--railties/lib/rails/initializable.rb19
-rw-r--r--railties/lib/rails/paths.rb51
-rw-r--r--railties/lib/rails/plugin.rb16
-rw-r--r--railties/lib/rails/rack/logger.rb24
-rw-r--r--railties/lib/rails/railtie.rb34
-rw-r--r--railties/lib/rails/webrick_server.rb156
-rw-r--r--railties/test/abstract_unit.rb2
-rw-r--r--railties/test/application/initializers/frameworks_test.rb10
-rw-r--r--railties/test/application/initializers/i18n_test.rb133
-rw-r--r--railties/test/application/initializers/load_path_test.rb2
-rw-r--r--railties/test/application/loading_test.rb86
-rw-r--r--railties/test/application/middleware_test.rb11
-rw-r--r--railties/test/application/model_initialization_test.rb33
-rw-r--r--railties/test/application/rake_test.rb2
-rw-r--r--railties/test/application/runner_test.rb49
-rw-r--r--railties/test/application/url_generation_test.rb1
-rw-r--r--railties/test/generators/actions_test.rb6
-rw-r--r--railties/test/generators/migration_generator_test.rb15
-rw-r--r--railties/test/generators/named_base_test.rb10
-rw-r--r--railties/test/generators/resource_generator_test.rb8
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb14
-rw-r--r--railties/test/generators/scaffold_generator_test.rb106
-rw-r--r--railties/test/initializable_test.rb39
-rw-r--r--railties/test/isolation/abstract_unit.rb3
-rw-r--r--railties/test/paths_test.rb56
-rw-r--r--railties/test/railties/engine_test.rb6
-rw-r--r--railties/test/railties/i18n_railtie_test.rb89
-rw-r--r--railties/test/railties/plugin_test.rb30
-rw-r--r--railties/test/railties/railtie_test.rb9
-rw-r--r--railties/test/railties/shared_tests.rb45
-rwxr-xr-xtools/profile8
-rw-r--r--version.rb2
437 files changed, 10662 insertions, 4334 deletions
diff --git a/Gemfile b/Gemfile
index fe6ef9aa8a..827c67e522 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,6 +1,7 @@
source 'http://rubygems.org'
gem "arel", :git => "git://github.com/rails/arel.git"
+#gem "rack-mount", :git => "git://github.com/rails/rack-mount.git"
gem "rails", :path => File.dirname(__FILE__)
gem "rake", ">= 0.8.7"
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 9e3437685d..11fa978b9c 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -4,6 +4,7 @@ require 'action_mailer/collector'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/proc'
+require 'action_mailer/log_subscriber'
module ActionMailer #:nodoc:
# Action Mailer allows you to send email from your application using a mailer model and views.
@@ -182,7 +183,7 @@ module ActionMailer #:nodoc:
# end
#
# Which will (if it had both a <tt>welcome.text.plain.erb</tt> and <tt>welcome.text.html.erb</tt>
- # tempalte in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
+ # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts,
# the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside,
# and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book
# with the filename +free_book.pdf+.
@@ -395,7 +396,7 @@ module ActionMailer #:nodoc:
end
# Wraps an email delivery inside of Active Support Notifications instrumentation. This
- # method is actually called by the <tt>Mail::Message</tt> object itself through a call back
+ # method is actually called by the <tt>Mail::Message</tt> object itself through a callback
# when you call <tt>:deliver</tt> on the Mail::Message, calling +deliver_mail+ directly
# and passing a Mail::Message will do nothing except tell the logger you sent the email.
def deliver_mail(mail) #:nodoc:
@@ -534,7 +535,9 @@ module ActionMailer #:nodoc:
# :reply_to => 'bounces@test.lindsaar.net'
# end
#
- # If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method.
+ # If you need other headers not listed above, you can either pass them in
+ # as part of the headers hash or use the <tt>headers['name'] = value</tt>
+ # method.
#
# When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from'
# address for the Mail message. Setting this is useful when you want delivery notifications
diff --git a/actionmailer/lib/action_mailer/deprecated_api.rb b/actionmailer/lib/action_mailer/deprecated_api.rb
index c08ab4164e..0070d8e016 100644
--- a/actionmailer/lib/action_mailer/deprecated_api.rb
+++ b/actionmailer/lib/action_mailer/deprecated_api.rb
@@ -1,7 +1,7 @@
module ActionMailer
# This is the API which is deprecated and is going to be removed on Rails 3.1 release.
# Part of the old API will be deprecated after 3.1, for a smoother deprecation process.
- # Chech those in OldApi instead.
+ # Check those in OldApi instead.
module DeprecatedApi #:nodoc:
extend ActiveSupport::Concern
diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb
new file mode 100644
index 0000000000..7ba57b19e0
--- /dev/null
+++ b/actionmailer/lib/action_mailer/log_subscriber.rb
@@ -0,0 +1,22 @@
+require 'active_support/core_ext/array/wrap'
+
+module ActionMailer
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def deliver(event)
+ recipients = Array.wrap(event.payload[:to]).join(', ')
+ info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
+ debug(event.payload[:mail])
+ end
+
+ def receive(event)
+ info("\nReceived mail (%.1fms)" % event.duration)
+ debug(event.payload[:mail])
+ end
+
+ def logger
+ ActionMailer::Base.logger
+ end
+ end
+end
+
+ActionMailer::LogSubscriber.attach_to :action_mailer \ No newline at end of file
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index 43a4936013..d7b09b2dc6 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -5,9 +5,6 @@ module ActionMailer
class Railtie < Rails::Railtie
config.action_mailer = ActiveSupport::OrderedOptions.new
- require "action_mailer/railties/log_subscriber"
- log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new
-
initializer "action_mailer.logger" do
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
end
diff --git a/actionmailer/lib/action_mailer/railties/log_subscriber.rb b/actionmailer/lib/action_mailer/railties/log_subscriber.rb
deleted file mode 100644
index af76d807d0..0000000000
--- a/actionmailer/lib/action_mailer/railties/log_subscriber.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-require 'active_support/core_ext/array/wrap'
-
-module ActionMailer
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def deliver(event)
- recipients = Array.wrap(event.payload[:to]).join(', ')
- info("\nSent mail to #{recipients} (%1.fms)" % event.duration)
- debug(event.payload[:mail])
- end
-
- def receive(event)
- info("\nReceived mail (%.1fms)" % event.duration)
- debug(event.payload[:mail])
- end
-
- def logger
- ActionMailer::Base.logger
- end
- end
- end
-end
diff --git a/actionmailer/test/log_subscriber_test.rb b/actionmailer/test/log_subscriber_test.rb
index c08c34b4a2..ba9b4d6500 100644
--- a/actionmailer/test/log_subscriber_test.rb
+++ b/actionmailer/test/log_subscriber_test.rb
@@ -1,13 +1,13 @@
require "abstract_unit"
-require "rails/log_subscriber/test_helper"
-require "action_mailer/railties/log_subscriber"
+require "active_support/log_subscriber/test_helper"
+require "action_mailer/log_subscriber"
class AMLogSubscriberTest < ActionMailer::TestCase
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
super
- Rails::LogSubscriber.add(:action_mailer, ActionMailer::Railties::LogSubscriber.new)
+ ActionMailer::LogSubscriber.attach_to :action_mailer
end
class TestMailer < ActionMailer::Base
diff --git a/actionmailer/test/old_base/mail_service_test.rb b/actionmailer/test/old_base/mail_service_test.rb
index 527b37218a..831adf3e32 100644
--- a/actionmailer/test/old_base/mail_service_test.rb
+++ b/actionmailer/test/old_base/mail_service_test.rb
@@ -674,7 +674,7 @@ The body
EOF
mail = Mail.new(msg)
assert_equal "testing testing \326\244", mail.subject
- assert_equal "Subject: testing testing =?UTF-8?Q?_=D6=A4=?=\r\n", mail[:subject].encoded
+ assert_equal "Subject: =?UTF-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded
end
def test_unquote_7bit_subject
@@ -863,7 +863,7 @@ EOF
def test_multipart_with_utf8_subject
mail = TestMailer.multipart_with_utf8_subject(@recipient)
- regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
+ regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)
@@ -871,7 +871,7 @@ EOF
def test_implicitly_multipart_with_utf8
mail = TestMailer.implicitly_multipart_with_utf8
- regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=')
+ regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=')
assert_match(/#{regex}/, mail.encoded)
string = "Foo áëô îü"
assert_match(string, mail.subject)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index a00f63dcd4..32aba2091a 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,22 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn]
+
+* url_for returns always unescaped strings, and the :escape option is gone. [fxn]
+
+* Added accept-charset parameter and _snowman hidden field to force the contents
+ of Rails POSTed forms to be in UTF-8 [Yehuda Katz]
+
+* Upgrade to Rack 1.2.1 [Jeremy Kemper]
+
+* Allow :path to be given to match/get/post/put/delete instead of :path_names in the new router [Carlos Antônio da Silva]
+
+* Added resources_path_names to the new router DSL [José Valim]
+
+* Allow options to be given to the namespace method in the new router [Carlos Antônio da Silva]
+
+* Deprecate :name_prefix in the new router DSL [José Valim]
+
* Add shallow routes back to the new router [Diego Carrion, Andrew White]
resources :posts do
@@ -14,7 +31,6 @@
* Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino]
-
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Remove middleware laziness [José Valim]
diff --git a/actionpack/README b/actionpack/README
index 8bdcb9120a..1a59f728cc 100644
--- a/actionpack/README
+++ b/actionpack/README
@@ -183,20 +183,20 @@ A short rundown of the major features:
* Automated benchmarking and integrated logging
- Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
- Parameters: {"action"=>"index", "controller"=>"weblog"}
- Rendering weblog/index (200 OK)
- Completed in 0.029281 (34 reqs/sec)
+ Started GET "/weblog" for 127.0.0.1 at Fri May 28 00:41:55
+ Processing by WeblogController#index as HTML
+ Rendered weblog/index.html.erb within layouts/application (25.7ms)
+ Completed 200 OK in 29.3ms
If Active Record is used as the model, you'll have the database debugging
as well:
- Processing PostsController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
- Params: {"controller"=>"posts", "action"=>"create",
- "post"=>{"title"=>"this is good"} }
- SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
+ Started POST "/posts" for 127.0.0.1 at Sat Jun 19 14:04:23
+ Processing by PostsController#create as HTML
+ Parameters: {"post"=>{"title"=>"this is good"}}
+ SQL (0.6ms) INSERT INTO posts (title) VALUES('this is good')
Redirected to http://example.com/posts/5
- Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
+ Completed 302 Found in 221ms (Views: 215ms | ActiveRecord: 0.6ms)
You specify a logger through a class method, such as:
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 8e95315252..b4311599a9 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -23,9 +23,9 @@ Gem::Specification.new do |s|
s.add_dependency('activemodel', version)
s.add_dependency('builder', '~> 2.1.2')
s.add_dependency('i18n', '~> 0.4.1')
- s.add_dependency('rack', '~> 1.1.0')
+ s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.4')
- s.add_dependency('rack-mount', '~> 0.6.4')
+ #s.add_dependency('rack-mount', '~> 0.6.6')
s.add_dependency('tzinfo', '~> 0.3.16')
s.add_dependency('erubis', '~> 2.6.5')
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index e1027840ef..8a8337858b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -1,4 +1,5 @@
require 'active_support/configurable'
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/module/anonymous'
module AbstractController
@@ -10,6 +11,7 @@ module AbstractController
attr_internal :action_name
include ActiveSupport::Configurable
+ extend ActiveSupport::DescendantsTracker
class << self
attr_reader :abstract
@@ -21,17 +23,6 @@ module AbstractController
@abstract = true
end
- def inherited(klass)
- ::AbstractController::Base.descendants << klass.to_s
- super
- end
-
- # A list of all descendents of AbstractController::Base. This is
- # useful for initializers which need to add behavior to all controllers.
- def descendants
- @descendants ||= []
- end
-
# A list of all internal methods for a controller. This finds the first
# abstract superclass of a controller, and gets a list of all public
# instance methods on that abstract class. Public instance methods of
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 8611d0d3c3..a70ba0d2e3 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,3 +1,5 @@
+require "action_controller/log_subscriber"
+
module ActionController
class Base < Metal
abstract!
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
new file mode 100644
index 0000000000..ece270b3ce
--- /dev/null
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -0,0 +1,56 @@
+require 'active_support/core_ext/object/blank'
+
+module ActionController
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ INTERNAL_PARAMS = %w(controller action format _method only_path)
+
+ def start_processing(event)
+ payload = event.payload
+ params = payload[:params].except(*INTERNAL_PARAMS)
+
+ info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
+ info " Parameters: #{params.inspect}" unless params.empty?
+ end
+
+ def process_action(event)
+ payload = event.payload
+ additions = ActionController::Base.log_process_action(payload)
+
+ message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration
+ message << " (#{additions.join(" | ")})" unless additions.blank?
+
+ info(message)
+ end
+
+ def send_file(event)
+ message = "Sent file %s"
+ message << " (%.1fms)"
+ info(message % [event.payload[:path], event.duration])
+ end
+
+ def redirect_to(event)
+ info "Redirected to #{event.payload[:location]}"
+ end
+
+ def send_data(event)
+ info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration])
+ end
+
+ %w(write_fragment read_fragment exist_fragment?
+ expire_fragment expire_page write_page).each do |method|
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{method}(event)
+ key_or_path = event.payload[:key] || event.payload[:path]
+ human_name = #{method.to_s.humanize.inspect}
+ info("\#{human_name} \#{key_or_path} (%.1fms)" % event.duration)
+ end
+ METHOD
+ end
+
+ def logger
+ ActionController::Base.logger
+ end
+ end
+end
+
+ActionController::LogSubscriber.attach_to :action_controller \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index 775a5002e2..2281c500c5 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -52,8 +52,7 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- # :api: public
- attr_internal :params, :env
+ attr_internal :env
# Returns the last part of the controller's name, underscored, without the ending
# "Controller". For instance, MyApp::MyPostsController would return "my_posts" for
@@ -62,7 +61,7 @@ module ActionController
# ==== Returns
# String
def self.controller_name
- @controller_name ||= controller_path.split("/").last
+ @controller_name ||= self.name.demodulize.sub(/Controller$/, '').underscore
end
# Delegates to the class' #controller_name
@@ -85,6 +84,14 @@ module ActionController
super
end
+ def params
+ @_params ||= request.parameters
+ end
+
+ def params=(val)
+ @_params = val
+ end
+
# Basic implementations for content_type=, location=, and headers are
# provided to reduce the dependency on the RackDelegation module
# in Renderer and Redirector.
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index b6d4fb1284..89201fb5ee 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -58,12 +58,12 @@ module ActionController
module ClassMethods
def helpers_dir
- ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead"
+ ActiveSupport::Deprecation.warn "helpers_dir is deprecated, use helpers_path instead", caller
self.helpers_path
end
def helpers_dir=(value)
- ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead"
+ ActiveSupport::Deprecation.warn "helpers_dir= is deprecated, use helpers_path= instead", caller
self.helpers_path = Array(value)
end
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index 508ea6e2b7..544b4989c7 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -14,10 +14,6 @@ module ActionController
super(action, request)
end
- def params
- @_params ||= @_request.parameters
- end
-
def response_body=(body)
response.body = body if response
super
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 8c25b147ef..b632e7aab6 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -55,7 +55,7 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
- # Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
+ # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 22bdcd0f3c..cb644dfd16 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -89,9 +89,7 @@ module ActionController #:nodoc:
def initialize(controller, resources, options={})
@controller = controller
- @request = controller.request
- @format = controller.formats.first
- @resource = resources.is_a?(Array) ? resources.last : resources
+ @resource = resources.last
@resources = resources
@options = options
@action = options.delete(:action)
@@ -101,6 +99,14 @@ module ActionController #:nodoc:
delegate :head, :render, :redirect_to, :to => :controller
delegate :get?, :post?, :put?, :delete?, :to => :request
+ def request
+ @request ||= @controller.request
+ end
+
+ def format
+ @format ||= @controller.formats.first
+ end
+
# Undefine :to_json and :to_yaml since it's defined on Object
undef_method(:to_json) if method_defined?(:to_json)
undef_method(:to_yaml) if method_defined?(:to_yaml)
@@ -147,7 +153,7 @@ module ActionController #:nodoc:
elsif has_errors? && default_action
render :action => default_action
else
- redirect_to resource_location
+ redirect_to navigation_location
end
end
@@ -160,7 +166,7 @@ module ActionController #:nodoc:
elsif has_errors?
display resource.errors, :status => :unprocessable_entity
elsif post?
- display resource, :status => :created, :location => resource_location
+ display resource, :status => :created, :location => api_location
else
head :ok
end
@@ -178,6 +184,8 @@ module ActionController #:nodoc:
def resource_location
options[:location] || resources
end
+ alias :navigation_location :resource_location
+ alias :api_location :resource_location
# If a given response block was given, use it, otherwise call render on
# controller.
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 7f2eb4306b..bee50a7a3b 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -11,7 +11,7 @@ module ActionController
# polymorphic_url([:admin, @article, @comment])
#
# results in:
- #
+ #
# admin_article_comment_url(@article, @comment)
#
# == Usage within the framework
@@ -166,6 +166,7 @@ module ActionController
route << RecordIdentifier.__send__("plural_class_name", record)
route = route.singularize if inflection == :singular
route << "_"
+ route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural
end
action_prefix(options) + route + routing_type(options).to_s
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 0e3cdffadc..86395c4d93 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -6,7 +6,6 @@ require "active_support/core_ext/class/subclasses"
require "active_support/deprecation/proxy_wrappers"
require "active_support/deprecation"
-require "action_controller/railties/log_subscriber"
require "action_controller/railties/url_helpers"
module ActionController
@@ -35,8 +34,6 @@ module ActionController
end
end
- log_subscriber :action_controller, ActionController::Railties::LogSubscriber.new
-
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
ac = app.config.action_controller
diff --git a/actionpack/lib/action_controller/railties/log_subscriber.rb b/actionpack/lib/action_controller/railties/log_subscriber.rb
deleted file mode 100644
index 00ac3bdf67..0000000000
--- a/actionpack/lib/action_controller/railties/log_subscriber.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'active_support/core_ext/object/blank'
-
-module ActionController
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- INTERNAL_PARAMS = %w(controller action format _method only_path)
-
- def start_processing(event)
- payload = event.payload
- params = payload[:params].except(*INTERNAL_PARAMS)
-
- info " Processing by #{payload[:controller]}##{payload[:action]} as #{payload[:formats].first.to_s.upcase}"
- info " Parameters: #{params.inspect}" unless params.empty?
- end
-
- def process_action(event)
- payload = event.payload
- additions = ActionController::Base.log_process_action(payload)
-
- message = "Completed #{payload[:status]} #{Rack::Utils::HTTP_STATUS_CODES[payload[:status]]} in %.0fms" % event.duration
- message << " (#{additions.join(" | ")})" unless additions.blank?
-
- info(message)
- end
-
- def send_file(event)
- message = "Sent file %s"
- message << " (%.1fms)"
- info(message % [event.payload[:path], event.duration])
- end
-
- def redirect_to(event)
- info "Redirected to #{event.payload[:location]}"
- end
-
- def send_data(event)
- info("Sent data %s (%.1fms)" % [event.payload[:filename], event.duration])
- end
-
- %w(write_fragment read_fragment exist_fragment?
- expire_fragment expire_page write_page).each do |method|
- class_eval <<-METHOD, __FILE__, __LINE__ + 1
- def #{method}(event)
- key_or_path = event.payload[:key] || event.payload[:path]
- human_name = #{method.to_s.humanize.inspect}
- info("\#{human_name} \#{key_or_path} (%.1fms)" % event.duration)
- end
- METHOD
- end
-
- def logger
- ActionController::Base.logger
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 907c369218..d20c3b64c5 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/module'
module ActionController
- # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
+ # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or
# Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate
# the view actions to a higher logical level. Example:
#
@@ -28,7 +28,7 @@ module ActionController
# end
#
# As the example above shows, you can stop caring to a large extent what the actual id of the post is. You just know
- # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
+ # that one is being assigned and that the subsequent calls in redirect_to and the RJS expect that same naming
# convention and allows you to write less code if you follow it.
module RecordIdentifier
extend self
@@ -59,7 +59,7 @@ module ActionController
# If you need to address multiple instances of the same class in the same view, you can prefix the dom_id:
#
# dom_id(Post.find(45), :edit) # => "edit_post_45"
- def dom_id(record, prefix = nil)
+ def dom_id(record, prefix = nil)
if record_id = record_key_for_dom_id(record)
"#{dom_class(record, prefix)}#{JOIN}#{record_id}"
else
@@ -102,6 +102,14 @@ module ActionController
model_name_from_record_or_class(record_or_class).singular
end
+ # Identifies whether the class name of a record or class is uncountable. Examples:
+ #
+ # uncountable?(Sheep) # => true
+ # uncountable?(Post) => false
+ def uncountable?(record_or_class)
+ plural_class_name(record_or_class) == singular_class_name(record_or_class)
+ end
+
private
def model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 21281b606e..650eb16ac0 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -40,7 +40,7 @@ module ActionController
ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
end
- # Asserts that the request was rendered with the appropriate template file or partials
+ # Asserts that the request was rendered with the appropriate template file or partials.
#
# ==== Examples
#
@@ -53,6 +53,12 @@ module ActionController
# # assert that no partials were rendered
# assert_template :partial => false
#
+ # In a view test case, you can also assert that specific locals are passed
+ # to partials:
+ #
+ # # assert that the "_customer" partial was rendered with a specific object
+ # assert_template :partial => '_customer', :locals => { :customer => @customer }
+ #
def assert_template(options = {}, message = nil)
validate_request!
@@ -72,9 +78,13 @@ module ActionController
end
when Hash
if expected_partial = options[:partial]
- if expected_count = options[:count]
+ if expected_locals = options[:locals]
+ actual_locals = @locals[expected_partial.to_s.sub(/^_/,'')]
+ expected_locals.each_pair do |k,v|
+ assert_equal(v, actual_locals[k])
+ end
+ elsif expected_count = options[:count]
actual_count = @partials[expected_partial]
- # actual_count = found.nil? ? 0 : found[1]
msg = build_message(message,
"expecting ? to be rendered ? time(s) but rendered ? time(s)",
expected_partial, expected_count, actual_count)
@@ -183,12 +193,14 @@ module ActionController
replace(session.stringify_keys)
@loaded = true
end
+
+ def exists?; true; end
end
# Superclass for ActionController functional tests. Functional tests allow you to
# test a single controller action per test method. This should not be confused with
# integration tests (see ActionController::IntegrationTest), which are more like
- # "stories" that can involve multiple controllers and mutliple actions (i.e. multiple
+ # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
# different HTTP requests).
#
# == Basic example
@@ -442,7 +454,7 @@ module ActionController
end
# When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
- # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
+ # (skipping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
# rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
# than 0.0.0.0.
#
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
index 6c0331636c..a874519978 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb
@@ -177,6 +177,7 @@ module HTML #:nodoc:
case text
when "\\" then
value << text
+ break if scanner.eos?
value << scanner.getch
when delim
break
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
index 064ff3724d..240dc1890f 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb
@@ -96,6 +96,7 @@ module HTML #:nodoc:
while match = @scanner.scan_until(/[\\#{delim}]/)
text << match
break if @scanner.matched == delim
+ break if @scanner.eos?
text << @scanner.getch # skip the escaped character
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 9b9e81440b..e9fdf75cc8 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -89,7 +89,7 @@ module ActionDispatch
if etag? || last_modified? || !@cache_control.empty?
set_conditional_cache_control!
elsif nonempty_ok_response?
- self.etag = @body
+ self.etag = body
if request && request.etag_matches?(etag)
self.status = 304
@@ -137,4 +137,4 @@ module ActionDispatch
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index d6a805bf3b..c6fc582851 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -60,7 +60,7 @@ module Mime
def initialize(order, name, q=nil)
@order = order
@name = name.strip
- q ||= 0.0 if @name == Mime::ALL # default wilcard match to end of list
+ q ||= 0.0 if @name == Mime::ALL # default wildcard match to end of list
@q = ((q || 1.0).to_f * 100).to_i
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index bc43414e75..add8cab2ab 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -6,7 +6,11 @@ module ActionDispatch
module Parameters
# Returns both GET and POST \parameters in a single hash.
def parameters
- @env["action_dispatch.request.parameters"] ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
+ @env["action_dispatch.request.parameters"] ||= begin
+ params = request_parameters.merge(query_parameters)
+ params.merge!(path_parameters)
+ encode_params(params).with_indifferent_access
+ end
end
alias :params :parameters
@@ -32,7 +36,32 @@ module ActionDispatch
end
private
- # Convert nested Hashs to HashWithIndifferentAccess
+
+ # TODO: Validate that the characters are UTF-8. If they aren't,
+ # you'll get a weird error down the road, but our form handling
+ # should really prevent that from happening
+ def encode_params(params)
+ return params unless "ruby".encoding_aware?
+
+ if params.is_a?(String)
+ return params.force_encoding("UTF-8").encode!
+ elsif !params.is_a?(Hash)
+ return params
+ end
+
+ params.each do |k, v|
+ case v
+ when Hash
+ encode_params(v)
+ when Array
+ v.map! {|el| encode_params(el) }
+ else
+ encode_params(v)
+ end
+ end
+ end
+
+ # Convert nested Hash to HashWithIndifferentAccess
def normalize_parameters(value)
case value
when Hash
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 98f4f5ae18..fd23b1df79 100755..100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -194,9 +194,12 @@ module ActionDispatch
@env['rack.input']
end
+ # TODO This should be broken apart into AD::Request::Session and probably
+ # be included by the session middleware.
def reset_session
- self.session_options.delete(:id)
+ session.destroy if session
self.session = {}
+ @env['action_dispatch.request.flash_hash'] = nil
end
def session=(session) #:nodoc:
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 81d2517304..8ee4b81cdd 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -31,8 +31,8 @@ module ActionDispatch
end
module Upload
- # Convert nested Hashs to HashWithIndifferentAccess and replace
- # file upload hashs with UploadedFile objects
+ # Convert nested Hash to HashWithIndifferentAccess and replace
+ # file upload hash with UploadedFile objects
def normalize_parameters(value)
if Hash === value && value.has_key?(:tempfile)
upload = value[:tempfile]
diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb
index d07841218a..e4ae480bfb 100644
--- a/actionpack/lib/action_dispatch/middleware/callbacks.rb
+++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb
@@ -8,7 +8,7 @@ module ActionDispatch
class Callbacks
include ActiveSupport::Callbacks
- define_callbacks :call, :terminator => "result == false", :rescuable => true
+ define_callbacks :call, :rescuable => true
define_callbacks :prepare, :scope => :name
# Add a preparation callback. Preparation callbacks are run before every
@@ -37,12 +37,12 @@ module ActionDispatch
def initialize(app, prepare_each_request = false)
@app, @prepare_each_request = app, prepare_each_request
- run_callbacks(:prepare)
+ _run_prepare_callbacks
end
def call(env)
- run_callbacks(:call) do
- run_callbacks(:prepare) if @prepare_each_request
+ _run_call_callbacks do
+ _run_prepare_callbacks if @prepare_each_request
@app.call(env)
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb
index 18771fe782..bfa30cf5af 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
- session['flash'] ||= Flash::FlashHash.new
+ @env['action_dispatch.request.flash_hash'] ||= (session["flash"] || Flash::FlashHash.new)
end
end
@@ -176,7 +176,14 @@ module ActionDispatch
@app.call(env)
ensure
- if (session = env['rack.session']) && session.key?('flash') && session['flash'].empty?
+ session = env['rack.session'] || {}
+ flash_hash = env['action_dispatch.request.flash_hash']
+
+ if flash_hash && (!flash_hash.empty? || session.key?('flash'))
+ session["flash"] = flash_hash
+ end
+
+ if session.key?('flash') && session['flash'].empty?
session.delete('flash')
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 3e8d64b0c6..08bc80dbc2 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -12,6 +12,35 @@ module ActionDispatch
ENV_SESSION_KEY = 'rack.session'.freeze
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
+ # thin wrapper around Hash that allows us to lazily
+ # load session id into session_options
+ class OptionsHash < Hash
+ def initialize(by, env, default_options)
+ @by = by
+ @env = env
+ @session_id_loaded = false
+ merge!(default_options)
+ end
+
+ def [](key)
+ if key == :id
+ load_session_id! unless super(:id) || has_session_id?
+ end
+ super(key)
+ end
+
+ private
+
+ def has_session_id?
+ @session_id_loaded
+ end
+
+ def load_session_id!
+ self[:id] = @by.send(:extract_session_id, @env)
+ @session_id_loaded = true
+ end
+ end
+
class SessionHash < Hash
def initialize(by, env)
super()
@@ -21,66 +50,75 @@ module ActionDispatch
end
def [](key)
- load! unless @loaded
+ load_for_read!
+ super(key.to_s)
+ end
+
+ def has_key?(key)
+ load_for_read!
super(key.to_s)
end
def []=(key, value)
- load! unless @loaded
+ load_for_write!
super(key.to_s, value)
end
def to_hash
+ load_for_read!
h = {}.replace(self)
h.delete_if { |k,v| v.nil? }
h
end
def update(hash)
- load! unless @loaded
+ load_for_write!
super(hash.stringify_keys)
end
def delete(key)
- load! unless @loaded
+ load_for_write!
super(key.to_s)
end
def inspect
- load! unless @loaded
+ load_for_read!
super
end
+ def exists?
+ return @exists if instance_variable_defined?(:@exists)
+ @exists = @by.send(:exists?, @env)
+ end
+
def loaded?
@loaded
end
+ def destroy
+ clear
+ @by.send(:destroy, @env) if @by
+ @env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY]
+ @loaded = false
+ end
+
private
- def load!
- stale_session_check! do
- id, session = @by.send(:load_session, @env)
- (@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
- replace(session.stringify_keys)
- @loaded = true
- end
+
+ def load_for_read!
+ load! if !loaded? && exists?
end
- def stale_session_check!
- yield
- rescue ArgumentError => argument_error
- if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
- begin
- # Note that the regexp does not allow $1 to end with a ':'
- $1.constantize
- rescue LoadError, NameError => const_error
- raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
- end
-
- retry
- else
- raise
- end
+ def load_for_write!
+ load! unless loaded?
+ end
+
+ def load!
+ id, session = @by.send(:load_session, @env)
+ @env[ENV_SESSION_OPTIONS_KEY][:id] = id
+ replace(session.stringify_keys)
+ @loaded = true
end
+
end
DEFAULT_OPTIONS = {
@@ -108,8 +146,8 @@ module ActionDispatch
session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY]
- if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
- session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
+ session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid
session_data = session_data.to_hash
@@ -133,7 +171,7 @@ module ActionDispatch
def prepare!(env)
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
- env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
+ env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
end
def generate_sid
@@ -141,15 +179,30 @@ module ActionDispatch
end
def set_cookie(request, options)
- request.cookie_jar[@key] = options
+ if request.cookie_jar[@key] != options[:value] || !options[:expires].nil?
+ request.cookie_jar[@key] = options
+ end
end
def load_session(env)
- request = Rack::Request.new(env)
- sid = request.cookies[@key]
- sid ||= request.params[@key] unless @cookie_only
- sid, session = get_session(env, sid)
- [sid, session]
+ stale_session_check! do
+ sid = current_session_id(env)
+ sid, session = get_session(env, sid)
+ [sid, session]
+ end
+ end
+
+ def extract_session_id(env)
+ stale_session_check! do
+ request = ActionDispatch::Request.new(env)
+ sid = request.cookies[@key]
+ sid ||= request.params[@key] unless @cookie_only
+ sid
+ end
+ end
+
+ def current_session_id(env)
+ env[ENV_SESSION_OPTIONS_KEY][:id]
end
def ensure_session_key!
@@ -161,6 +214,26 @@ module ActionDispatch
end
end
+ def stale_session_check!
+ yield
+ rescue ArgumentError => argument_error
+ if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
+ begin
+ # Note that the regexp does not allow $1 to end with a ':'
+ $1.constantize
+ rescue LoadError, NameError => const_error
+ raise ActionDispatch::Session::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n"
+ end
+ retry
+ else
+ raise
+ end
+ end
+
+ def exists?(env)
+ current_session_id(env).present?
+ end
+
def get_session(env, sid)
raise '#get_session needs to be implemented.'
end
@@ -169,6 +242,10 @@ module ActionDispatch
raise '#set_session needs to be implemented and should return ' <<
'the value to be stored in the cookie (usually the sid)'
end
+
+ def destroy(env)
+ raise '#destroy needs to be implemented.'
+ end
end
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 92a86ee229..dce47c63bc 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -39,16 +39,6 @@ module ActionDispatch
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < AbstractStore
- class OptionsHash < Hash
- def initialize(by, env, default_options)
- @session_data = env[AbstractStore::ENV_SESSION_KEY]
- merge!(default_options)
- end
-
- def [](key)
- key == :id ? @session_data[:session_id] : super(key)
- end
- end
def initialize(app, options = {})
super(app, options.merge!(:cookie_only => true))
@@ -57,19 +47,32 @@ module ActionDispatch
private
- def prepare!(env)
- env[ENV_SESSION_KEY] = SessionHash.new(self, env)
- env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
- end
-
def load_session(env)
- request = ActionDispatch::Request.new(env)
- data = request.cookie_jar.signed[@key]
+ data = unpacked_cookie_data(env)
data = persistent_session_id!(data)
- data.stringify_keys!
[data["session_id"], data]
end
+ def extract_session_id(env)
+ if data = unpacked_cookie_data(env)
+ data["session_id"]
+ else
+ nil
+ end
+ end
+
+ def unpacked_cookie_data(env)
+ env["action_dispatch.request.unsigned_session_cookie"] ||= begin
+ stale_session_check! do
+ request = ActionDispatch::Request.new(env)
+ if data = request.cookie_jar.signed[@key]
+ data.stringify_keys!
+ end
+ data || {}
+ end
+ end
+ end
+
def set_cookie(request, options)
request.cookie_jar.signed[@key] = options
end
@@ -78,6 +81,10 @@ module ActionDispatch
persistent_session_id!(session_data, sid)
end
+ def destroy(env)
+ # session data is stored on client; nothing to do here
+ end
+
def persistent_session_id!(data, sid=nil)
data ||= {}
data["session_id"] ||= sid || generate_sid
diff --git a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
index 8df8f977e8..28e3dbd732 100644
--- a/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/mem_cache_store.rb
@@ -42,6 +42,15 @@ module ActionDispatch
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
false
end
+
+ def destroy(env)
+ if sid = current_session_id(env)
+ @pool.delete(sid)
+ end
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED
+ false
+ end
+
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 0a6d2bfc8a..e095b51342 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -122,7 +122,7 @@ module ActionDispatch
end
def render(status, body)
- [status, {'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s}, [body]]
+ [status, {'Content-Type' => 'text/html', 'Content-Length' => body.bytesize.to_s}, [body]]
end
def public_path
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 4240e7a5d5..4618f3befc 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -70,7 +70,7 @@ module ActionDispatch
end
def active
- ActiveSupport::Deprecation.warn "All middlewares in the chaing are active since the laziness " <<
+ ActiveSupport::Deprecation.warn "All middlewares in the chain are active since the laziness " <<
"was removed from the middleware stack", caller
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index 38da44d7e7..ed93211255 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -10,7 +10,7 @@ module ActionDispatch
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
- ActionDispatch::Callbacks.to_prepare { app.routes_reloader.reload_if_changed }
+ ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated }
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 3d9084a27e..401d98b663 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/regexp'
require 'action_controller/polymorphic_routes'
module ActionDispatch
- # == Routing
+ # = Routing
#
# The routing module provides URL rewriting in native Ruby. It's a way to
# redirect incoming requests to controllers and actions. This replaces
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
index 1b676669e2..bc3f62fb48 100644
--- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -28,61 +28,13 @@ module ActionDispatch
end
end
- # Mapper instances are used to build routes. The object passed to the draw
- # block in config/routes.rb is a Mapper instance.
- #
- # Mapper instances have relatively few instance methods, in order to avoid
- # clashes with named routes.
- #
- # == Overview
- #
- # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms,
- # is something that can be pointed at and it will respond with a representation of the data requested.
- # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application
- # requests XML data.
- #
- # RESTful design is based on the assumption that there are four generic verbs that a user of an
- # application can request from a \resource (the noun).
- #
- # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used
- # denotes the type of action that should take place.
- #
- # === The Different Methods and their Usage
- #
- # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request.
- # * POST - Creation of \resources.
- # * PUT - Editing of attributes on a \resource.
- # * DELETE - Deletion of a \resource.
- #
- # === Examples
- #
- # # A GET request on the Posts resource is asking for all Posts
- # GET /posts
- #
- # # A GET request on a single Post resource is asking for that particular Post
- # GET /posts/1
- #
- # # A POST request on the Posts resource is asking for a Post to be created with the supplied details
- # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } }
- #
- # # A PUT request on a single Post resource is asking for a Post to be updated
- # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } }
- #
- # # A DELETE request on a single Post resource is asking for it to be deleted
- # DELETE /posts # with => { :id => 1 }
- #
- # By using the REST convention, users of our application can assume certain things about how the data
- # is requested and how it is returned. Rails simplifies the routing part of RESTful design by
- # supplying you with methods to create them in your routes.rb file.
- #
- # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer
class DeprecatedMapper #:nodoc:
def initialize(set) #:nodoc:
+ ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
+ "Please check how to update your router file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
@set = set
end
- # Create an unnamed route with the provided +path+ and +options+. See
- # ActionDispatch::Routing for an introduction to routes.
def connect(path, options = {})
options = options.dup
@@ -240,17 +192,6 @@ module ActionDispatch
connect(path, options)
end
- # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
- # Example:
- #
- # map.namespace(:admin) do |admin|
- # admin.resources :products,
- # :has_many => [ :tags, :images, :variants ]
- # end
- #
- # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
- # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
- # Admin::TagsController.
def namespace(name, options = {}, &block)
if options[:namespace]
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
@@ -411,334 +352,11 @@ module ActionDispatch
alias_method :nesting_path_prefix, :path
end
- # Creates named routes for implementing verb-oriented controllers
- # for a collection \resource.
- #
- # For example:
- #
- # map.resources :messages
- #
- # will map the following actions in the corresponding controller:
- #
- # class MessagesController < ActionController::Base
- # # GET messages_url
- # def index
- # # return all messages
- # end
- #
- # # GET new_message_url
- # def new
- # # return an HTML form for describing a new message
- # end
- #
- # # POST messages_url
- # def create
- # # create a new message
- # end
- #
- # # GET message_url(:id => 1)
- # def show
- # # find and return a specific message
- # end
- #
- # # GET edit_message_url(:id => 1)
- # def edit
- # # return an HTML form for editing a specific message
- # end
- #
- # # PUT message_url(:id => 1)
- # def update
- # # find and update a specific message
- # end
- #
- # # DELETE message_url(:id => 1)
- # def destroy
- # # delete a specific message
- # end
- # end
- #
- # Along with the routes themselves, +resources+ generates named routes for use in
- # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers:
- #
- # Named Route Helpers
- # ============ =====================================================
- # messages messages_url, hash_for_messages_url,
- # messages_path, hash_for_messages_path
- #
- # message message_url(id), hash_for_message_url(id),
- # message_path(id), hash_for_message_path(id)
- #
- # new_message new_message_url, hash_for_new_message_url,
- # new_message_path, hash_for_new_message_path
- #
- # edit_message edit_message_url(id), hash_for_edit_message_url(id),
- # edit_message_path(id), hash_for_edit_message_path(id)
- #
- # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example:
- #
- # redirect_to :controller => 'messages', :action => 'index'
- # # and
- # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %>
- #
- # now become:
- #
- # redirect_to messages_url
- # # and
- # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically
- #
- # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your
- # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object:
- #
- # <%= form_tag message_path(@message), :method => :put %>
- #
- # or
- #
- # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %>
- #
- # or
- #
- # <% form_for @message do |f| %>
- #
- # which takes into account whether <tt>@message</tt> is a new record or not and generates the
- # path and method accordingly.
- #
- # The +resources+ method accepts the following options to customize the resulting routes:
- # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection.
- # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>,
- # an array of any of the previous, or <tt>:any</tt> if the method does not matter.
- # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+.
- # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member.
- # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
- # * <tt>:controller</tt> - Specify the controller name for the routes.
- # * <tt>:singular</tt> - Specify the singular name used in the member routes.
- # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
- # regular expressions (which must match for the route to match) or extra parameters. For example:
- #
- # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
- #
- # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
- # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
- # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
- # # products_path == '/productos'
- # map.resources :products, :as => 'productos' do |product|
- # # product_reviews_path(product) == '/productos/1234/comentarios'
- # product.resources :product_reviews, :as => 'comentarios'
- # end
- #
- # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current.
- # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources.
- #
- # You may directly specify the routing association with +has_one+ and +has_many+ like:
- #
- # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments]
- #
- # This is the same as:
- #
- # map.resources :notes do |notes|
- # notes.resource :author
- # notes.resources :comments
- # notes.resources :attachments
- # end
- #
- # * <tt>:path_names</tt> - Specify different path names for the actions. For example:
- # # new_products_path == '/productos/nuevo'
- # # bids_product_path(1) == '/productos/1/licitacoes'
- # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' }
- #
- # You can also set default action names from an environment, like this:
- # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' }
- #
- # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables.
- #
- # Weblog comments usually belong to a post, so you might use +resources+ like:
- #
- # map.resources :articles
- # map.resources :comments, :path_prefix => '/articles/:article_id'
- #
- # You can nest +resources+ calls to set this automatically:
- #
- # map.resources :articles do |article|
- # article.resources :comments
- # end
- #
- # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>.
- #
- # article_comments_url(@article)
- # article_comment_url(@article, @comment)
- #
- # article_comments_url(:article_id => @article)
- # article_comment_url(:article_id => @article, :id => @comment)
- #
- # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly:
- #
- # articles_comments_url(@comment.article_id, @comment)
- #
- # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore.
- # Use this if you have named routes that may clash.
- #
- # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_'
- # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_'
- #
- # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource:
- #
- # map.resources :articles do |article|
- # article.resources :comments, :name_prefix => nil
- # end
- #
- # This will yield named \resources like so:
- #
- # comments_url(@article)
- # comment_url(@article, @comment)
- #
- # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member
- # (ie. those with an :id parameter) will not use the parent path prefix or name prefix.
- #
- # The <tt>:shallow</tt> option is inherited by any nested resource(s).
- #
- # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources:
- #
- # map.resources :users, :shallow => true do |user|
- # user.resources :posts do |post|
- # post.resources :comments
- # end
- # end
- # # --> GET /users/1/posts (maps to the PostsController#index action as usual)
- # # also adds the usual named route called "user_posts"
- # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested)
- # # also adds the named route called "post"
- # # --> GET /posts/2/comments (maps to the CommentsController#index action)
- # # also adds the named route called "post_comments"
- # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested)
- # # also adds the named route called "comment"
- #
- # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like:
- #
- # map.resources :users, :has_many => { :posts => :comments }, :shallow => true
- #
- # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to.
- #
- # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a
- # list of action names. By default, routes are generated for all seven actions.
- #
- # For example:
- #
- # map.resources :posts, :only => [:index, :show] do |post|
- # post.resources :comments, :except => [:update, :destroy]
- # end
- # # --> GET /posts (maps to the PostsController#index action)
- # # --> POST /posts (fails)
- # # --> GET /posts/1 (maps to the PostsController#show action)
- # # --> DELETE /posts/1 (fails)
- # # --> POST /posts/1/comments (maps to the CommentsController#create action)
- # # --> PUT /posts/1/comments/1 (fails)
- #
- # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied.
- #
- # Examples:
- #
- # map.resources :messages, :path_prefix => "/thread/:thread_id"
- # # --> GET /thread/7/messages/1
- #
- # map.resources :messages, :collection => { :rss => :get }
- # # --> GET /messages/rss (maps to the #rss action)
- # # also adds a named route called "rss_messages"
- #
- # map.resources :messages, :member => { :mark => :post }
- # # --> POST /messages/1/mark (maps to the #mark action)
- # # also adds a named route called "mark_message"
- #
- # map.resources :messages, :new => { :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- #
- # map.resources :messages, :new => { :new => :any, :preview => :post }
- # # --> POST /messages/new/preview (maps to the #preview action)
- # # also adds a named route called "preview_new_message"
- # # --> /messages/new can be invoked via any request method
- #
- # map.resources :messages, :controller => "categories",
- # :path_prefix => "/category/:category_id",
- # :name_prefix => "category_"
- # # --> GET /categories/7/messages/1
- # # has named route "category_message"
- #
- # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an
- # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in
- # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes.
def resources(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_resource(entity, options.dup, &block) }
end
- # Creates named routes for implementing verb-oriented controllers for a singleton \resource.
- # A singleton \resource is global to its current context. For unnested singleton \resources,
- # the \resource is global to the current user visiting the application, such as a user's
- # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent
- # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>.
- # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>:
- #
- # map.resources :projects do |project|
- # project.resource :project_manager
- # end
- #
- # See +resources+ for general conventions. These are the main differences:
- # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name.
- # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option.
- # * No default index route is created for the singleton \resource controller.
- # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1')
- #
- # For example:
- #
- # map.resource :account
- #
- # maps these actions in the Accounts controller:
- #
- # class AccountsController < ActionController::Base
- # # GET new_account_url
- # def new
- # # return an HTML form for describing the new account
- # end
- #
- # # POST account_url
- # def create
- # # create an account
- # end
- #
- # # GET account_url
- # def show
- # # find and return the account
- # end
- #
- # # GET edit_account_url
- # def edit
- # # return an HTML form for editing the account
- # end
- #
- # # PUT account_url
- # def update
- # # find and update the account
- # end
- #
- # # DELETE account_url
- # def destroy
- # # delete the account
- # end
- # end
- #
- # Along with the routes themselves, +resource+ generates named routes for
- # use in controllers and views. <tt>map.resource :account</tt> produces
- # these named routes and helpers:
- #
- # Named Route Helpers
- # ============ =============================================
- # account account_url, hash_for_account_url,
- # account_path, hash_for_account_path
- #
- # new_account new_account_url, hash_for_new_account_url,
- # new_account_path, hash_for_new_account_path
- #
- # edit_account edit_account_url, hash_for_edit_account_url,
- # edit_account_path, hash_for_edit_account_path
def resource(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 46304b0336..0b4ba8c9f5 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -33,7 +33,7 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow]
+ IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
def initialize(set, scope, args)
@set, @scope = set, scope
@@ -55,7 +55,7 @@ module ActionDispatch
path = args.first
end
- if @scope[:module] && options[:to]
+ if @scope[:module] && options[:to] && !options[:to].is_a?(Proc)
if options[:to].to_s.include?("#")
options[:to] = "#{@scope[:module]}/#{options[:to]}"
elsif @scope[:controller].nil?
@@ -102,7 +102,7 @@ module ActionDispatch
end
def requirements
- @requirements ||= (@options[:constraints] || {}).tap do |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) }
end
@@ -131,6 +131,7 @@ module ActionDispatch
end
defaults[:controller] ||= default_controller
+ defaults[:action] ||= default_action
defaults.delete(:controller) if defaults[:controller].blank?
defaults.delete(:action) if defaults[:action].blank?
@@ -187,6 +188,12 @@ module ActionDispatch
@scope[:controller].to_s
end
end
+
+ def default_action
+ if @options[:action]
+ @options[:action].to_s
+ end
+ end
end
# Invokes Rack::Mount::Utils.normalize path and ensure that
@@ -194,7 +201,7 @@ module ActionDispatch
# for root cases, where the latter is the correct one.
def self.normalize_path(path)
path = Rack::Mount::Utils.normalize_path(path)
- path.sub!(%r{/(\(+)/?:}, '\1/:') unless path =~ %r{^/\(+:.*\)$}
+ path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^/]+\)$}
path
end
@@ -299,6 +306,11 @@ module ActionDispatch
options = args.extract_options!
options = options.dup
+ if name_prefix = options.delete(:name_prefix)
+ options[:as] ||= name_prefix
+ ActiveSupport::Deprecation.warn ":name_prefix was deprecated in the new router syntax. Use :as instead.", caller
+ end
+
case args.first
when String
options[:path] = args.first
@@ -341,9 +353,11 @@ module ActionDispatch
scope(controller.to_sym) { yield }
end
- def namespace(path)
+ def namespace(path, options = {})
path = path.to_s
- scope(:path => path, :name_prefix => path, :module => path) { yield }
+ options = { :path => path, :as => path, :module => path,
+ :shallow_path => path, :shallow_prefix => path }.merge!(options)
+ scope(options) { yield }
end
def constraints(constraints = {})
@@ -359,10 +373,10 @@ module ActionDispatch
options = (@scope[:options] || {}).merge(options)
- if @scope[:name_prefix] && !options[:as].blank?
- options[:as] = "#{@scope[:name_prefix]}_#{options[:as]}"
- elsif @scope[:name_prefix] && options[:as] == ""
- options[:as] = @scope[:name_prefix].to_s
+ if @scope[:as] && !options[:as].blank?
+ options[:as] = "#{@scope[:as]}_#{options[:as]}"
+ elsif @scope[:as] && options[:as] == ""
+ options[:as] = @scope[:as].to_s
end
args.push(options)
@@ -378,7 +392,15 @@ module ActionDispatch
Mapper.normalize_path("#{parent}/#{child}")
end
- def merge_name_prefix_scope(parent, child)
+ def merge_shallow_path_scope(parent, child)
+ Mapper.normalize_path("#{parent}/#{child}")
+ end
+
+ def merge_as_scope(parent, child)
+ parent ? "#{parent}_#{child}" : child
+ end
+
+ def merge_shallow_prefix_scope(parent, child)
parent ? "#{parent}_#{child}" : child
end
@@ -403,7 +425,9 @@ module ActionDispatch
end
def merge_blocks_scope(parent, child)
- (parent || []) + [child]
+ merged = parent ? parent.dup : []
+ merged << child if child
+ merged
end
def merge_options_scope(parent, child)
@@ -416,22 +440,27 @@ module ActionDispatch
end
module Resources
+ # CANONICAL_ACTIONS holds all actions that does not need a prefix or
+ # a path appended since they fit properly in their scope level.
+ VALID_ON_OPTIONS = [:new, :collection, :member]
+ CANONICAL_ACTIONS = [:index, :create, :new, :show, :update, :destroy]
+ MERGE_FROM_SCOPE_OPTIONS = [:shallow, :constraints]
+
class Resource #:nodoc:
- def self.default_actions
- [:index, :create, :new, :show, :update, :destroy, :edit]
- end
+ DEFAULT_ACTIONS = [:index, :create, :new, :show, :update, :destroy, :edit]
attr_reader :controller, :path, :options
def initialize(entities, options = {})
@name = entities.to_s
@path = options.delete(:path) || @name
- @controller = options.delete(:controller) || @name.to_s.pluralize
+ @controller = (options.delete(:controller) || @name).to_s
+ @as = options.delete(:as)
@options = options
end
def default_actions
- self.class.default_actions
+ self.class::DEFAULT_ACTIONS
end
def actions
@@ -445,7 +474,7 @@ module ActionDispatch
end
def name
- options[:as] || @name
+ @as || @name
end
def plural
@@ -497,7 +526,7 @@ module ActionDispatch
def nested_options
{}.tap do |opts|
- opts[:name_prefix] = member_name
+ opts[:as] = member_name
opts["#{singular}_id".to_sym] = id_constraint if id_constraint?
opts[:options] = { :shallow => shallow? } unless options[:shallow].nil?
end
@@ -515,8 +544,8 @@ module ActionDispatch
["#{path}/:id", options]
end
- def new_scope
- [path]
+ def new_scope(new_path)
+ ["#{path}/#{new_path}"]
end
def nested_scope
@@ -525,17 +554,20 @@ module ActionDispatch
end
class SingletonResource < Resource #:nodoc:
- def self.default_actions
- [:show, :create, :update, :destroy, :new, :edit]
- end
+ DEFAULT_ACTIONS = [:show, :create, :update, :destroy, :new, :edit]
- def initialize(entity, options = {})
- super
+ def initialize(entities, options)
+ @name = entities.to_s
+ @path = options.delete(:path) || @name
+ @controller = (options.delete(:controller) || @name.to_s.pluralize).to_s
+ @as = options.delete(:as)
+ @options = options
end
def member_name
name
end
+ alias :collection_name :member_name
def nested_path
path
@@ -543,7 +575,7 @@ module ActionDispatch
def nested_options
{}.tap do |opts|
- opts[:name_prefix] = member_name
+ opts[:as] = member_name
opts[:options] = { :shallow => shallow? } unless @options[:shallow].nil?
end
end
@@ -562,10 +594,12 @@ module ActionDispatch
@scope[:path_names] = @set.resources_path_names
end
+ def resources_path_names(options)
+ @scope[:path_names].merge!(options)
+ end
+
def resource(*resources, &block)
options = resources.extract_options!
- options = (@scope[:options] || {}).merge(options)
- options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resource, resources, options, &block)
return self
@@ -575,9 +609,12 @@ module ActionDispatch
yield if block_given?
collection_scope do
- post :create if parent_resource.actions.include?(:create)
- get :new if parent_resource.actions.include?(:new)
- end
+ post :create
+ end if parent_resource.actions.include?(:create)
+
+ new_scope do
+ get :new
+ end if parent_resource.actions.include?(:new)
member_scope do
get :show if parent_resource.actions.include?(:show)
@@ -592,8 +629,6 @@ module ActionDispatch
def resources(*resources, &block)
options = resources.extract_options!
- options = (@scope[:options] || {}).merge(options)
- options[:shallow] = true if @scope[:shallow] && !options.has_key?(:shallow)
if apply_common_behavior_for(:resources, resources, options, &block)
return self
@@ -605,9 +640,12 @@ module ActionDispatch
collection_scope do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
- get :new if parent_resource.actions.include?(:new)
end
+ new_scope do
+ get :new
+ end if parent_resource.actions.include?(:new)
+
member_scope do
get :show if parent_resource.actions.include?(:show)
put :update if parent_resource.actions.include?(:update)
@@ -644,10 +682,8 @@ module ActionDispatch
raise ArgumentError, "can't use new outside resource(s) scope"
end
- with_scope_level(:new) do
- scope(*parent_resource.new_scope) do
- yield
- end
+ new_scope do
+ yield
end
end
@@ -659,10 +695,10 @@ module ActionDispatch
with_scope_level(:nested) do
if parent_resource.shallow?
with_exclusive_scope do
- if @scope[:module].blank?
+ if @scope[:shallow_path].blank?
scope(*parent_resource.nested_scope) { yield }
else
- scope(@scope[:module], :name_prefix => @scope[:module].tr('/', '_')) do
+ scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
scope(*parent_resource.nested_scope) { yield }
end
end
@@ -673,7 +709,7 @@ module ActionDispatch
end
end
- def namespace(path)
+ def namespace(path, options = {})
if resource_scope?
nested { super }
else
@@ -688,8 +724,7 @@ module ActionDispatch
end
def match(*args)
- options = args.extract_options!
-
+ options = args.extract_options!.dup
options[:anchor] = true unless options.key?(:anchor)
if args.length > 1
@@ -697,17 +732,12 @@ module ActionDispatch
return self
end
- if [:collection, :member, :new].include?(options[:on])
+ on = options.delete(:on)
+ if VALID_ON_OPTIONS.include?(on)
args.push(options)
-
- case options.delete(:on)
- when :collection
- return collection { match(*args) }
- when :member
- return member { match(*args) }
- when :new
- return new { match(*args) }
- end
+ return send(on){ match(*args) }
+ elsif on
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
end
if @scope[:scope_level] == :resource
@@ -715,14 +745,26 @@ module ActionDispatch
return member { match(*args) }
end
- path_names = options.delete(:path_names)
+ path = options.delete(:path)
+ action = args.first
- if args.first.is_a?(Symbol)
- path = path_for_action(args.first, path_names)
- options = options_for_action(args.first, options)
+ if action.is_a?(Symbol)
+ path = path_for_action(action, path)
+ options[:to] ||= action
+ options[:as] = name_for_action(action, options[:as])
+
+ with_exclusive_scope do
+ return super(path, options)
+ end
+ elsif resource_method_scope?
+ path = path_for_custom_action
+ options[:as] = name_for_action(options[:as]) if options[:as]
+ args.push(options)
with_exclusive_scope do
- return match(path, options)
+ scope(path) do
+ return super
+ end
end
end
@@ -736,8 +778,8 @@ module ActionDispatch
def root(options={})
if @scope[:scope_level] == :resources
- with_scope_level(:collection) do
- scope(parent_resource.path, :name_prefix => parent_resource.collection_name) do
+ with_scope_level(:nested) do
+ scope(parent_resource.path, :as => parent_resource.collection_name) do
super(options)
end
end
@@ -747,11 +789,11 @@ module ActionDispatch
end
protected
+
def parent_resource #:nodoc:
@scope[:scope_level_resource]
end
- private
def apply_common_behavior_for(method, resources, options, &block)
if resources.length > 1
resources.each { |r| send(method, r, options, &block) }
@@ -765,6 +807,10 @@ module ActionDispatch
return true
end
+ scope_options = @scope.slice(*MERGE_FROM_SCOPE_OPTIONS).delete_if{ |k,v| v.blank? }
+ options.reverse_merge!(scope_options) unless scope_options.empty?
+ options.reverse_merge!(@scope[:options]) unless @scope[:options].blank?
+
if resource_scope?
nested do
send(method, resources.pop, options, &block)
@@ -779,14 +825,20 @@ module ActionDispatch
[:resource, :resources].include?(@scope[:scope_level])
end
+ def resource_method_scope?
+ [:collection, :member, :new].include?(@scope[:scope_level])
+ end
+
def with_exclusive_scope
begin
- old_name_prefix, old_path = @scope[:name_prefix], @scope[:path]
- @scope[:name_prefix], @scope[:path] = nil, nil
+ old_name_prefix, old_path = @scope[:as], @scope[:path]
+ @scope[:as], @scope[:path] = nil, nil
- yield
+ with_scope_level(:exclusive) do
+ yield
+ end
ensure
- @scope[:name_prefix], @scope[:path] = old_name_prefix, old_path
+ @scope[:as], @scope[:path] = old_name_prefix, old_path
end
end
@@ -807,6 +859,14 @@ module ActionDispatch
end
end
+ def new_scope
+ with_scope_level(:new) do
+ scope(*parent_resource.new_scope(action_path(:new))) do
+ yield
+ end
+ end
+ end
+
def collection_scope
with_scope_level(:collection) do
scope(*parent_resource.collection_scope) do
@@ -823,91 +883,71 @@ module ActionDispatch
end
end
- def path_for_action(action, path_names)
- case action
- when :index, :create
- "#{@scope[:path]}(.:format)"
- when :show, :update, :destroy
- if parent_resource.shallow?
- "#{@scope[:module]}/#{parent_resource.path}/:id(.:format)"
- else
- "#{@scope[:path]}(.:format)"
- end
- when :new
- "#{@scope[:path]}/#{action_path(:new)}(.:format)"
- when :edit
- if parent_resource.shallow?
- "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(:edit)}(.:format)"
- else
- "#{@scope[:path]}/#{action_path(:edit)}(.:format)"
- end
+ def canonical_action?(action, flag)
+ flag && CANONICAL_ACTIONS.include?(action)
+ end
+
+ def shallow_scoping?
+ parent_resource && parent_resource.shallow? && @scope[:scope_level] == :member
+ end
+
+ def path_for_action(action, path)
+ prefix = shallow_scoping? ?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
+
+ if canonical_action?(action, path.blank?)
+ "#{prefix}(.:format)"
else
- case @scope[:scope_level]
- when :collection
- "#{@scope[:path]}/#{action_path(action)}(.:format)"
- when :new
- "#{@scope[:path]}/#{action_path(:new)}/#{action_path(action)}(.:format)"
- else
- if parent_resource.shallow?
- "#{@scope[:module]}/#{parent_resource.path}/:id/#{action_path(action)}(.:format)"
- else
- "#{@scope[:path]}/#{action_path(action)}(.:format)"
- end
- end
+ "#{prefix}/#{action_path(action, path)}(.:format)"
end
end
- def action_path(name, path_names = nil)
- path_names ||= @scope[:path_names]
- path_names[name.to_sym] || name.to_s
+ def path_for_custom_action
+ if shallow_scoping?
+ "#{@scope[:shallow_path]}/#{parent_resource.path}/:id"
+ else
+ @scope[:path]
+ end
end
- def options_for_action(action, options)
- options.reverse_merge(
- :to => action,
- :as => name_for_action(action)
- )
+ def action_path(name, path = nil)
+ path || @scope[:path_names][name.to_sym] || name.to_s
+ end
+
+ def prefix_name_for_action(action, as)
+ if as.present?
+ "#{as}_"
+ elsif as
+ ""
+ elsif !canonical_action?(action, @scope[:scope_level])
+ "#{action}_"
+ end
end
- def name_for_action(action)
- name_prefix = @scope[:name_prefix].blank? ? "" : "#{@scope[:name_prefix]}_"
- shallow_prefix = @scope[:module].blank? ? "" : "#{@scope[:module].tr('/', '_')}_"
+ def name_for_action(action, as=nil)
+ prefix = prefix_name_for_action(action, as)
+ name_prefix = @scope[:as]
- case action
- when :index
- "#{name_prefix}#{parent_resource.collection_name}"
- when :show
- if parent_resource.shallow?
- "#{shallow_prefix}#{parent_resource.member_name}"
- else
- "#{name_prefix}#{parent_resource.member_name}"
- end
- when :edit
- if parent_resource.shallow?
- "edit_#{shallow_prefix}#{parent_resource.member_name}"
- else
- "edit_#{name_prefix}#{parent_resource.member_name}"
- end
+ if parent_resource
+ collection_name = parent_resource.collection_name
+ member_name = parent_resource.member_name
+ name_prefix = "#{name_prefix}_" if name_prefix.present?
+ end
+
+ case @scope[:scope_level]
+ when :collection
+ "#{prefix}#{name_prefix}#{collection_name}"
when :new
- "new_#{name_prefix}#{parent_resource.member_name}"
- when :update, :create, :destroy
- nil
+ "#{prefix}new_#{name_prefix}#{member_name}"
else
- case @scope[:scope_level]
- when :collection
- "#{action}_#{name_prefix}#{parent_resource.collection_name}"
- when :new
- "#{action}_new_#{name_prefix}#{parent_resource.member_name}"
+ if shallow_scoping?
+ shallow_prefix = "#{@scope[:shallow_prefix]}_" if @scope[:shallow_prefix].present?
+ "#{prefix}#{shallow_prefix}#{member_name}"
else
- if parent_resource.shallow?
- "#{action}_#{shallow_prefix}#{parent_resource.member_name}"
- else
- "#{action}_#{name_prefix}#{parent_resource.member_name}"
- end
+ "#{prefix}#{name_prefix}#{member_name}"
end
end
end
-
end
include Base
diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb
index 6f37eadd6b..aefebf8f80 100644
--- a/actionpack/lib/action_dispatch/routing/route.rb
+++ b/actionpack/lib/action_dispatch/routing/route.rb
@@ -2,9 +2,10 @@ module ActionDispatch
module Routing
class Route #:nodoc:
attr_reader :app, :conditions, :defaults, :name
- attr_reader :path, :requirements
+ attr_reader :path, :requirements, :set
- def initialize(app, conditions, requirements, defaults, name, anchor)
+ def initialize(set, app, conditions, requirements, defaults, name, anchor)
+ @set = set
@app = app
@defaults = defaults
@name = name
@@ -24,6 +25,9 @@ module ActionDispatch
h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
h
}
+
+ @conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
+ @requirements.delete_if{ |k,v| !valid_condition?(k) }
end
def verb
@@ -50,6 +54,11 @@ module ActionDispatch
"%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect]
end
end
+
+ private
+ def valid_condition?(method)
+ segment_keys.include?(method) || set.valid_conditions.include?(method)
+ end
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 57a73dde75..5ecad6bc04 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,8 +1,10 @@
-require 'rack/mount'
require 'forwardable'
require 'active_support/core_ext/object/to_query'
require 'action_dispatch/routing/deprecated_mapper'
+$: << File.expand_path('../../vendor/rack-mount-0.6.6.pre', __FILE__)
+require 'rack/mount'
+
module ActionDispatch
module Routing
class RouteSet #:nodoc:
@@ -185,9 +187,9 @@ module ActionDispatch
end
end
- attr_accessor :routes, :named_routes
+ attr_accessor :set, :routes, :named_routes
attr_accessor :disable_clear_and_finalize, :resources_path_names
- attr_accessor :default_url_options, :request_class
+ attr_accessor :default_url_options, :request_class, :valid_conditions
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
@@ -199,7 +201,11 @@ module ActionDispatch
self.resources_path_names = self.class.default_resources_path_names.dup
self.controller_namespaces = Set.new
self.default_url_options = {}
+
self.request_class = request_class
+ self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym }
+ self.valid_conditions.delete(:id)
+ self.valid_conditions.push(:controller, :action)
@disable_clear_and_finalize = false
clear!
@@ -277,7 +283,7 @@ module ActionDispatch
end
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
- route = Route.new(app, conditions, requirements, defaults, name, anchor)
+ route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
@set.add_route(*route)
named_routes[name] = route if name
routes << route
@@ -296,6 +302,7 @@ module ActionDispatch
@extras = extras
normalize_options!
+ normalize_recall!
normalize_controller_action_id!
use_relative_controller!
controller.sub!(%r{^/}, '') if controller
@@ -336,6 +343,15 @@ module ActionDispatch
end
end
+ def normalize_recall!
+ # If the target route is not a standard route then remove controller and action
+ # from the options otherwise they will appear in the url parameters
+ if block_or_proc_route_target?
+ recall.delete(:controller) unless segment_keys.include?(:controller)
+ recall.delete(:action) unless segment_keys.include?(:action)
+ end
+ end
+
# This pulls :controller, :action, and :id out of the recall.
# The recall key is only used if there is no key in the options
# or if the key in the options is identical. If any of
@@ -371,7 +387,7 @@ module ActionDispatch
def generate
error = ActionController::RoutingError.new("No route matches #{options.inspect}")
- path, params = @set.generate(:path_info, named_route, options, recall, opts)
+ path, params = @set.set.generate(:path_info, named_route, options, recall, opts)
raise error unless path
@@ -402,6 +418,19 @@ module ActionDispatch
return false unless current_controller
controller.to_param != current_controller.to_param
end
+
+ private
+ def named_route_exists?
+ named_route && set.named_routes[named_route]
+ end
+
+ def block_or_proc_route_target?
+ named_route_exists? && !set.named_routes[named_route].app.is_a?(Dispatcher)
+ end
+
+ def segment_keys
+ named_route_exists? ? set.named_routes[named_route].segment_keys : []
+ end
end
# Generate the path indicated by the arguments, and return an array of
@@ -415,7 +444,7 @@ module ActionDispatch
end
def generate(options, recall = {}, extras = false)
- Generator.new(options, recall, @set, extras).generate
+ Generator.new(options, recall, self, extras).generate
end
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
@@ -447,7 +476,7 @@ module ActionDispatch
# ROUTES TODO: This can be called directly, so script_name should probably be set in the router
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "##{Rack::Utils.escape(options[:anchor].to_param.to_s)}" if options[:anchor]
+ rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 1499c03bdf..9338fa9e70 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -53,7 +53,6 @@ module ActionDispatch
extras.each_key { |key| expected_options.delete key } unless extras.nil?
expected_options.stringify_keys!
- routing_diff = expected_options.diff(request.path_parameters)
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
assert_block(msg) { request.path_parameters == expected_options }
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index 0e82b41590..2fc9e2b7d6 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -7,9 +7,7 @@ require 'action_controller/vendor/html-scanner'
module ActionDispatch
module Assertions
- unless const_defined?(:NO_STRIP)
- NO_STRIP = %w{pre script style textarea}
- end
+ NO_STRIP = %w{pre script style textarea}
# Adds the +assert_select+ method for use in Rails functional
# test cases, which can be used to make assertions on the response HTML of a controller
@@ -359,7 +357,7 @@ module ActionDispatch
# position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt>
# and <tt>:after</tt>.
#
- # Use the argument <tt>:redirect</tt> follwed by a path to check that an statement
+ # Use the argument <tt>:redirect</tt> followed by a path to check that an statement
# which redirects to the specified path is generated.
#
# Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will
@@ -581,27 +579,25 @@ module ActionDispatch
end
protected
- unless const_defined?(:RJS_STATEMENTS)
- RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
- RJS_ANY_ID = "\"([^\"])*\""
- RJS_STATEMENTS = {
- :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
- :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
- :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
- :redirect => "window.location.href = #{RJS_ANY_ID}"
- }
- [:remove, :show, :hide, :toggle].each do |action|
- RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
- end
- RJS_INSERTIONS = ["top", "bottom", "before", "after"]
- RJS_INSERTIONS.each do |insertion|
- RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
- end
- RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
- RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
- RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
+ RJS_PATTERN_HTML = "\"((\\\\\"|[^\"])*)\""
+ RJS_ANY_ID = "\"([^\"])*\""
+ RJS_STATEMENTS = {
+ :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)",
+ :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)",
+ :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
+ :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)",
+ :redirect => "window.location.href = #{RJS_ANY_ID}"
+ }
+ [:remove, :show, :hide, :toggle].each do |action|
+ RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)"
+ end
+ RJS_INSERTIONS = ["top", "bottom", "before", "after"]
+ RJS_INSERTIONS.each do |insertion|
+ RJS_STATEMENTS["insert_#{insertion}".to_sym] = "Element.insert\\(#{RJS_ANY_ID}, \\{ #{insertion}: #{RJS_PATTERN_HTML} \\}\\)"
end
+ RJS_STATEMENTS[:insert_html] = "Element.insert\\(#{RJS_ANY_ID}, \\{ (#{RJS_INSERTIONS.join('|')}): #{RJS_PATTERN_HTML} \\}\\)"
+ RJS_STATEMENTS[:any] = Regexp.new("(#{RJS_STATEMENTS.values.join('|')})")
+ RJS_PATTERN_UNICODE_ESCAPED_CHAR = /\\u([0-9a-zA-Z]{4})/
# +assert_select+ and +css_select+ call this to obtain the content in the HTML
# page, or from all the RJS statements, depending on the type of response.
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 9a51a32899..44fb1bde99 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -53,7 +53,7 @@ module ActionDispatch
# Returns the template of the file which was used to
# render this response (or nil)
def rendered
- ActiveSupport::Deprecation.warn("response.rendered has been deprecated. Use tempate.rendered instead", caller)
+ ActiveSupport::Deprecation.warn("response.rendered has been deprecated. Use template.rendered instead", caller)
@template.instance_variable_get(:@_rendered)
end
@@ -89,7 +89,7 @@ module ActionDispatch
# A shortcut to the template.assigns
def template_objects
- ActiveSupport::Deprecation.warn("response.template_objects has been deprecated. Use tempate.assigns instead", caller)
+ ActiveSupport::Deprecation.warn("response.template_objects has been deprecated. Use template.assigns instead", caller)
@template.assigns || {}
end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb
new file mode 100644
index 0000000000..9fbf707724
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb
@@ -0,0 +1,32 @@
+require 'rack'
+
+module Rack #:nodoc:
+ # A stackable dynamic tree based Rack router.
+ #
+ # Rack::Mount supports Rack's Cascade style of trying several routes until
+ # it finds one that is not a 404. This allows multiple routes to be nested
+ # or stacked on top of each other. Since the application endpoint can
+ # trigger the router to continue matching, middleware can be used to add
+ # arbitrary conditions to any route. This allows you to route based on
+ # other request attributes, session information, or even data dynamically
+ # pulled from a database.
+ module Mount
+ autoload :CodeGeneration, 'rack/mount/code_generation'
+ autoload :GeneratableRegexp, 'rack/mount/generatable_regexp'
+ autoload :Multimap, 'rack/mount/multimap'
+ autoload :Prefix, 'rack/mount/prefix'
+ autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups'
+ autoload :Route, 'rack/mount/route'
+ autoload :RouteSet, 'rack/mount/route_set'
+ autoload :RoutingError, 'rack/mount/route_set'
+ autoload :Strexp, 'rack/mount/strexp'
+ autoload :Utils, 'rack/mount/utils'
+ autoload :Version, 'rack/mount/version'
+
+ module Analysis #:nodoc:
+ autoload :Frequency, 'rack/mount/analysis/frequency'
+ autoload :Histogram, 'rack/mount/analysis/histogram'
+ autoload :Splitting, 'rack/mount/analysis/splitting'
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb
new file mode 100644
index 0000000000..671258f807
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb
@@ -0,0 +1,60 @@
+require 'rack/mount/utils'
+
+module Rack::Mount
+ module Analysis
+ class Frequency #:nodoc:
+ def initialize(*keys)
+ clear
+ keys.each { |key| self << key }
+ end
+
+ def clear
+ @raw_keys = []
+ @key_frequency = Analysis::Histogram.new
+ self
+ end
+
+ def <<(key)
+ raise ArgumentError unless key.is_a?(Hash)
+ @raw_keys << key
+ nil
+ end
+
+ def possible_keys
+ @possible_keys ||= begin
+ @raw_keys.map do |key|
+ key.inject({}) { |requirements, (method, requirement)|
+ process_key(requirements, method, requirement)
+ requirements
+ }
+ end
+ end
+ end
+
+ def process_key(requirements, method, requirement)
+ if requirement.is_a?(Regexp)
+ expression = Utils.parse_regexp(requirement)
+
+ if expression.is_a?(Regin::Expression) && expression.anchored_to_line?
+ expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) })
+ return requirements[method] = expression.to_s if expression.literal?
+ end
+ end
+
+ requirements[method] = requirement
+ end
+
+ def report
+ @report ||= begin
+ possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
+ return [] if @key_frequency.count <= 1
+ @key_frequency.keys_in_upper_quartile
+ end
+ end
+
+ def expire!
+ @possible_keys = @report = nil
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb
new file mode 100644
index 0000000000..20aaa132f9
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb
@@ -0,0 +1,74 @@
+module Rack::Mount
+ module Analysis
+ class Histogram < Hash #:nodoc:
+ attr_reader :count
+
+ def initialize
+ @count = 0
+ super(0)
+ expire_caches!
+ end
+
+ def <<(value)
+ @count += 1
+ self[value] += 1 if value
+ expire_caches!
+ self
+ end
+
+ def sorted_by_frequency
+ sort_by { |_, value| value }.reverse!
+ end
+
+ def max
+ @max ||= values.max || 0
+ end
+
+ def min
+ @min ||= values.min || 0
+ end
+
+ def mean
+ @mean ||= calculate_mean
+ end
+
+ def standard_deviation
+ @standard_deviation ||= calculate_standard_deviation
+ end
+
+ def upper_quartile_limit
+ @upper_quartile_limit ||= calculate_upper_quartile_limit
+ end
+
+ def keys_in_upper_quartile
+ @keys_in_upper_quartile ||= compute_keys_in_upper_quartile
+ end
+
+ private
+ def calculate_mean
+ count / size
+ end
+
+ def calculate_variance
+ values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / count.to_f
+ end
+
+ def calculate_standard_deviation
+ Math.sqrt(calculate_variance)
+ end
+
+ def calculate_upper_quartile_limit
+ mean + standard_deviation
+ end
+
+ def compute_keys_in_upper_quartile
+ sorted_by_frequency.select { |_, value| value >= upper_quartile_limit }.map! { |key, _| key }
+ end
+
+ def expire_caches!
+ @max = @min = @mean = @standard_deviation = nil
+ @keys_in_upper_quartile = nil
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb
new file mode 100644
index 0000000000..8a8c551302
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb
@@ -0,0 +1,159 @@
+require 'rack/mount/utils'
+
+module Rack::Mount
+ module Analysis
+ class Splitting < Frequency
+ NULL = "\0".freeze
+
+ class Key < Struct.new(:method, :index, :separators)
+ def self.split(value, separator_pattern)
+ keys = value.split(separator_pattern)
+ keys.shift if keys[0] == ''
+ keys << NULL
+ keys
+ end
+
+ def call(cache, obj)
+ (cache[method] ||= self.class.split(obj.send(method), separators))[index]
+ end
+
+ def call_source(cache, obj)
+ "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]"
+ end
+
+ def inspect
+ "#{method}[#{index}]"
+ end
+ end
+
+ def clear
+ @boundaries = {}
+ super
+ end
+
+ def <<(key)
+ super
+ key.each_pair do |k, v|
+ analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new)
+ end
+ end
+
+ def separators(key)
+ (@boundaries[key].keys_in_upper_quartile + ['/']).uniq
+ end
+
+ def process_key(requirements, method, requirement)
+ separators = separators(method)
+ if requirement.is_a?(Regexp) && separators.any?
+ generate_split_keys(requirement, separators).each_with_index do |value, index|
+ requirements[Key.new(method, index, Regexp.union(*separators))] = value
+ end
+ else
+ super
+ end
+ end
+
+ private
+ def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
+ return boundaries unless regexp.is_a?(Regexp)
+
+ parts = Utils.parse_regexp(regexp)
+ parts.each_with_index do |part, index|
+ if part.is_a?(Regin::Group)
+ if index > 0
+ previous = parts[index-1]
+ if previous.is_a?(Regin::Character) && previous.literal?
+ boundaries << previous.to_s
+ end
+ end
+
+ if inside = part.expression[0]
+ if inside.is_a?(Regin::Character) && inside.literal?
+ boundaries << inside.to_s
+ end
+ end
+
+ if index < parts.length
+ following = parts[index+1]
+ if following.is_a?(Regin::Character) && following.literal?
+ boundaries << following.to_s
+ end
+ end
+ end
+ end
+
+ boundaries
+ end
+
+ def generate_split_keys(regexp, separators) #:nodoc:
+ segments = []
+ buf = nil
+ parts = Utils.parse_regexp(regexp)
+ parts.each_with_index do |part, index|
+ case part
+ when Regin::Anchor
+ if part.value == '$' || part.value == '\Z'
+ segments << join_buffer(buf, regexp) if buf
+ segments << NULL
+ buf = nil
+ break
+ end
+ when Regin::CharacterClass
+ break if separators.any? { |s| part.include?(s) }
+ buf = nil
+ segments << part.to_regexp(true)
+ when Regin::Character
+ if separators.any? { |s| part.include?(s) }
+ segments << join_buffer(buf, regexp) if buf
+ peek = parts[index+1]
+ if peek.is_a?(Regin::Character) && separators.include?(peek.value)
+ segments << ''
+ end
+ buf = nil
+ else
+ buf ||= Regin::Expression.new([])
+ buf += [part]
+ end
+ when Regin::Group
+ if part.quantifier == '?'
+ value = part.expression.first
+ if separators.any? { |s| value.include?(s) }
+ segments << join_buffer(buf, regexp) if buf
+ buf = nil
+ end
+ break
+ elsif part.quantifier == nil
+ break if separators.any? { |s| part.include?(s) }
+ buf = nil
+ segments << part.to_regexp(true)
+ else
+ break
+ end
+ else
+ break
+ end
+
+ if index + 1 == parts.size
+ segments << join_buffer(buf, regexp) if buf
+ buf = nil
+ break
+ end
+ end
+
+ while segments.length > 0 && (segments.last.nil? || segments.last == '')
+ segments.pop
+ end
+
+ segments
+ end
+
+ def join_buffer(parts, regexp)
+ if parts.literal?
+ parts.to_s
+ else
+ parts.to_regexp(true)
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb
new file mode 100644
index 0000000000..903c79fdc6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb
@@ -0,0 +1,113 @@
+module Rack::Mount
+ module CodeGeneration #:nodoc:
+ def _expired_recognize(env) #:nodoc:
+ raise 'route set not finalized'
+ end
+
+ def rehash
+ super
+ optimize_recognize!
+ end
+
+ private
+ def expire!
+ if @optimized_recognize_defined
+ remove_metaclass_method :recognize
+
+ class << self
+ alias_method :recognize, :_expired_recognize
+ end
+
+ @optimized_recognize_defined = false
+ end
+
+ super
+ end
+
+ def optimize_container_iterator(container)
+ body = []
+
+ container.each_with_index { |route, i|
+ body << "route = self[#{i}]"
+ body << 'matches = {}'
+ body << 'params = route.defaults.dup'
+
+ conditions = []
+ route.conditions.each do |method, condition|
+ b = []
+ if condition.is_a?(Regexp)
+ b << "if m = obj.#{method}.match(#{condition.inspect})"
+ b << "matches[:#{method}] = m"
+ if (named_captures = route.named_captures[method]) && named_captures.any?
+ b << 'captures = m.captures'
+ b << 'p = nil'
+ b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
+ end
+ else
+ b << "if m = obj.#{method} == route.conditions[:#{method}]"
+ end
+ b << 'true'
+ b << 'end'
+ conditions << "(#{b.join('; ')})"
+ end
+
+ body << <<-RUBY
+ if #{conditions.join(' && ')}
+ yield route, matches, params
+ end
+ RUBY
+ }
+
+ container.instance_eval(<<-RUBY, __FILE__, __LINE__)
+ def optimized_each(obj)
+ #{body.join("\n")}
+ nil
+ end
+ RUBY
+ end
+
+ def optimize_recognize!
+ keys = @recognition_keys.map { |key|
+ if key.respond_to?(:call_source)
+ key.call_source(:cache, :obj)
+ else
+ "obj.#{key}"
+ end
+ }.join(', ')
+
+ @optimized_recognize_defined = true
+
+ remove_metaclass_method :recognize
+
+ instance_eval(<<-RUBY, __FILE__, __LINE__)
+ def recognize(obj)
+ cache = {}
+ container = @recognition_graph[#{keys}]
+ optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
+
+ if block_given?
+ container.optimized_each(obj) do |route, matches, params|
+ yield route, matches, params
+ end
+ else
+ container.optimized_each(obj) do |route, matches, params|
+ return route, matches, params
+ end
+ end
+
+ nil
+ end
+ RUBY
+ end
+
+ # method_defined? can't distinguish between instance
+ # and meta methods. So we have to rescue if the method
+ # has not been defined in the metaclass yet.
+ def remove_metaclass_method(symbol)
+ metaclass = class << self; self; end
+ metaclass.send(:remove_method, symbol)
+ rescue NameError => e
+ nil
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb
new file mode 100644
index 0000000000..47bbab3784
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb
@@ -0,0 +1,210 @@
+require 'rack/mount/utils'
+
+module Rack::Mount
+ class GeneratableRegexp < Regexp #:nodoc:
+ class DynamicSegment #:nodoc:
+ attr_reader :name, :requirement
+
+ def initialize(name, requirement)
+ @name, @requirement = name.to_sym, requirement
+ freeze
+ end
+
+ def ==(obj)
+ @name == obj.name && @requirement == obj.requirement
+ end
+
+ def =~(str)
+ @requirement =~ str
+ end
+
+ def to_hash
+ { @name => @requirement }
+ end
+
+ def inspect
+ "/(?<#{@name}>#{@requirement.source})/"
+ end
+ end
+
+ module InstanceMethods
+ def self.extended(obj)
+ obj.segments
+ end
+
+ def defaults=(defaults)
+ @required_captures = nil
+ @required_params = nil
+ @required_defaults = nil
+ @defaults = defaults
+ end
+
+ def defaults
+ @defaults ||= {}
+ end
+
+ def generatable?
+ segments.any?
+ end
+
+ def generate(params = {}, recall = {}, options = {})
+ return nil unless generatable?
+
+ merged = recall.merge(params)
+ return nil unless required_params.all? { |p| merged.include?(p) }
+ return nil unless required_defaults.all? { |k, v| merged[k] == v }
+
+ generate_from_segments(segments, params, merged, options)
+ end
+
+ def segments
+ @segments ||= begin
+ defaults
+ segments = []
+ catch(:halt) do
+ expression = Utils.parse_regexp(self)
+ segments = parse_segments(expression)
+ end
+ segments
+ end
+ end
+
+ def captures
+ segments.flatten.find_all { |s| s.is_a?(DynamicSegment) }
+ end
+
+ def required_captures
+ @required_captures ||= segments.find_all { |s|
+ s.is_a?(DynamicSegment) && !@defaults.include?(s.name)
+ }.freeze
+ end
+
+ def required_params
+ @required_params ||= required_captures.map { |s| s.name }.freeze
+ end
+
+ def required_defaults
+ @required_defaults ||= begin
+ required_defaults = @defaults.dup
+ captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
+ required_defaults.delete(name)
+ }
+ required_defaults
+ end
+ end
+
+ def freeze
+ segments
+ captures
+ required_captures
+ required_params
+ required_defaults
+ super
+ end
+
+ private
+ def parse_segments(segments)
+ s = []
+ segments.each_with_index do |part, index|
+ case part
+ when Regin::Anchor
+ # ignore
+ when Regin::Character
+ throw :halt unless part.literal?
+
+ if s.last.is_a?(String)
+ s.last << part.value.dup
+ else
+ s << part.value.dup
+ end
+ when Regin::Group
+ if part.name
+ s << DynamicSegment.new(part.name, part.expression.to_regexp(true))
+ else
+ s << parse_segments(part.expression)
+ end
+ when Regin::Expression
+ return parse_segments(part)
+ else
+ throw :halt
+ end
+ end
+
+ s
+ end
+
+ EMPTY_STRING = ''.freeze
+
+ def generate_from_segments(segments, params, merged, options, optional = false)
+ if optional
+ return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
+ return EMPTY_STRING unless segments.flatten.any? { |s|
+ params.has_key?(s.name) if s.is_a?(DynamicSegment)
+ }
+ return EMPTY_STRING if segments.any? { |segment|
+ if segment.is_a?(DynamicSegment)
+ value = merged[segment.name] || @defaults[segment.name]
+ value = parameterize(segment.name, value, options)
+
+ merged_value = parameterize(segment.name, merged[segment.name], options)
+ default_value = parameterize(segment.name, @defaults[segment.name], options)
+
+ if value.nil? || segment !~ value
+ true
+ elsif merged_value == default_value
+ # Nasty control flow
+ return :clear_remaining_segments
+ else
+ false
+ end
+ end
+ }
+ end
+
+ generated = segments.map do |segment|
+ case segment
+ when String
+ segment
+ when DynamicSegment
+ value = params[segment.name] || merged[segment.name] || @defaults[segment.name]
+ value = parameterize(segment.name, value, options)
+ if value && segment =~ value.to_s
+ value
+ else
+ return
+ end
+ when Array
+ value = generate_from_segments(segment, params, merged, options, true)
+ if value == :clear_remaining_segments
+ segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
+ EMPTY_STRING
+ elsif value.nil?
+ EMPTY_STRING
+ else
+ value
+ end
+ end
+ end
+
+ # Delete any used items from the params
+ segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
+
+ generated.join
+ end
+
+ def parameterize(name, value, options)
+ if block = options[:parameterize]
+ block.call(name, value)
+ else
+ value
+ end
+ end
+ end
+ include InstanceMethods
+
+ def initialize(regexp)
+ super
+ segments
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb
new file mode 100644
index 0000000000..0f8eaaec67
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb
@@ -0,0 +1,53 @@
+begin
+ require 'nested_multimap'
+rescue LoadError
+ $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
+ require 'nested_multimap'
+end
+
+module Rack::Mount
+ class Multimap < NestedMultimap #:nodoc:
+ def store(*args)
+ keys = args.dup
+ value = keys.pop
+ key = keys.shift
+
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
+
+ unless key.respond_to?(:=~)
+ raise ArgumentError, "unsupported key: #{args.first.inspect}"
+ end
+
+ if key.is_a?(Regexp)
+ if keys.empty?
+ @hash.each_pair { |k, l| l << value if k =~ key }
+ self.default << value
+ else
+ @hash.each_pair { |k, _|
+ if k =~ key
+ args[0] = k
+ super(*args)
+ end
+ }
+
+ self.default = self.class.new(default) unless default.is_a?(self.class)
+ default[*keys.dup] = value
+ end
+ else
+ super(*args)
+ end
+ end
+ alias_method :[]=, :store
+
+ undef :index, :invert
+
+ def height
+ containers_with_default.max { |a, b| a.length <=> b.length }.length
+ end
+
+ def average_height
+ lengths = containers_with_default.map { |e| e.length }
+ lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb
new file mode 100644
index 0000000000..58892e4c4f
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb
@@ -0,0 +1,36 @@
+require 'rack/mount/utils'
+
+module Rack::Mount
+ class Prefix #:nodoc:
+ EMPTY_STRING = ''.freeze
+ PATH_INFO = 'PATH_INFO'.freeze
+ SCRIPT_NAME = 'SCRIPT_NAME'.freeze
+ SLASH = '/'.freeze
+
+ KEY = 'rack.mount.prefix'.freeze
+
+ def initialize(app, prefix = nil)
+ @app, @prefix = app, prefix.freeze
+ freeze
+ end
+
+ def call(env)
+ if prefix = env[KEY] || @prefix
+ old_path_info = env[PATH_INFO].dup
+ old_script_name = env[SCRIPT_NAME].dup
+
+ begin
+ env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING))
+ env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
+ env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
+ @app.call(env)
+ ensure
+ env[PATH_INFO] = old_path_info
+ env[SCRIPT_NAME] = old_script_name
+ end
+ else
+ @app.call(env)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb
new file mode 100644
index 0000000000..c11292b2a2
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb
@@ -0,0 +1,69 @@
+module Rack::Mount
+ if Regin.regexp_supports_named_captures?
+ RegexpWithNamedGroups = Regexp
+ else
+ require 'strscan'
+
+ # A wrapper that adds shim named capture support to older
+ # versions of Ruby.
+ #
+ # Because the named capture syntax causes a parse error, an
+ # alternate syntax is used to indicate named captures.
+ #
+ # Ruby 1.9+ named capture syntax:
+ #
+ # /(?<foo>[a-z]+)/
+ #
+ # Ruby 1.8 shim syntax:
+ #
+ # /(?:<foo>[a-z]+)/
+ class RegexpWithNamedGroups < Regexp
+ def self.new(regexp) #:nodoc:
+ if regexp.is_a?(RegexpWithNamedGroups)
+ regexp
+ else
+ super
+ end
+ end
+
+ # Wraps Regexp with named capture support.
+ def initialize(regexp)
+ regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp)
+ source, options = regexp.source, regexp.options
+ @names, scanner = [], StringScanner.new(source)
+
+ while scanner.skip_until(/\(/)
+ if scanner.scan(/\?:<([^>]+)>/)
+ @names << scanner[1]
+ elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/)
+ # ignore noncapture
+ else
+ @names << nil
+ end
+ end
+ source.gsub!(/\?:<([^>]+)>/, '')
+
+ @names = [] unless @names.any?
+ @names.freeze
+
+ super(source, options)
+ end
+
+ def names
+ @names.dup
+ end
+
+ def named_captures
+ named_captures = {}
+ names.each_with_index { |n, i|
+ named_captures[n] = [i+1] if n
+ }
+ named_captures
+ end
+
+ def eql?(other)
+ super && @names.eql?(other.names)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb
new file mode 100644
index 0000000000..680c40f147
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb
@@ -0,0 +1,130 @@
+require 'rack/mount/generatable_regexp'
+require 'rack/mount/regexp_with_named_groups'
+require 'rack/mount/utils'
+
+module Rack::Mount
+ # Route is an internal class used to wrap a single route attributes.
+ #
+ # Plugins should not depend on any method on this class or instantiate
+ # new Route objects. Instead use the factory method, RouteSet#add_route
+ # to create new routes and add them to the set.
+ class Route
+ # Valid rack application to call if conditions are met
+ attr_reader :app
+
+ # A hash of conditions to match against. Conditions may be expressed
+ # as strings or regexps to match against.
+ attr_reader :conditions
+
+ # A hash of values that always gets merged into the parameters hash
+ attr_reader :defaults
+
+ # Symbol identifier for the route used with named route generations
+ attr_reader :name
+
+ attr_reader :named_captures
+
+ def initialize(app, conditions, defaults, name)
+ unless app.respond_to?(:call)
+ raise ArgumentError, 'app must be a valid rack application' \
+ ' and respond to call'
+ end
+ @app = app
+
+ @name = name ? name.to_sym : nil
+ @defaults = (defaults || {}).freeze
+
+ @conditions = {}
+
+ conditions.each do |method, pattern|
+ next unless method && pattern
+
+ pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String)
+
+ if pattern.is_a?(Regexp)
+ pattern = Utils.normalize_extended_expression(pattern)
+ pattern = RegexpWithNamedGroups.new(pattern)
+ pattern.extend(GeneratableRegexp::InstanceMethods)
+ pattern.defaults = @defaults
+ end
+
+ @conditions[method] = pattern.freeze
+ end
+
+ @named_captures = {}
+ @conditions.map { |method, condition|
+ next unless condition.respond_to?(:named_captures)
+ @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
+ named_captures[k.to_sym] = v.last - 1
+ named_captures
+ }.freeze
+ }
+ @named_captures.freeze
+
+ @has_significant_params = @conditions.any? { |method, condition|
+ (condition.respond_to?(:required_params) && condition.required_params.any?) ||
+ (condition.respond_to?(:required_defaults) && condition.required_defaults.any?)
+ }
+
+ if @conditions.has_key?(:path_info) &&
+ !Utils.regexp_anchored?(@conditions[:path_info])
+ @prefix = true
+ @app = Prefix.new(@app)
+ else
+ @prefix = false
+ end
+
+ @conditions.freeze
+ end
+
+ def prefix?
+ @prefix
+ end
+
+
+ def generation_keys
+ @conditions.inject({}) { |keys, (method, condition)|
+ if condition.respond_to?(:required_defaults)
+ keys.merge!(condition.required_defaults)
+ else
+ keys
+ end
+ }
+ end
+
+ def significant_params?
+ @has_significant_params
+ end
+
+ def generate(method, params = {}, recall = {}, options = {})
+ if method.nil?
+ result = @conditions.inject({}) { |h, (m, condition)|
+ if condition.respond_to?(:generate)
+ h[m] = condition.generate(params, recall, options)
+ end
+ h
+ }
+ return nil if result.values.compact.empty?
+ else
+ if condition = @conditions[method]
+ if condition.respond_to?(:generate)
+ result = condition.generate(params, recall, options)
+ end
+ end
+ end
+
+ if result
+ @defaults.each do |key, value|
+ params.delete(key) if params[key] == value
+ end
+ end
+
+ result
+ end
+
+
+ def inspect #:nodoc:
+ "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>"
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
new file mode 100644
index 0000000000..0e5a65a640
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
@@ -0,0 +1,409 @@
+require 'rack/mount/multimap'
+require 'rack/mount/route'
+require 'rack/mount/utils'
+
+module Rack::Mount
+ class RoutingError < StandardError; end
+
+ class RouteSet
+ # Initialize a new RouteSet without optimizations
+ def self.new_without_optimizations(options = {}, &block)
+ new(options.merge(:_optimize => false), &block)
+ end
+
+ # Basic RouteSet initializer.
+ #
+ # If a block is given, the set is yielded and finalized.
+ #
+ # See other aspects for other valid options:
+ # - <tt>Generation::RouteSet.new</tt>
+ # - <tt>Recognition::RouteSet.new</tt>
+ def initialize(options = {}, &block)
+ @parameters_key = options.delete(:parameters_key) || 'rack.routing_args'
+ @parameters_key.freeze
+
+ @named_routes = {}
+
+ @recognition_key_analyzer = Analysis::Splitting.new
+ @generation_key_analyzer = Analysis::Frequency.new
+
+ @request_class = options.delete(:request_class) || Rack::Request
+ @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
+
+ extend CodeGeneration unless options[:_optimize] == false
+ @optimized_recognize_defined = false
+
+ @routes = []
+ expire!
+
+ if block_given?
+ yield self
+ rehash
+ end
+ end
+
+ # Builder method to add a route to the set
+ #
+ # <tt>app</tt>:: A valid Rack app to call if the conditions are met.
+ # <tt>conditions</tt>:: A hash of conditions to match against.
+ # Conditions may be expressed as strings or
+ # regexps to match against.
+ # <tt>defaults</tt>:: A hash of values that always gets merged in
+ # <tt>name</tt>:: Symbol identifier for the route used with named
+ # route generations
+ def add_route(app, conditions = {}, defaults = {}, name = nil)
+ unless conditions.is_a?(Hash)
+ raise ArgumentError, 'conditions must be a Hash'
+ end
+
+ unless conditions.all? { |method, pattern|
+ @valid_conditions.include?(method)
+ }
+ raise ArgumentError, 'conditions may only include ' +
+ @valid_conditions.inspect
+ end
+
+ route = Route.new(app, conditions, defaults, name)
+ @routes << route
+
+ @recognition_key_analyzer << route.conditions
+
+ @named_routes[route.name] = route if route.name
+ @generation_key_analyzer << route.generation_keys
+
+ expire!
+ route
+ end
+
+ def recognize(obj)
+ raise 'route set not finalized' unless @recognition_graph
+
+ cache = {}
+ keys = @recognition_keys.map { |key|
+ if key.respond_to?(:call)
+ key.call(cache, obj)
+ else
+ obj.send(key)
+ end
+ }
+
+ @recognition_graph[*keys].each do |route|
+ matches = {}
+ params = route.defaults.dup
+
+ if route.conditions.all? { |method, condition|
+ value = obj.send(method)
+ if condition.is_a?(Regexp) && (m = value.match(condition))
+ matches[method] = m
+ captures = m.captures
+ route.named_captures[method].each do |k, i|
+ if v = captures[i]
+ params[k] = v
+ end
+ end
+ true
+ elsif value == condition
+ true
+ else
+ false
+ end
+ }
+ if block_given?
+ yield route, matches, params
+ else
+ return route, matches, params
+ end
+ end
+ end
+
+ nil
+ end
+
+ X_CASCADE = 'X-Cascade'.freeze
+ PASS = 'pass'.freeze
+ PATH_INFO = 'PATH_INFO'.freeze
+
+ # Rack compatible recognition and dispatching method. Routes are
+ # tried until one returns a non-catch status code. If no routes
+ # match, the catch status code is returned.
+ #
+ # This method can only be invoked after the RouteSet has been
+ # finalized.
+ def call(env)
+ raise 'route set not finalized' unless @recognition_graph
+
+ env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
+
+ request = nil
+ req = @request_class.new(env)
+ recognize(req) do |route, matches, params|
+ # TODO: We only want to unescape params from uri related methods
+ params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
+
+ if route.prefix?
+ env[Prefix::KEY] = matches[:path_info].to_s
+ end
+
+ env[@parameters_key] = params
+ result = route.app.call(env)
+ return result unless result[1][X_CASCADE] == PASS
+ end
+
+ request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']]
+ end
+
+ # Generates a url from Rack env and identifiers or significant keys.
+ #
+ # To generate a url by named route, pass the name in as a +Symbol+.
+ # url(env, :dashboard) # => "/dashboard"
+ #
+ # Additional parameters can be passed in as a hash
+ # url(env, :people, :id => "1") # => "/people/1"
+ #
+ # If no name route is given, it will fall back to a slower
+ # generation search.
+ # url(env, :controller => "people", :action => "show", :id => "1")
+ # # => "/people/1"
+ def url(env, *args)
+ named_route, params = nil, {}
+
+ case args.length
+ when 2
+ named_route, params = args[0], args[1].dup
+ when 1
+ if args[0].is_a?(Hash)
+ params = args[0].dup
+ else
+ named_route = args[0]
+ end
+ else
+ raise ArgumentError
+ end
+
+ only_path = params.delete(:only_path)
+ recall = env[@parameters_key] || {}
+
+ unless result = generate(:all, named_route, params, recall,
+ :parameterize => lambda { |name, param| Utils.escape_uri(param) })
+ return
+ end
+
+ parts, params = result
+ return unless parts
+
+ params.each do |k, v|
+ if v
+ params[k] = v
+ else
+ params.delete(k)
+ end
+ end
+
+ req = stubbed_request_class.new(env)
+ req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params))
+ only_path ? req.fullpath : req.url
+ end
+
+ def generate(method, *args) #:nodoc:
+ raise 'route set not finalized' unless @generation_graph
+
+ method = nil if method == :all
+ named_route, params, recall, options = extract_params!(*args)
+ merged = recall.merge(params)
+ route = nil
+
+ if named_route
+ if route = @named_routes[named_route.to_sym]
+ recall = route.defaults.merge(recall)
+ url = route.generate(method, params, recall, options)
+ [url, params]
+ else
+ raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
+ end
+ else
+ keys = @generation_keys.map { |key|
+ if k = merged[key]
+ k.to_s
+ else
+ nil
+ end
+ }
+ @generation_graph[*keys].each do |r|
+ next unless r.significant_params?
+ if url = r.generate(method, params, recall, options)
+ return [url, params]
+ end
+ end
+
+ raise RoutingError, "No route matches #{params.inspect}"
+ end
+ end
+
+ # Number of routes in the set
+ def length
+ @routes.length
+ end
+
+ def rehash #:nodoc:
+ @recognition_keys = build_recognition_keys
+ @recognition_graph = build_recognition_graph
+ @generation_keys = build_generation_keys
+ @generation_graph = build_generation_graph
+ end
+
+ # Finalizes the set and builds optimized data structures. You *must*
+ # freeze the set before you can use <tt>call</tt> and <tt>url</tt>.
+ # So remember to call freeze after you are done adding routes.
+ def freeze
+ unless frozen?
+ rehash
+
+ @recognition_key_analyzer = nil
+ @generation_key_analyzer = nil
+ @valid_conditions = nil
+
+ @routes.each { |route| route.freeze }
+ @routes.freeze
+ end
+
+ super
+ end
+
+ def marshal_dump #:nodoc:
+ hash = {}
+
+ instance_variables_to_serialize.each do |ivar|
+ hash[ivar] = instance_variable_get(ivar)
+ end
+
+ if graph = hash[:@recognition_graph]
+ hash[:@recognition_graph] = graph.dup
+ end
+
+ hash
+ end
+
+ def marshal_load(hash) #:nodoc:
+ hash.each do |ivar, value|
+ instance_variable_set(ivar, value)
+ end
+ end
+
+ protected
+ def recognition_stats
+ { :keys => @recognition_keys,
+ :keys_size => @recognition_keys.size,
+ :graph_size => @recognition_graph.size,
+ :graph_height => @recognition_graph.height,
+ :graph_average_height => @recognition_graph.average_height }
+ end
+
+ private
+ def expire! #:nodoc:
+ @recognition_keys = @recognition_graph = nil
+ @recognition_key_analyzer.expire!
+
+ @generation_keys = @generation_graph = nil
+ @generation_key_analyzer.expire!
+ end
+
+ def instance_variables_to_serialize
+ instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined]
+ end
+
+ # An internal helper method for constructing a nested set from
+ # the linear route set.
+ #
+ # build_nested_route_set([:request_method, :path_info]) { |route, method|
+ # route.send(method)
+ # }
+ def build_nested_route_set(keys, &block)
+ graph = Multimap.new
+ @routes.each_with_index do |route, index|
+ catch(:skip) do
+ k = keys.map { |key| block.call(key, index) }
+ Utils.pop_trailing_nils!(k)
+ k.map! { |key| key || /.+/ }
+ graph[*k] = route
+ end
+ end
+ graph
+ end
+
+ def build_recognition_graph
+ build_nested_route_set(@recognition_keys) { |k, i|
+ @recognition_key_analyzer.possible_keys[i][k]
+ }
+ end
+
+ def build_recognition_keys
+ @recognition_key_analyzer.report
+ end
+
+ def build_generation_graph
+ build_nested_route_set(@generation_keys) { |k, i|
+ throw :skip unless @routes[i].significant_params?
+
+ if k = @generation_key_analyzer.possible_keys[i][k]
+ k.to_s
+ else
+ nil
+ end
+ }
+ end
+
+ def build_generation_keys
+ @generation_key_analyzer.report
+ end
+
+ def extract_params!(*args)
+ case args.length
+ when 4
+ named_route, params, recall, options = args
+ when 3
+ if args[0].is_a?(Hash)
+ params, recall, options = args
+ else
+ named_route, params, recall = args
+ end
+ when 2
+ if args[0].is_a?(Hash)
+ params, recall = args
+ else
+ named_route, params = args
+ end
+ when 1
+ if args[0].is_a?(Hash)
+ params = args[0]
+ else
+ named_route = args[0]
+ end
+ else
+ raise ArgumentError
+ end
+
+ named_route ||= nil
+ params ||= {}
+ recall ||= {}
+ options ||= {}
+
+ [named_route, params.dup, recall.dup, options.dup]
+ end
+
+ def stubbed_request_class
+ @stubbed_request_class ||= begin
+ klass = Class.new(@request_class)
+ klass.public_instance_methods.each do |method|
+ next if method =~ /^__|object_id/
+ klass.class_eval <<-RUBY
+ def #{method}(*args, &block)
+ @_stubbed_values[:#{method}] || super
+ end
+ RUBY
+ end
+ klass.class_eval { attr_accessor :_stubbed_values }
+ klass
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb
new file mode 100644
index 0000000000..d0d8797008
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb
@@ -0,0 +1,68 @@
+require 'rack/mount/strexp/parser'
+
+module Rack::Mount
+ class Strexp
+ class << self
+ # Parses segmented string expression and converts it into a Regexp
+ #
+ # Strexp.compile('foo')
+ # # => %r{\Afoo\Z}
+ #
+ # Strexp.compile('foo/:bar', {}, ['/'])
+ # # => %r{\Afoo/(?<bar>[^/]+)\Z}
+ #
+ # Strexp.compile(':foo.example.com')
+ # # => %r{\A(?<foo>.+)\.example\.com\Z}
+ #
+ # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/'])
+ # # => %r{\Afoo/(?<bar>[a-z]+)\Z}
+ #
+ # Strexp.compile('foo(.:extension)')
+ # # => %r{\Afoo(\.(?<extension>.+))?\Z}
+ #
+ # Strexp.compile('src/*files')
+ # # => %r{\Asrc/(?<files>.+)\Z}
+ def compile(str, requirements = {}, separators = [], anchor = true)
+ return Regexp.compile(str) if str.is_a?(Regexp)
+
+ requirements = requirements ? requirements.dup : {}
+ normalize_requirements!(requirements, separators)
+
+ parser = StrexpParser.new
+ parser.anchor = anchor
+ parser.requirements = requirements
+
+ begin
+ re = parser.scan_str(str)
+ rescue Racc::ParseError => e
+ raise RegexpError, e.message
+ end
+
+ Regexp.compile(re)
+ end
+ alias_method :new, :compile
+
+ private
+ def normalize_requirements!(requirements, separators)
+ requirements.each do |key, value|
+ if value.is_a?(Regexp)
+ if regexp_has_modifiers?(value)
+ requirements[key] = value
+ else
+ requirements[key] = value.source
+ end
+ else
+ requirements[key] = Regexp.escape(value)
+ end
+ end
+ requirements.default ||= separators.any? ?
+ "[^#{separators.join}]+" : '.+'
+ requirements
+ end
+
+ def regexp_has_modifiers?(regexp)
+ regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb
new file mode 100644
index 0000000000..cfe05afc61
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb
@@ -0,0 +1,160 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by Racc 1.4.6
+# from Racc grammer file "".
+#
+
+require 'racc/parser.rb'
+
+require 'rack/mount/utils'
+require 'rack/mount/strexp/tokenizer'
+
+module Rack
+ module Mount
+ class StrexpParser < Racc::Parser
+
+
+if Regin.regexp_supports_named_captures?
+ REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
+else
+ REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
+end
+
+attr_accessor :anchor, :requirements
+##### State transition tables begin ###
+
+racc_action_table = [
+ 1, 2, 3, 9, 4, 1, 2, 3, 12, 4,
+ 1, 2, 3, 11, 4, 1, 2, 3, nil, 4 ]
+
+racc_action_check = [
+ 0, 0, 0, 5, 0, 3, 3, 3, 9, 3,
+ 8, 8, 8, 8, 8, 6, 6, 6, nil, 6 ]
+
+racc_action_pointer = [
+ -2, nil, nil, 3, nil, 3, 13, nil, 8, 8,
+ nil, nil, nil ]
+
+racc_action_default = [
+ -8, -4, -5, -8, -7, -8, -1, -3, -8, -8,
+ -2, -6, 13 ]
+
+racc_goto_table = [
+ 6, 5, 10, 8, 10 ]
+
+racc_goto_check = [
+ 2, 1, 3, 2, 3 ]
+
+racc_goto_pointer = [
+ nil, 1, 0, -4 ]
+
+racc_goto_default = [
+ nil, nil, nil, 7 ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 1, 8, :_reduce_1,
+ 2, 9, :_reduce_2,
+ 1, 9, :_reduce_none,
+ 1, 10, :_reduce_4,
+ 1, 10, :_reduce_5,
+ 3, 10, :_reduce_6,
+ 1, 10, :_reduce_7 ]
+
+racc_reduce_n = 8
+
+racc_shift_n = 13
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :PARAM => 2,
+ :GLOB => 3,
+ :LPAREN => 4,
+ :RPAREN => 5,
+ :CHAR => 6 }
+
+racc_nt_base = 7
+
+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",
+ "PARAM",
+ "GLOB",
+ "LPAREN",
+ "RPAREN",
+ "CHAR",
+ "$start",
+ "target",
+ "expr",
+ "token" ]
+
+Racc_debug_parser = false
+
+##### State transition tables end #####
+
+# reduce 0 omitted
+
+def _reduce_1(val, _values, result)
+ result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}"
+ result
+end
+
+def _reduce_2(val, _values, result)
+ result = val.join
+ result
+end
+
+# reduce 3 omitted
+
+def _reduce_4(val, _values, result)
+ name = val[0].to_sym
+ requirement = requirements[name]
+ result = REGEXP_NAMED_CAPTURE % [name, requirement]
+
+ result
+end
+
+def _reduce_5(val, _values, result)
+ name = val[0].to_sym
+ requirement = requirements[name]
+ result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement]
+
+ result
+end
+
+def _reduce_6(val, _values, result)
+ result = "(?:#{val[1]})?"
+ result
+end
+
+def _reduce_7(val, _values, result)
+ result = Regexp.escape(val[0])
+ result
+end
+
+def _reduce_none(val, _values, result)
+ val[0]
+end
+
+ end # class StrexpParser
+ end # module Mount
+end # module Rack
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y
new file mode 100644
index 0000000000..ffbd9fae11
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y
@@ -0,0 +1,34 @@
+class Rack::Mount::StrexpParser
+rule
+ target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" }
+
+ expr: expr token { result = val.join }
+ | token
+
+ token: PARAM {
+ name = val[0].to_sym
+ requirement = requirements[name]
+ result = REGEXP_NAMED_CAPTURE % [name, requirement]
+ }
+ | GLOB {
+ name = val[0].to_sym
+ requirement = requirements[name]
+ result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement]
+ }
+ | LPAREN expr RPAREN { result = "(?:#{val[1]})?" }
+ | CHAR { result = Regexp.escape(val[0]) }
+end
+
+---- header ----
+require 'rack/mount/utils'
+require 'rack/mount/strexp/tokenizer'
+
+---- inner
+
+if Regin.regexp_supports_named_captures?
+ REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
+else
+ REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
+end
+
+attr_accessor :anchor, :requirements
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb
new file mode 100644
index 0000000000..0ff7f67661
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb
@@ -0,0 +1,83 @@
+#--
+# DO NOT MODIFY!!!!
+# This file is automatically generated by rex 1.0.5.beta1
+# from lexical definition file "lib/rack/mount/strexp/tokenizer.rex".
+#++
+
+require 'racc/parser'
+class Rack::Mount::StrexpParser < Racc::Parser
+ require 'strscan'
+
+ class ScanError < StandardError ; end
+
+ attr_reader :lineno
+ attr_reader :filename
+ attr_accessor :state
+
+ def scan_setup(str)
+ @ss = StringScanner.new(str)
+ @lineno = 1
+ @state = nil
+ end
+
+ def action
+ yield
+ end
+
+ def scan_str(str)
+ scan_setup(str)
+ do_parse
+ end
+ alias :scan :scan_str
+
+ def load_file( filename )
+ @filename = filename
+ open(filename, "r") do |f|
+ scan_setup(f.read)
+ end
+ end
+
+ def scan_file( filename )
+ load_file(filename)
+ do_parse
+ end
+
+
+ def next_token
+ return if @ss.eos?
+
+ text = @ss.peek(1)
+ @lineno += 1 if text == "\n"
+ token = case @state
+ when nil
+ case
+ when (text = @ss.scan(/\\(\(|\)|:|\*)/))
+ action { [:CHAR, @ss[1]] }
+
+ when (text = @ss.scan(/\:([a-zA-Z_]\w*)/))
+ action { [:PARAM, @ss[1]] }
+
+ when (text = @ss.scan(/\*([a-zA-Z_]\w*)/))
+ action { [:GLOB, @ss[1]] }
+
+ when (text = @ss.scan(/\(/))
+ action { [:LPAREN, text] }
+
+ when (text = @ss.scan(/\)/))
+ action { [:RPAREN, text] }
+
+ when (text = @ss.scan(/./))
+ action { [:CHAR, text] }
+
+ else
+ text = @ss.string[@ss.pos .. -1]
+ raise ScanError, "can not match: '" + text + "'"
+ end # if
+
+ else
+ raise ScanError, "undefined state: '" + state.to_s + "'"
+ end # case state
+ token
+ end # def next_token
+
+end # class
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex
new file mode 100644
index 0000000000..473bd096e1
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex
@@ -0,0 +1,12 @@
+class Rack::Mount::StrexpParser
+macro
+ RESERVED \(|\)|:|\*
+ ALPHA_U [a-zA-Z_]
+rule
+ \\({RESERVED}) { [:CHAR, @ss[1]] }
+ \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] }
+ \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] }
+ \( { [:LPAREN, text] }
+ \) { [:RPAREN, text] }
+ . { [:CHAR, text] }
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb
new file mode 100644
index 0000000000..aa23b1162f
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb
@@ -0,0 +1,148 @@
+begin
+ require 'regin'
+rescue LoadError
+ $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin'))
+ require 'regin'
+end
+
+require 'uri'
+
+module Rack::Mount
+ # Private utility methods used throughout Rack::Mount.
+ #--
+ # This module is a trash can. Try to move these functions into
+ # more appropriate contexts.
+ #++
+ module Utils
+ # 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 normalize_path(path)
+ path = "/#{path}"
+ path.squeeze!('/')
+ path.sub!(%r{/+\Z}, '')
+ path = '/' if path == ''
+ path
+ end
+ module_function :normalize_path
+
+ # Removes trailing nils from array.
+ #
+ # pop_trailing_nils!([1, 2, 3]) # => [1, 2, 3]
+ # pop_trailing_nils!([1, 2, 3, nil, nil]) # => [1, 2, 3]
+ # pop_trailing_nils!([nil]) # => []
+ def pop_trailing_nils!(ary)
+ while ary.length > 0 && ary.last.nil?
+ ary.pop
+ end
+ ary
+ end
+ module_function :pop_trailing_nils!
+
+ RESERVED_PCHAR = ':@&=+$,;%'
+ SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
+ if RUBY_VERSION >= '1.9'
+ UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
+ else
+ UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
+ end
+
+ Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
+
+ def escape_uri(uri)
+ Parser.escape(uri.to_s, UNSAFE_PCHAR)
+ end
+ module_function :escape_uri
+
+ if ''.respond_to?(:force_encoding)
+ def unescape_uri(uri)
+ Parser.unescape(uri).force_encoding('utf-8')
+ end
+ else
+ def unescape_uri(uri)
+ URI.unescape(uri)
+ end
+ end
+ module_function :unescape_uri
+
+ # Taken from Rack 1.1.x to build nested query strings
+ def build_nested_query(value, prefix = nil) #:nodoc:
+ case value
+ when Array
+ value.map { |v|
+ build_nested_query(v, "#{prefix}[]")
+ }.join("&")
+ when Hash
+ value.map { |k, v|
+ build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k))
+ }.join("&")
+ when String
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
+ "#{prefix}=#{Rack::Utils.escape(value)}"
+ else
+ prefix
+ end
+ end
+ module_function :build_nested_query
+
+ # Determines whether the regexp must match the entire string.
+ #
+ # regexp_anchored?(/^foo$/) # => true
+ # regexp_anchored?(/foo/) # => false
+ # regexp_anchored?(/^foo/) # => false
+ # regexp_anchored?(/foo$/) # => false
+ def regexp_anchored?(regexp)
+ regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false
+ end
+ module_function :regexp_anchored?
+
+ def normalize_extended_expression(regexp)
+ return regexp unless regexp.options & Regexp::EXTENDED != 0
+ source = regexp.source
+ source.gsub!(/#.+$/, '')
+ source.gsub!(/\s+/, '')
+ source.gsub!(/\\\//, '/')
+ Regexp.compile(source)
+ end
+ module_function :normalize_extended_expression
+
+ def parse_regexp(regexp)
+ cache = @@_parse_regexp_cache ||= {}
+
+ if expression = cache[regexp]
+ return expression
+ end
+
+ unless regexp.is_a?(RegexpWithNamedGroups)
+ regexp = RegexpWithNamedGroups.new(regexp)
+ end
+
+ expression = Regin.parse(regexp)
+
+ unless Regin.regexp_supports_named_captures?
+ tag_captures = Proc.new do |group|
+ case group
+ when Regin::Group
+ # TODO: dup instead of mutating
+ group.instance_variable_set('@name', regexp.names[group.index]) if group.index
+ tag_captures.call(group.expression)
+ when Regin::Expression
+ group.each { |child| tag_captures.call(child) }
+ end
+ end
+ tag_captures.call(expression)
+ end
+
+ cache[regexp] = expression.freeze
+ expression
+ rescue Racc::ParseError, Regin::Parser::ScanError
+ []
+ end
+ module_function :parse_regexp
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb
new file mode 100644
index 0000000000..0b49b49280
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb
@@ -0,0 +1,569 @@
+require 'forwardable'
+require 'multiset'
+
+# Multimap is a generalization of a map or associative array
+# abstract data type in which more than one value may be associated
+# with and returned for a given key.
+#
+# == Example
+#
+# require 'multimap'
+# map = Multimap.new
+# map["a"] = 100
+# map["b"] = 200
+# map["a"] = 300
+# map["a"] # -> [100, 300]
+# map["b"] # -> [200]
+# map.keys # -> #<Multiset: {a, a, b}>
+class Multimap
+ extend Forwardable
+
+ include Enumerable
+
+ # call-seq:
+ # Multimap[ [key =>|, value]* ] => multimap
+ #
+ # Creates a new multimap populated with the given objects.
+ #
+ # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]}
+ # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]}
+ def self.[](*args)
+ default = []
+
+ if args.size == 2 && args.last.is_a?(Hash)
+ default = args.shift
+ elsif !args.first.is_a?(Hash) && args.size % 2 == 1
+ default = args.shift
+ end
+
+ if args.size == 1 && args.first.is_a?(Hash)
+ args[0] = args.first.inject({}) { |hash, (key, value)|
+ unless value.is_a?(default.class)
+ value = (default.dup << value)
+ end
+ hash[key] = value
+ hash
+ }
+ else
+ index = 0
+ args.map! { |value|
+ unless index % 2 == 0 || value.is_a?(default.class)
+ value = (default.dup << value)
+ end
+ index += 1
+ value
+ }
+ end
+
+ map = new
+ map.instance_variable_set(:@hash, Hash[*args])
+ map.default = default
+ map
+ end
+
+ # call-seq:
+ # Multimap.new => multimap
+ # Multimap.new(default) => multimap
+ #
+ # Returns a new, empty multimap.
+ #
+ # map = Multimap.new(Set.new)
+ # h["a"] = 100
+ # h["b"] = 200
+ # h["a"] #=> [100].to_set
+ # h["c"] #=> [].to_set
+ def initialize(default = [])
+ @hash = Hash.new(default)
+ end
+
+ def initialize_copy(original) #:nodoc:
+ @hash = Hash.new(original.default.dup)
+ original._internal_hash.each_pair do |key, container|
+ @hash[key] = container.dup
+ end
+ end
+
+ def_delegators :@hash, :clear, :default, :default=, :empty?,
+ :fetch, :has_key?, :key?
+
+ # Retrieves the <i>value</i> object corresponding to the
+ # <i>*keys</i> object.
+ def [](key)
+ @hash[key]
+ end
+
+ # call-seq:
+ # map[key] = value => value
+ # map.store(key, value) => value
+ #
+ # Associates the value given by <i>value</i> with the key
+ # given by <i>key</i>. Unlike a regular hash, multiple can be
+ # assoicated with the same value.
+ #
+ # map = Multimap["a" => 100, "b" => 200]
+ # map["a"] = 9
+ # map["c"] = 4
+ # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]}
+ def store(key, value)
+ update_container(key) do |container|
+ container << value
+ container
+ end
+ end
+ alias_method :[]=, :store
+
+ # call-seq:
+ # map.delete(key, value) => value
+ # map.delete(key) => value
+ #
+ # Deletes and returns a key-value pair from <i>map</i>. If only
+ # <i>key</i> is given, all the values matching that key will be
+ # deleted.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.delete("b", 300) #=> 300
+ # map.delete("a") #=> [100]
+ def delete(key, value = nil)
+ if value
+ @hash[key].delete(value)
+ else
+ @hash.delete(key)
+ end
+ end
+
+ # call-seq:
+ # map.each { |key, value| block } => map
+ #
+ # Calls <i>block</i> for each key/value pair in <i>map</i>, passing
+ # the key and value to the block as a two-element array.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each { |key, value| puts "#{key} is #{value}" }
+ #
+ # <em>produces:</em>
+ #
+ # a is 100
+ # b is 200
+ # b is 300
+ def each
+ each_pair do |key, value|
+ yield [key, value]
+ end
+ end
+
+ # call-seq:
+ # map.each_association { |key, container| block } => map
+ #
+ # Calls <i>block</i> once for each key/container in <i>map</i>, passing
+ # the key and container to the block as parameters.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each_association { |key, container| puts "#{key} is #{container}" }
+ #
+ # <em>produces:</em>
+ #
+ # a is [100]
+ # b is [200, 300]
+ def each_association(&block)
+ @hash.each_pair(&block)
+ end
+
+ # call-seq:
+ # map.each_container { |container| block } => map
+ #
+ # Calls <i>block</i> for each container in <i>map</i>, passing the
+ # container as a parameter.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each_container { |container| puts container }
+ #
+ # <em>produces:</em>
+ #
+ # [100]
+ # [200, 300]
+ def each_container
+ each_association do |_, container|
+ yield container
+ end
+ end
+
+ # call-seq:
+ # map.each_key { |key| block } => map
+ #
+ # Calls <i>block</i> for each key in <i>hsh</i>, passing the key
+ # as a parameter.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each_key { |key| puts key }
+ #
+ # <em>produces:</em>
+ #
+ # a
+ # b
+ # b
+ def each_key
+ each_pair do |key, _|
+ yield key
+ end
+ end
+
+ # call-seq:
+ # map.each_pair { |key_value_array| block } => map
+ #
+ # Calls <i>block</i> for each key/value pair in <i>map</i>,
+ # passing the key and value as parameters.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each_pair { |key, value| puts "#{key} is #{value}" }
+ #
+ # <em>produces:</em>
+ #
+ # a is 100
+ # b is 200
+ # b is 300
+ def each_pair
+ each_association do |key, values|
+ values.each do |value|
+ yield key, value
+ end
+ end
+ end
+
+ # call-seq:
+ # map.each_value { |value| block } => map
+ #
+ # Calls <i>block</i> for each key in <i>map</i>, passing the
+ # value as a parameter.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.each_value { |value| puts value }
+ #
+ # <em>produces:</em>
+ #
+ # 100
+ # 200
+ # 300
+ def each_value
+ each_pair do |_, value|
+ yield value
+ end
+ end
+
+ def ==(other) #:nodoc:
+ case other
+ when Multimap
+ @hash == other._internal_hash
+ else
+ @hash == other
+ end
+ end
+
+ def eql?(other) #:nodoc:
+ case other
+ when Multimap
+ @hash.eql?(other._internal_hash)
+ else
+ @hash.eql?(other)
+ end
+ end
+
+ def freeze #:nodoc:
+ each_container { |container| container.freeze }
+ default.freeze
+ super
+ end
+
+ # call-seq:
+ # map.has_value?(value) => true or false
+ # map.value?(value) => true or false
+ #
+ # Returns <tt>true</tt> if the given value is present for any key
+ # in <i>map</i>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.has_value?(300) #=> true
+ # map.has_value?(999) #=> false
+ def has_value?(value)
+ values.include?(value)
+ end
+ alias_method :value?, :has_value?
+
+ # call-seq:
+ # map.index(value) => key
+ #
+ # Returns the key for a given value. If not found, returns
+ # <tt>nil</tt>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.index(100) #=> "a"
+ # map.index(200) #=> "b"
+ # map.index(999) #=> nil
+ def index(value)
+ invert[value]
+ end
+
+ # call-seq:
+ # map.delete_if {| key, value | block } -> map
+ #
+ # Deletes every key-value pair from <i>map</i> for which <i>block</i>
+ # evaluates to <code>true</code>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.delete_if {|key, value| value >= 300 }
+ # #=> Multimap["a" => 100, "b" => 200]
+ #
+ def delete_if
+ each_association do |key, container|
+ container.delete_if do |value|
+ yield [key, value]
+ end
+ end
+ self
+ end
+
+ # call-seq:
+ # map.reject {| key, value | block } -> map
+ #
+ # Same as <code>Multimap#delete_if</code>, but works on (and returns) a
+ # copy of the <i>map</i>. Equivalent to
+ # <code><i>map</i>.dup.delete_if</code>.
+ #
+ def reject(&block)
+ dup.delete_if(&block)
+ end
+
+ # call-seq:
+ # map.reject! {| key, value | block } -> map or nil
+ #
+ # Equivalent to <code>Multimap#delete_if</code>, but returns
+ # <code>nil</code> if no changes were made.
+ #
+ def reject!(&block)
+ old_size = size
+ delete_if(&block)
+ old_size == size ? nil : self
+ end
+
+ # call-seq:
+ # map.replace(other_map) => map
+ #
+ # Replaces the contents of <i>map</i> with the contents of
+ # <i>other_map</i>.
+ #
+ # map = Multimap["a" => 100, "b" => 200]
+ # map.replace({ "c" => 300, "d" => 400 })
+ # #=> Multimap["c" => 300, "d" => 400]
+ def replace(other)
+ case other
+ when Array
+ @hash.replace(self.class[self.default, *other])
+ when Hash
+ @hash.replace(self.class[self.default, other])
+ when self.class
+ @hash.replace(other)
+ else
+ raise ArgumentError
+ end
+ end
+
+ # call-seq:
+ # map.invert => multimap
+ #
+ # Returns a new multimap created by using <i>map</i>'s values as keys,
+ # and the keys as values.
+ #
+ # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]]
+ # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"]
+ def invert
+ h = self.class.new(default.dup)
+ each_pair { |key, value| h[value] = key }
+ h
+ end
+
+ # call-seq:
+ # map.keys => multiset
+ #
+ # Returns a new +Multiset+ populated with the keys from this hash. See also
+ # <tt>Multimap#values</tt> and <tt>Multimap#containers</tt>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
+ # map.keys #=> Multiset.new(["a", "b", "b", "c"])
+ def keys
+ keys = Multiset.new
+ each_key { |key| keys << key }
+ keys
+ end
+
+ # Returns true if the given key is present in Multimap.
+ def include?(key)
+ keys.include?(key)
+ end
+ alias_method :member?, :include?
+
+ # call-seq:
+ # map.length => fixnum
+ # map.size => fixnum
+ #
+ # Returns the number of key-value pairs in the map.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
+ # map.length #=> 4
+ # map.delete("a") #=> 100
+ # map.length #=> 3
+ def size
+ values.size
+ end
+ alias_method :length, :size
+
+ # call-seq:
+ # map.merge(other_map) => multimap
+ #
+ # Returns a new multimap containing the contents of <i>other_map</i> and
+ # the contents of <i>map</i>.
+ #
+ # map1 = Multimap["a" => 100, "b" => 200]
+ # map2 = Multimap["a" => 254, "c" => 300]
+ # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
+ # map1 #=> Multimap["a" => 100, "b" => 200]
+ def merge(other)
+ dup.update(other)
+ end
+
+ # call-seq:
+ # map.merge!(other_map) => multimap
+ # map.update(other_map) => multimap
+ #
+ # Adds each pair from <i>other_map</i> to <i>map</i>.
+ #
+ # map1 = Multimap["a" => 100, "b" => 200]
+ # map2 = Multimap["b" => 254, "c" => 300]
+ #
+ # map1.merge!(map2)
+ # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
+ def update(other)
+ case other
+ when self.class
+ other.each_pair { |key, value| store(key, value) }
+ when Hash
+ update(self.class[self.default, other])
+ else
+ raise ArgumentError
+ end
+ self
+ end
+ alias_method :merge!, :update
+
+ # call-seq:
+ # map.select { |key, value| block } => multimap
+ #
+ # Returns a new Multimap consisting of the pairs for which the
+ # block returns true.
+ #
+ # map = Multimap["a" => 100, "b" => 200, "c" => 300]
+ # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300]
+ # map.select { |k,v| v < 200 } #=> Multimap["a" => 100]
+ def select
+ inject(self.class.new) { |map, (key, value)|
+ map[key] = value if yield([key, value])
+ map
+ }
+ end
+
+ # call-seq:
+ # map.to_a => array
+ #
+ # Converts <i>map</i> to a nested array of [<i>key,
+ # value</i>] arrays.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
+ # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]]
+ def to_a
+ ary = []
+ each_pair do |key, value|
+ ary << [key, value]
+ end
+ ary
+ end
+
+ # call-seq:
+ # map.to_hash => hash
+ #
+ # Converts <i>map</i> to a basic hash.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.to_hash #=> { "a" => [100], "b" => [200, 300] }
+ def to_hash
+ @hash.dup
+ end
+
+ # call-seq:
+ # map.containers => array
+ #
+ # Returns a new array populated with the containers from <i>map</i>. See
+ # also <tt>Multimap#keys</tt> and <tt>Multimap#values</tt>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.containers #=> [[100], [200, 300]]
+ def containers
+ containers = []
+ each_container { |container| containers << container }
+ containers
+ end
+
+ # call-seq:
+ # map.values => array
+ #
+ # Returns a new array populated with the values from <i>map</i>. See
+ # also <tt>Multimap#keys</tt> and <tt>Multimap#containers</tt>.
+ #
+ # map = Multimap["a" => 100, "b" => [200, 300]]
+ # map.values #=> [100, 200, 300]
+ def values
+ values = []
+ each_value { |value| values << value }
+ values
+ end
+
+ # Return an array containing the values associated with the given keys.
+ def values_at(*keys)
+ @hash.values_at(*keys)
+ end
+
+ def marshal_dump #:nodoc:
+ @hash
+ end
+
+ def marshal_load(hash) #:nodoc:
+ @hash = hash
+ end
+
+ def to_yaml(opts = {}) #:nodoc:
+ YAML::quick_emit(self, opts) do |out|
+ out.map(taguri, to_yaml_style) do |map|
+ @hash.each do |k, v|
+ map.add(k, v)
+ end
+ map.add('__default__', @hash.default)
+ end
+ end
+ end
+
+ def yaml_initialize(tag, val) #:nodoc:
+ default = val.delete('__default__')
+ @hash = val
+ @hash.default = default
+ self
+ end
+
+ protected
+ def _internal_hash #:nodoc:
+ @hash
+ end
+
+ def update_container(key) #:nodoc:
+ container = @hash[key]
+ container = container.dup if container.equal?(default)
+ container = yield(container)
+ @hash[key] = container
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb
new file mode 100644
index 0000000000..119bf12646
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb
@@ -0,0 +1,185 @@
+require 'set'
+
+# Multiset implements a collection of unordered values and
+# allows duplicates.
+#
+# == Example
+#
+# require 'multiset'
+# s1 = Multiset.new [1, 2] # -> #<Multiset: {1, 2}>
+# s1.add(2) # -> #<Multiset: {1, 2, 2}>
+# s1.merge([2, 6]) # -> #<Multiset: {1, 2, 2, 2, 3}>
+# s1.multiplicity(2) # -> 3
+# s1.multiplicity(3) # -> 1
+class Multiset < Set
+ def initialize(*args, &block) #:nodoc:
+ @hash = Hash.new(0)
+ super
+ end
+
+ # Returns the number of times an element belongs to the multiset.
+ def multiplicity(e)
+ @hash[e]
+ end
+
+ # Returns the total number of elements in a multiset, including
+ # repeated memberships
+ def cardinality
+ @hash.inject(0) { |s, (e, m)| s += m }
+ end
+ alias_method :size, :cardinality
+ alias_method :length, :cardinality
+
+ # Converts the set to an array. The order of elements is uncertain.
+ def to_a
+ inject([]) { |ary, (key, _)| ary << key }
+ end
+
+ # Returns true if the set is a superset of the given set.
+ def superset?(set)
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
+ return false if cardinality < set.cardinality
+ set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
+ end
+
+ # Returns true if the set is a proper superset of the given set.
+ def proper_superset?(set)
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
+ return false if cardinality <= set.cardinality
+ set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
+ end
+
+ # Returns true if the set is a subset of the given set.
+ def subset?(set)
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
+ return false if set.cardinality < cardinality
+ all? { |o| multiplicity(o) <= set.multiplicity(o) }
+ end
+
+ # Returns true if the set is a proper subset of the given set.
+ def proper_subset?(set)
+ set.is_a?(self.class) or raise ArgumentError, "value must be a set"
+ return false if set.cardinality <= cardinality
+ all? { |o| multiplicity(o) <= set.multiplicity(o) }
+ end
+
+ # Calls the given block once for each element in the set, passing
+ # the element as parameter. Returns an enumerator if no block is
+ # given.
+ def each
+ @hash.each_pair do |key, multiplicity|
+ multiplicity.times do
+ yield(key)
+ end
+ end
+ self
+ end
+
+ # Adds the given object to the set and returns self. Use +merge+ to
+ # add many elements at once.
+ def add(o)
+ @hash[o] ||= 0
+ @hash[o] += 1
+ self
+ end
+ alias << add
+
+ undef :add?
+
+ # Deletes all the identical object from the set and returns self.
+ # If +n+ is given, it will remove that amount of identical objects
+ # from the set. Use +subtract+ to delete many different items at
+ # once.
+ def delete(o, n = nil)
+ if n
+ @hash[o] ||= 0
+ @hash[o] -= n if @hash[o] > 0
+ @hash.delete(o) if @hash[o] == 0
+ else
+ @hash.delete(o)
+ end
+ self
+ end
+
+ undef :delete?
+
+ # Deletes every element of the set for which block evaluates to
+ # true, and returns self.
+ def delete_if
+ each { |o| delete(o) if yield(o) }
+ self
+ end
+
+ # Merges the elements of the given enumerable object to the set and
+ # returns self.
+ def merge(enum)
+ enum.each { |o| add(o) }
+ self
+ end
+
+ # Deletes every element that appears in the given enumerable object
+ # and returns self.
+ def subtract(enum)
+ enum.each { |o| delete(o, 1) }
+ self
+ end
+
+ # Returns a new set containing elements common to the set and the
+ # given enumerable object.
+ def &(enum)
+ s = dup
+ n = self.class.new
+ enum.each { |o|
+ if s.include?(o)
+ s.delete(o, 1)
+ n.add(o)
+ end
+ }
+ n
+ end
+ alias intersection &
+
+ # Returns a new set containing elements exclusive between the set
+ # and the given enumerable object. (set ^ enum) is equivalent to
+ # ((set | enum) - (set & enum)).
+ def ^(enum)
+ n = self.class.new(enum)
+ each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) }
+ n
+ end
+
+ # Returns true if two sets are equal. Two multisets are equal if
+ # they have the same cardinalities and each element has the same
+ # multiplicity in both sets. The equality of each element inside
+ # the multiset is defined according to Object#eql?.
+ def eql?(set)
+ return true if equal?(set)
+ set = self.class.new(set) unless set.is_a?(self.class)
+ return false unless cardinality == set.cardinality
+ superset?(set) && subset?(set)
+ end
+ alias_method :==, :eql?
+
+ def marshal_dump #:nodoc:
+ @hash
+ end
+
+ def marshal_load(hash) #:nodoc:
+ @hash = hash
+ end
+
+ def to_yaml(opts = {}) #:nodoc:
+ YAML::quick_emit(self, opts) do |out|
+ out.map(taguri, to_yaml_style) do |map|
+ @hash.each do |k, v|
+ map.add(k, v)
+ end
+ end
+ end
+ end
+
+ def yaml_initialize(tag, val) #:nodoc:
+ @hash = val
+ self
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb
new file mode 100644
index 0000000000..4eb088b91a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb
@@ -0,0 +1,158 @@
+require 'multimap'
+
+# NestedMultimap allows values to be assoicated with a nested
+# set of keys.
+class NestedMultimap < Multimap
+ # call-seq:
+ # multimap[*keys] = value => value
+ # multimap.store(*keys, value) => value
+ #
+ # Associates the value given by <i>value</i> with multiple key
+ # given by <i>keys</i>.
+ #
+ # map = NestedMultimap.new
+ # map["a"] = 100
+ # map["a", "b"] = 101
+ # map["a"] = 102
+ # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}}
+ def store(*args)
+ keys = args
+ value = args.pop
+
+ raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
+
+ if keys.length > 1
+ update_container(keys.shift) do |container|
+ container = self.class.new(container) unless container.is_a?(self.class)
+ container[*keys] = value
+ container
+ end
+ elsif keys.length == 1
+ super(keys.first, value)
+ else
+ self << value
+ end
+ end
+ alias_method :[]=, :store
+
+ # call-seq:
+ # multimap << obj => multimap
+ #
+ # Pushes the given object on to the end of all the containers.
+ #
+ # map = NestedMultimap["a" => [100], "b" => [200, 300]]
+ # map << 300
+ # map["a"] #=> [100, 300]
+ # map["c"] #=> [300]
+ def <<(value)
+ @hash.each_value { |container| container << value }
+ self.default << value
+ self
+ end
+
+ # call-seq:
+ # multimap[*keys] => value
+ # multimap[key1, key2, key3] => value
+ #
+ # Retrieves the <i>value</i> object corresponding to the
+ # <i>*keys</i> object.
+ def [](*keys)
+ i, l, r, k = 0, keys.length, self, self.class
+ while r.is_a?(k)
+ r = i < l ? r._internal_hash[keys[i]] : r.default
+ i += 1
+ end
+ r
+ end
+
+ # call-seq:
+ # multimap.each_association { |key, container| block } => multimap
+ #
+ # Calls <i>block</i> once for each key/container in <i>map</i>, passing
+ # the key and container to the block as parameters.
+ #
+ # map = NestedMultimap.new
+ # map["a"] = 100
+ # map["a", "b"] = 101
+ # map["a"] = 102
+ # map["c"] = 200
+ # map.each_association { |key, container| puts "#{key} is #{container}" }
+ #
+ # <em>produces:</em>
+ #
+ # ["a", "b"] is [100, 101, 102]
+ # "c" is [200]
+ def each_association
+ super() do |key, container|
+ if container.respond_to?(:each_association)
+ container.each_association do |nested_key, value|
+ yield [key, nested_key].flatten, value
+ end
+ else
+ yield key, container
+ end
+ end
+ end
+
+ # call-seq:
+ # multimap.each_container_with_default { |container| block } => map
+ #
+ # Calls <i>block</i> for every container in <i>map</i> including
+ # the default, passing the container as a parameter.
+ #
+ # map = NestedMultimap.new
+ # map["a"] = 100
+ # map["a", "b"] = 101
+ # map["a"] = 102
+ # map.each_container_with_default { |container| puts container }
+ #
+ # <em>produces:</em>
+ #
+ # [100, 101, 102]
+ # [100, 102]
+ # []
+ def each_container_with_default(&block)
+ @hash.each_value do |container|
+ iterate_over_container(container, &block)
+ end
+ iterate_over_container(default, &block)
+ self
+ end
+
+ # call-seq:
+ # multimap.containers_with_default => array
+ #
+ # Returns a new array populated with all the containers from
+ # <i>map</i> including the default.
+ #
+ # map = NestedMultimap.new
+ # map["a"] = 100
+ # map["a", "b"] = 101
+ # map["a"] = 102
+ # map.containers_with_default #=> [[100, 101, 102], [100, 102], []]
+ def containers_with_default
+ containers = []
+ each_container_with_default { |container| containers << container }
+ containers
+ end
+
+ def inspect #:nodoc:
+ super.gsub(/\}$/, ", default => #{default.inspect}}")
+ end
+
+ private
+ def iterate_over_container(container)
+ if container.respond_to?(:each_container_with_default)
+ container.each_container_with_default do |value|
+ yield value
+ end
+ else
+ yield container
+ end
+ end
+end
+
+begin
+ require 'nested_multimap_ext'
+rescue LoadError
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb
new file mode 100644
index 0000000000..d38922bcc6
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb
@@ -0,0 +1,45 @@
+module Regin
+ autoload :Alternation, 'regin/alternation'
+ autoload :Anchor, 'regin/anchor'
+ autoload :Atom, 'regin/atom'
+ autoload :Character, 'regin/character'
+ autoload :CharacterClass, 'regin/character_class'
+ autoload :Collection, 'regin/collection'
+ autoload :Expression, 'regin/expression'
+ autoload :Group, 'regin/group'
+ autoload :Options, 'regin/options'
+ autoload :Parser, 'regin/parser'
+
+ class << self
+ begin
+ eval('foo = /(?<foo>.*)/').named_captures
+
+ # Returns true if the interpreter is using the Oniguruma Regexp lib
+ # and supports named captures.
+ #
+ # /(?<foo>bar)/
+ def regexp_supports_named_captures?
+ true
+ end
+ rescue SyntaxError, NoMethodError
+ def regexp_supports_named_captures? #:nodoc:
+ false
+ end
+ end
+
+ # Parses Regexp and returns a Expression data structure.
+ def parse(regexp)
+ Parser.parse_regexp(regexp)
+ end
+
+ # Recompiles Regexp by parsing it and turning it back into a Regexp.
+ #
+ # (In the future Regin will perform some Regexp optimizations
+ # such as removing unnecessary captures and options)
+ def compile(source)
+ regexp = Regexp.compile(source)
+ expression = parse(regexp)
+ Regexp.compile(expression.to_s(true), expression.flags)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb
new file mode 100644
index 0000000000..ce4f52bfdb
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb
@@ -0,0 +1,40 @@
+module Regin
+ class Alternation < Collection
+ def initialize(*args)
+ args, options = extract_options(args)
+
+ if args.length == 1 && args.first.instance_of?(Array)
+ super(args.first)
+ else
+ super(args)
+ end
+
+ if options.key?(:ignorecase)
+ @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) }
+ end
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ #
+ # Alternation groups are never literal.
+ def literal?
+ false
+ end
+
+ def flags
+ 0
+ end
+
+ def dup(options = {})
+ self.class.new(to_a, options)
+ end
+
+ def to_s(parent = false)
+ map { |e| e.to_s(parent) }.join('|')
+ end
+
+ def inspect #:nodoc:
+ to_s.inspect
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb
new file mode 100644
index 0000000000..05520dd5e0
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb
@@ -0,0 +1,4 @@
+module Regin
+ class Anchor < Atom
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb
new file mode 100644
index 0000000000..eb1923a5a1
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb
@@ -0,0 +1,59 @@
+module Regin
+ class Atom
+ attr_reader :value, :ignorecase
+
+ def initialize(value, options = {})
+ @value = value
+ @ignorecase = options[:ignorecase]
+ end
+
+ def option_names
+ %w( ignorecase )
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ def literal?
+ false
+ end
+
+ def casefold?
+ ignorecase ? true : false
+ end
+
+ def dup(options = {})
+ original_options = option_names.inject({}) do |h, m|
+ h[m.to_sym] = send(m)
+ h
+ end
+ self.class.new(value, original_options.merge(options))
+ end
+
+ def to_s(parent = false)
+ "#{value}"
+ end
+
+ def inspect #:nodoc:
+ "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>"
+ end
+
+ def ==(other) #:nodoc:
+ case other
+ when String
+ other == to_s
+ else
+ eql?(other)
+ end
+ end
+
+ def eql?(other) #:nodoc:
+ other.instance_of?(self.class) &&
+ self.value.eql?(other.value) &&
+ (!!self.ignorecase).eql?(!!other.ignorecase)
+ end
+
+ def freeze #:nodoc:
+ value.freeze
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb
new file mode 100644
index 0000000000..12a9199d2a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb
@@ -0,0 +1,56 @@
+module Regin
+ class Character < Atom
+ attr_reader :quantifier
+
+ def initialize(value, options = {})
+ @quantifier = options[:quantifier]
+ super
+ end
+
+ def option_names
+ %w( quantifier ) + super
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ #
+ # A Character is literal is there is no quantifier attached to it.
+ def literal?
+ quantifier.nil? && !ignorecase
+ end
+
+ def to_s(parent = false)
+ if !parent && ignorecase
+ "(?i-mx:#{value})#{quantifier}"
+ else
+ "#{value}#{quantifier}"
+ end
+ end
+
+ def to_regexp(anchored = false)
+ re = to_s(true)
+ re = "\\A#{re}\\Z" if anchored
+ Regexp.compile(re, ignorecase)
+ end
+
+ def match(char)
+ to_regexp(true).match(char)
+ end
+
+ def include?(char)
+ if ignorecase
+ value.downcase == char.downcase
+ else
+ value == char
+ end
+ end
+
+ def eql?(other) #:nodoc:
+ super && quantifier.eql?(other.quantifier)
+ end
+
+ def freeze #:nodoc:
+ quantifier.freeze if quantifier
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb
new file mode 100644
index 0000000000..caed5ef9d0
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb
@@ -0,0 +1,55 @@
+module Regin
+ class CharacterClass < Character
+ def initialize(value, options = {})
+ @negate = options[:negate]
+ super
+ end
+
+ def option_names
+ %w( negate ) + super
+ end
+
+ attr_reader :negate
+
+ def negated?
+ negate ? true : false
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ #
+ # A CharacterClass is never literal.
+ def literal?
+ false
+ end
+
+ def bracketed?
+ value != '.' && value !~ /^\\[dDsSwW]$/
+ end
+
+ def to_s(parent = false)
+ if bracketed?
+ if !parent && ignorecase
+ "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}"
+ else
+ "[#{negate && '^'}#{value}]#{quantifier}"
+ end
+ else
+ super
+ end
+ end
+
+ def include?(char)
+ re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s
+ Regexp.compile("\\A#{re}\\Z").match(char)
+ end
+
+ def eql?(other) #:nodoc:
+ super && negate == other.negate
+ end
+
+ def freeze #:nodoc:
+ negate.freeze if negate
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb
new file mode 100644
index 0000000000..b60353268a
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb
@@ -0,0 +1,83 @@
+module Regin
+ class Collection
+ include Enumerable
+
+ def initialize(*args)
+ @array = Array.new(*args)
+ end
+
+ def each
+ @array.each{ |item| yield item }
+ end
+
+ def [](i)
+ @array[i]
+ end
+
+ def length
+ @array.length
+ end
+ alias_method :size, :length
+
+ def first
+ @array.first
+ end
+
+ def last
+ @array.last
+ end
+
+ def +(other)
+ ary = other.is_a?(self.class) ? other.internal_array : other
+ self.class.new(@array + ary)
+ end
+
+ def to_regexp(anchored = false)
+ re = to_s(true)
+ re = "\\A#{re}\\Z" if anchored
+ Regexp.compile(re, flags)
+ end
+
+ def match(char)
+ to_regexp.match(char)
+ end
+
+ def include?(char)
+ any? { |e| e.include?(char) }
+ end
+
+ def ==(other) #:nodoc:
+ case other
+ when String
+ other == to_s
+ when Array
+ other == @array
+ else
+ eql?(other)
+ end
+ end
+
+ def eql?(other) #:nodoc:
+ other.instance_of?(self.class) && @array.eql?(other.internal_array)
+ end
+
+ def freeze #:nodoc:
+ each { |e| e.freeze }
+ @array.freeze
+ super
+ end
+
+ protected
+ def internal_array #:nodoc:
+ @array
+ end
+
+ def extract_options(args)
+ if args.last.is_a?(Hash)
+ return args[0..-2], args.last
+ else
+ return args, {}
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb
new file mode 100644
index 0000000000..18e4965097
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb
@@ -0,0 +1,126 @@
+module Regin
+ class Expression < Collection
+ attr_reader :ignorecase, :multiline, :extended
+
+ def initialize(*args)
+ args, options = extract_options(args)
+
+ @multiline = @ignorecase = @extended = nil
+
+ if args.length == 1 && args.first.instance_of?(Array)
+ super(args.first)
+ else
+ args = args.map { |e| e.instance_of?(String) ? Character.new(e) : e }
+ super(args)
+ end
+
+ self.multiline = options[:multiline] if options.key?(:multiline)
+ self.ignorecase = options[:ignorecase] if options.key?(:ignorecase)
+ self.extended = options[:extended] if options.key?(:extended)
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ #
+ # A Expression is literal if all its elements are literal.
+ def literal?
+ !ignorecase && all? { |e| e.literal? }
+ end
+
+ def anchored?
+ anchored_to_start? && anchored_to_end?
+ end
+
+ def anchored_to_start?
+ first.is_a?(Anchor) && first == '\A'
+ end
+
+ def anchored_to_end?
+ last.is_a?(Anchor) && last == '\Z'
+ end
+
+ def anchored_to_line?
+ anchored_to_start_of_line? && anchored_to_end_of_line?
+ end
+
+ def anchored_to_start_of_line?
+ anchored_to_start? || (first.is_a?(Anchor) && first == '^')
+ end
+
+ def anchored_to_end_of_line?
+ anchored_to_end? || (last.is_a?(Anchor) && last == '$')
+ end
+
+ def options?
+ options.any?(true)
+ end
+
+ def flags
+ options.to_i
+ end
+
+ def +(other)
+ ary = other.is_a?(self.class) ? other.internal_array : other
+ ary = @array + ary + [options.to_h(true)]
+ self.class.new(*ary)
+ end
+
+ def dup(options = {})
+ expression = super()
+ expression.multiline = options[:multiline] if options.key?(:multiline)
+ expression.ignorecase = options[:ignorecase] if options.key?(:ignorecase)
+ expression.extended = options[:extended] if options.key?(:extended)
+ expression
+ end
+
+ def to_s(parent = false)
+ if parent || !options?
+ map { |e| e.to_s(parent) }.join
+ else
+ with, without = [], []
+ multiline ? (with << 'm') : (without << 'm')
+ ignorecase ? (with << 'i') : (without << 'i')
+ extended ? (with << 'x') : (without << 'x')
+
+ with = with.join
+ without = without.any? ? "-#{without.join}" : ''
+
+ "(?#{with}#{without}:#{map { |e| e.to_s(true) }.join})"
+ end
+ end
+
+ def inspect #:nodoc:
+ "#<Expression #{to_s.inspect}>"
+ end
+
+ def casefold?
+ ignorecase
+ end
+
+ def eql?(other) #:nodoc:
+ super &&
+ !!self.multiline == !!other.multiline &&
+ !!self.ignorecase == !!other.ignorecase &&
+ !!self.extended == !!other.extended
+ end
+
+ protected
+ def options
+ Options.new(multiline, ignorecase, extended)
+ end
+
+ def multiline=(multiline)
+ @multiline = multiline
+ end
+
+ def ignorecase=(ignorecase)
+ if @ignorecase.nil?
+ @array.map! { |e| e.dup(:ignorecase => ignorecase) }
+ @ignorecase = ignorecase
+ end
+ end
+
+ def extended=(extended)
+ @extended = extended
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb
new file mode 100644
index 0000000000..d682148bd9
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb
@@ -0,0 +1,90 @@
+module Regin
+ class Group
+ attr_reader :expression, :quantifier, :capture, :index, :name
+
+ def initialize(expression, options = {})
+ @quantifier = @index = @name = nil
+ @capture = true
+ @expression = expression.dup(options)
+
+ @quantifier = options[:quantifier] if options.key?(:quantifier)
+ @capture = options[:capture] if options.key?(:capture)
+ @index = options[:index] if options.key?(:index)
+ @name = options[:name] if options.key?(:name)
+ end
+
+ def option_names
+ %w( quantifier capture index name )
+ end
+
+ # Returns true if expression could be treated as a literal string.
+ #
+ # A Group is literal if its expression is literal and it has no quantifier.
+ def literal?
+ quantifier.nil? && expression.literal?
+ end
+
+ def to_s(parent = false)
+ if !expression.options?
+ "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}"
+ elsif capture == false
+ "#{expression.to_s}#{quantifier}"
+ else
+ "(#{expression.to_s})#{quantifier}"
+ end
+ end
+
+ def to_regexp(anchored = false)
+ re = to_s
+ re = "\\A#{re}\\Z" if anchored
+ Regexp.compile(re)
+ end
+
+ def dup(options = {})
+ original_options = option_names.inject({}) do |h, m|
+ h[m.to_sym] = send(m)
+ h
+ end
+ self.class.new(expression, original_options.merge(options))
+ end
+
+ def inspect #:nodoc:
+ to_s.inspect
+ end
+
+ def match(char)
+ to_regexp.match(char)
+ end
+
+ def include?(char)
+ expression.include?(char)
+ end
+
+ def capture?
+ capture
+ end
+
+ def ==(other) #:nodoc:
+ case other
+ when String
+ other == to_s
+ else
+ eql?(other)
+ end
+ end
+
+ def eql?(other) #:nodoc:
+ other.is_a?(self.class) &&
+ self.expression == other.expression &&
+ self.quantifier == other.quantifier &&
+ self.capture == other.capture &&
+ self.index == other.index &&
+ self.name == other.name
+ end
+
+ def freeze #:nodoc:
+ expression.freeze if expression
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb
new file mode 100644
index 0000000000..03ba29d9a5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb
@@ -0,0 +1,55 @@
+module Regin
+ class Options
+ def self.from_int(flags)
+ multiline = flags & Regexp::MULTILINE != 0
+ ignorecase = flags & Regexp::IGNORECASE != 0
+ extended = flags & Regexp::EXTENDED != 0
+
+ new(multiline, ignorecase, extended)
+ end
+
+ attr_reader :multiline, :ignorecase, :extended
+
+ def initialize(*args)
+ if args.first.is_a?(Hash)
+ @multiline = args[0][:multiline]
+ @ignorecase = args[0][:ignorecase]
+ @extended = args[0][:extended]
+ else
+ @multiline = args[0]
+ @ignorecase = args[1]
+ @extended = args[2]
+ end
+ end
+
+ def any?(explicit = false)
+ if explicit
+ !multiline.nil? || !ignorecase.nil? || !extended.nil?
+ else
+ multiline || ignorecase || extended
+ end
+ end
+
+ def to_h(explicit = false)
+ if explicit
+ options = {}
+ options[:multiline] = multiline unless multiline.nil?
+ options[:ignorecase] = ignorecase unless ignorecase.nil?
+ options[:extended] = extended unless extended.nil?
+ options
+ else
+ { :multiline => multiline,
+ :ignorecase => ignorecase,
+ :extended => extended }
+ end
+ end
+
+ def to_i
+ flag = 0
+ flag |= Regexp::MULTILINE if multiline
+ flag |= Regexp::IGNORECASE if ignorecase
+ flag |= Regexp::EXTENDED if extended
+ flag
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb
new file mode 100644
index 0000000000..0bb9b87e9c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb
@@ -0,0 +1,415 @@
+#
+# DO NOT MODIFY!!!!
+# This file is automatically generated by Racc 1.4.6
+# from Racc grammer file "".
+#
+
+require 'racc/parser.rb'
+module Regin
+ class Parser < Racc::Parser #:nodoc: all
+
+def self.parse_regexp(regexp)
+ options = Options.from_int(regexp.options)
+
+ parser = new
+ parser.options_stack << options.to_h
+
+ expression = parser.scan_str(regexp.source)
+ expression = expression.dup(options.to_h) if options.any?
+ expression
+end
+
+attr_accessor :options_stack
+
+def initialize
+ @capture_index = 0
+ @capture_index_stack = []
+ @options_stack = []
+end
+
+##### State transition tables begin ###
+
+racc_action_table = [
+ 2, 18, 19, 19, 8, 10, 11, 13, 48, 19,
+ 2, 45, 3, 5, 8, 10, 11, 13, 64, 47,
+ 2, 55, 3, 5, 8, 10, 11, 13, 29, 19,
+ 2, 16, 3, 5, 8, 10, 11, 13, 61, 19,
+ 2, 63, 3, 5, 8, 10, 11, 13, 60, 36,
+ 2, 34, 3, 5, 8, 10, 11, 13, 28, 49,
+ 2, nil, 3, 5, 8, 10, 11, 13, nil, nil,
+ 2, nil, 3, 5, 8, 10, 11, 13, nil, 26,
+ 42, 43, 3, 5, 37, 38, 40, 21, 44, 37,
+ 38, 40, 22, 23, 24, 14, nil, 15, 31, 32,
+ 16, nil, 33, 46, 32, nil, nil, 33, 51, 37,
+ 38, 40, 58, 37, 38, 40, 37, 38, 40, 37,
+ 38, 40, 37, 38, 40, 37, 38, 40, 37, 38,
+ 40 ]
+
+racc_action_check = [
+ 0, 4, 27, 4, 0, 0, 0, 0, 36, 56,
+ 49, 27, 0, 0, 49, 49, 49, 49, 56, 36,
+ 43, 48, 49, 49, 43, 43, 43, 43, 15, 53,
+ 6, 15, 43, 43, 6, 6, 6, 6, 53, 52,
+ 42, 55, 6, 6, 42, 42, 42, 42, 52, 24,
+ 35, 18, 42, 42, 35, 35, 35, 35, 14, 39,
+ 19, nil, 35, 35, 19, 19, 19, 19, nil, nil,
+ 13, nil, 19, 19, 13, 13, 13, 13, nil, 13,
+ 26, 26, 13, 13, 54, 54, 54, 9, 26, 26,
+ 26, 26, 9, 9, 9, 2, nil, 2, 17, 17,
+ 2, nil, 17, 30, 30, nil, nil, 30, 41, 41,
+ 41, 41, 50, 50, 50, 50, 44, 44, 44, 59,
+ 59, 59, 58, 58, 58, 51, 51, 51, 62, 62,
+ 62 ]
+
+racc_action_pointer = [
+ -3, nil, 91, nil, 1, nil, 27, nil, nil, 75,
+ nil, nil, nil, 67, 53, 22, nil, 93, 51, 57,
+ nil, nil, nil, nil, 40, nil, 67, 0, nil, nil,
+ 98, nil, nil, nil, nil, 47, -1, nil, nil, 46,
+ nil, 87, 37, 17, 94, nil, nil, nil, 12, 7,
+ 91, 103, 37, 27, 62, 21, 7, nil, 100, 97,
+ nil, nil, 106, nil, nil, nil, nil, nil ]
+
+racc_action_default = [
+ -37, -13, -37, -19, -37, -20, -2, -4, -11, -6,
+ -12, -14, -7, -37, -37, -29, -28, -37, -37, -37,
+ -3, -23, -21, -22, -37, -5, -37, -37, -8, -29,
+ -37, -9, -27, -26, 68, -1, -37, -34, -35, -37,
+ -36, -37, -37, -37, -37, -15, -10, -25, -37, -37,
+ -37, -37, -37, -37, -37, -37, -37, -33, -37, -37,
+ -17, -18, -37, -24, -16, -32, -31, -30 ]
+
+racc_goto_table = [
+ 4, 41, 20, 35, 17, 39, 25, nil, nil, nil,
+ nil, nil, nil, 27, nil, nil, 50, 30, nil, 54,
+ nil, nil, nil, nil, nil, 57, 59, nil, nil, 62,
+ nil, 20, nil, 65, 66, nil, nil, 67, nil, nil,
+ nil, nil, 52, 53, nil, nil, nil, nil, nil, 56 ]
+
+racc_goto_check = [
+ 1, 10, 3, 2, 7, 9, 5, nil, nil, nil,
+ nil, nil, nil, 1, nil, nil, 10, 7, nil, 10,
+ nil, nil, nil, nil, nil, 10, 10, nil, nil, 10,
+ nil, 3, nil, 10, 10, nil, nil, 10, nil, nil,
+ nil, nil, 1, 1, nil, nil, nil, nil, nil, 1 ]
+
+racc_goto_pointer = [
+ nil, 0, -16, -4, nil, -3, nil, 2, nil, -21,
+ -25 ]
+
+racc_goto_default = [
+ nil, nil, 6, 7, 9, nil, 12, nil, 1, nil,
+ nil ]
+
+racc_reduce_table = [
+ 0, 0, :racc_error,
+ 3, 26, :_reduce_1,
+ 1, 26, :_reduce_2,
+ 2, 27, :_reduce_3,
+ 1, 27, :_reduce_4,
+ 2, 28, :_reduce_5,
+ 1, 28, :_reduce_none,
+ 1, 29, :_reduce_none,
+ 3, 29, :_reduce_8,
+ 3, 29, :_reduce_9,
+ 4, 29, :_reduce_10,
+ 1, 29, :_reduce_11,
+ 1, 29, :_reduce_12,
+ 1, 29, :_reduce_13,
+ 1, 29, :_reduce_14,
+ 3, 31, :_reduce_15,
+ 6, 31, :_reduce_16,
+ 5, 31, :_reduce_17,
+ 5, 31, :_reduce_18,
+ 1, 33, :_reduce_none,
+ 1, 33, :_reduce_none,
+ 1, 30, :_reduce_none,
+ 1, 30, :_reduce_none,
+ 1, 30, :_reduce_none,
+ 5, 30, :_reduce_24,
+ 3, 30, :_reduce_25,
+ 2, 32, :_reduce_26,
+ 2, 32, :_reduce_27,
+ 1, 32, :_reduce_none,
+ 1, 32, :_reduce_none,
+ 4, 34, :_reduce_30,
+ 4, 34, :_reduce_31,
+ 4, 34, :_reduce_32,
+ 3, 34, :_reduce_33,
+ 1, 35, :_reduce_34,
+ 1, 35, :_reduce_35,
+ 1, 35, :_reduce_36 ]
+
+racc_reduce_n = 37
+
+racc_shift_n = 68
+
+racc_token_table = {
+ false => 0,
+ :error => 1,
+ :BAR => 2,
+ :LBRACK => 3,
+ :CTYPE => 4,
+ :RBRACK => 5,
+ :NEGATE => 6,
+ :CCLASS => 7,
+ :DOT => 8,
+ :CHAR => 9,
+ :LPAREN => 10,
+ :RPAREN => 11,
+ :QMARK => 12,
+ :COLON => 13,
+ :NAME => 14,
+ :L_ANCHOR => 15,
+ :R_ANCHOR => 16,
+ :STAR => 17,
+ :PLUS => 18,
+ :LCURLY => 19,
+ :RCURLY => 20,
+ :MINUS => 21,
+ :MULTILINE => 22,
+ :IGNORECASE => 23,
+ :EXTENDED => 24 }
+
+racc_nt_base = 25
+
+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",
+ "BAR",
+ "LBRACK",
+ "CTYPE",
+ "RBRACK",
+ "NEGATE",
+ "CCLASS",
+ "DOT",
+ "CHAR",
+ "LPAREN",
+ "RPAREN",
+ "QMARK",
+ "COLON",
+ "NAME",
+ "L_ANCHOR",
+ "R_ANCHOR",
+ "STAR",
+ "PLUS",
+ "LCURLY",
+ "RCURLY",
+ "MINUS",
+ "MULTILINE",
+ "IGNORECASE",
+ "EXTENDED",
+ "$start",
+ "expression",
+ "subexpression",
+ "quantified_atom",
+ "atom",
+ "quantifier",
+ "group",
+ "bracket_expression",
+ "anchor",
+ "options",
+ "modifier" ]
+
+Racc_debug_parser = false
+
+##### State transition tables end #####
+
+# reduce 0 omitted
+
+def _reduce_1(val, _values, result)
+ # TODO remove this conditional by breaking
+ # it into another production
+ if val[0][0].is_a?(Regin::Alternation)
+ alt = val[0][0] + [Expression.new(val[2])]
+ else
+ alt = Alternation.new(val[0], Expression.new(val[2]))
+ end
+ result = Expression.new(alt)
+
+ result
+end
+
+def _reduce_2(val, _values, result)
+ result = Expression.new(val[0])
+ result
+end
+
+def _reduce_3(val, _values, result)
+ result = val[0] + [val[1]]
+ result
+end
+
+def _reduce_4(val, _values, result)
+ result = [val[0]]
+ result
+end
+
+def _reduce_5(val, _values, result)
+ result = val[0].dup(:quantifier => val[1])
+ result
+end
+
+# reduce 6 omitted
+
+# reduce 7 omitted
+
+def _reduce_8(val, _values, result)
+ result = CharacterClass.new(val[1])
+ result
+end
+
+def _reduce_9(val, _values, result)
+ result = CharacterClass.new(val[1])
+ result
+end
+
+def _reduce_10(val, _values, result)
+ result = CharacterClass.new(val[2], :negate => true)
+ result
+end
+
+def _reduce_11(val, _values, result)
+ result = CharacterClass.new(val[0])
+ result
+end
+
+def _reduce_12(val, _values, result)
+ result = CharacterClass.new('.')
+ result
+end
+
+def _reduce_13(val, _values, result)
+ result = Anchor.new(val[0])
+ result
+end
+
+def _reduce_14(val, _values, result)
+ result = Character.new(val[0])
+ result
+end
+
+def _reduce_15(val, _values, result)
+ result = Group.new(val[1], :index => @capture_index_stack.pop)
+
+ result
+end
+
+def _reduce_16(val, _values, result)
+ result = Group.new(val[4], val[2].merge(:capture => false))
+ @options_stack.pop
+
+ result
+end
+
+def _reduce_17(val, _values, result)
+ result = Group.new(val[3], :capture => false);
+
+ result
+end
+
+def _reduce_18(val, _values, result)
+ result = Group.new(val[3], :name => val[2], :index => @capture_index_stack.pop);
+
+ result
+end
+
+# reduce 19 omitted
+
+# reduce 20 omitted
+
+# reduce 21 omitted
+
+# reduce 22 omitted
+
+# reduce 23 omitted
+
+def _reduce_24(val, _values, result)
+ result = val.join
+ result
+end
+
+def _reduce_25(val, _values, result)
+ result = val.join
+ result
+end
+
+def _reduce_26(val, _values, result)
+ result = val.join
+ result
+end
+
+def _reduce_27(val, _values, result)
+ result = val.join
+ result
+end
+
+# reduce 28 omitted
+
+# reduce 29 omitted
+
+def _reduce_30(val, _values, result)
+ @options_stack << result = { val[1] => false, val[2] => false, val[3] => false }
+
+ result
+end
+
+def _reduce_31(val, _values, result)
+ @options_stack << result = { val[0] => true, val[2] => false, val[3] => false }
+
+ result
+end
+
+def _reduce_32(val, _values, result)
+ @options_stack << result = { val[0] => true, val[1] => true, val[3] => false }
+
+ result
+end
+
+def _reduce_33(val, _values, result)
+ @options_stack << result = { val[0] => true, val[1] => true, val[2] => true }
+
+ result
+end
+
+def _reduce_34(val, _values, result)
+ result = :multiline
+ result
+end
+
+def _reduce_35(val, _values, result)
+ result = :ignorecase
+ result
+end
+
+def _reduce_36(val, _values, result)
+ result = :extended
+ result
+end
+
+def _reduce_none(val, _values, result)
+ val[0]
+end
+
+ end # class Parser
+end # module Regin
+
+require 'regin/tokenizer'
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb
new file mode 100644
index 0000000000..59e4ffb611
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb
@@ -0,0 +1,213 @@
+#--
+# DO NOT MODIFY!!!!
+# This file is automatically generated by rex 1.0.5.beta1
+# from lexical definition file "lib/regin/tokenizer.rex".
+#++
+
+require 'racc/parser'
+class Regin::Parser < Racc::Parser
+ require 'strscan'
+
+ class ScanError < StandardError ; end
+
+ attr_reader :lineno
+ attr_reader :filename
+ attr_accessor :state
+
+ def scan_setup(str)
+ @ss = StringScanner.new(str)
+ @lineno = 1
+ @state = nil
+ end
+
+ def action
+ yield
+ end
+
+ def scan_str(str)
+ scan_setup(str)
+ do_parse
+ end
+ alias :scan :scan_str
+
+ def load_file( filename )
+ @filename = filename
+ open(filename, "r") do |f|
+ scan_setup(f.read)
+ end
+ end
+
+ def scan_file( filename )
+ load_file(filename)
+ do_parse
+ end
+
+
+ def next_token
+ return if @ss.eos?
+
+ text = @ss.peek(1)
+ @lineno += 1 if text == "\n"
+ token = case @state
+ when nil
+ case
+ when (text = @ss.scan(/\\[dDsSwW]/))
+ action { [:CCLASS, text] }
+
+ when (text = @ss.scan(/\^|\\A/))
+ action { [:L_ANCHOR, text] }
+
+ when (text = @ss.scan(/\$|\\Z/))
+ action { [:R_ANCHOR, text] }
+
+ when (text = @ss.scan(/<(\w+)>/))
+ action { [:NAME, @ss[1]] }
+
+ when (text = @ss.scan(/\(/))
+ action {
+ @capture_index_stack << @capture_index
+ @capture_index += 1
+ @state = :OPTIONS if @ss.peek(1) == '?';
+ [:LPAREN, text]
+ }
+
+
+ when (text = @ss.scan(/\)/))
+ action { [:RPAREN, text] }
+
+ when (text = @ss.scan(/\[/))
+ action { @state = :CCLASS; [:LBRACK, text] }
+
+ when (text = @ss.scan(/\{/))
+ action { [:LCURLY, text] }
+
+ when (text = @ss.scan(/\}/))
+ action { [:RCURLY, text] }
+
+ when (text = @ss.scan(/\|/))
+ action { [:BAR, text] }
+
+ when (text = @ss.scan(/\./))
+ action { [:DOT, text] }
+
+ when (text = @ss.scan(/\?/))
+ action { [:QMARK, text] }
+
+ when (text = @ss.scan(/\+(?:\?)/))
+ action { [:PLUS, text] }
+
+ when (text = @ss.scan(/\*(?:\?)/))
+ action { [:STAR, text] }
+
+ when (text = @ss.scan(/\#/))
+ action {
+ if @options_stack[-1][:extended]
+ @state = :COMMENT;
+ next_token
+ else
+ [:CHAR, text]
+ end
+ }
+
+
+ when (text = @ss.scan(/\s|\n/))
+ action {
+ if @options_stack[-1][:extended]
+ next_token
+ else
+ [:CHAR, text]
+ end
+ }
+
+
+ when (text = @ss.scan(/\\(.)/))
+ action { [:CHAR, @ss[1]] }
+
+ when (text = @ss.scan(/./))
+ action { [:CHAR, text] }
+
+ else
+ text = @ss.string[@ss.pos .. -1]
+ raise ScanError, "can not match: '" + text + "'"
+ end # if
+
+ when :CCLASS
+ case
+ when (text = @ss.scan(/\]/))
+ action { @state = nil; [:RBRACK, text] }
+
+ when (text = @ss.scan(/\^/))
+ action { [:NEGATE, text] }
+
+ when (text = @ss.scan(/:(alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit):/))
+ action { [:CTYPE, text] }
+
+ when (text = @ss.scan(/\\-/))
+ action { [:CHAR, text] }
+
+ when (text = @ss.scan(/\\(.)/))
+ action { [:CHAR, @ss[1]] }
+
+ when (text = @ss.scan(/./))
+ action { [:CHAR, text] }
+
+ else
+ text = @ss.string[@ss.pos .. -1]
+ raise ScanError, "can not match: '" + text + "'"
+ end # if
+
+ when :OPTIONS
+ case
+ when (text = @ss.scan(/\?/))
+ action {
+ @state = nil unless @ss.peek(1) =~ /-|m|i|x|:/
+ [:QMARK, text]
+ }
+
+
+ when (text = @ss.scan(/\-/))
+ action { [:MINUS, text] }
+
+ when (text = @ss.scan(/m/))
+ action { [:MULTILINE, text] }
+
+ when (text = @ss.scan(/i/))
+ action { [:IGNORECASE, text] }
+
+ when (text = @ss.scan(/x/))
+ action { [:EXTENDED, text] }
+
+ when (text = @ss.scan(/\:/))
+ action {
+ @capture_index_stack.pop
+ @capture_index -= 1
+ @state = nil;
+ [:COLON, text]
+ }
+
+
+ else
+ text = @ss.string[@ss.pos .. -1]
+ raise ScanError, "can not match: '" + text + "'"
+ end # if
+
+ when :COMMENT
+ case
+ when (text = @ss.scan(/\n/))
+ action { @state = nil; next_token }
+
+ when (text = @ss.scan(/./))
+ action { next_token }
+
+ else
+ text = @ss.string[@ss.pos .. -1]
+ raise ScanError, "can not match: '" + text + "'"
+ end # if
+
+ else
+ raise ScanError, "undefined state: '" + state.to_s + "'"
+ end # case state
+ token
+ end # def next_token
+
+end # class
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb
new file mode 100644
index 0000000000..7ad2a5a25e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb
@@ -0,0 +1,3 @@
+module Regin
+ Version = '0.3.3'
+end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb
new file mode 100644
index 0000000000..a3688b102e
--- /dev/null
+++ b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb
@@ -0,0 +1,5 @@
+module Rack
+ module Mount
+ Version = '0.6.6.pre'
+ end
+end
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 4d06ca0d89..956c88a553 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -3,16 +3,19 @@ require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/array/wrap'
require 'active_support/ordered_options'
+require 'action_view/log_subscriber'
module ActionView #:nodoc:
class NonConcattingString < ActiveSupport::SafeBuffer
end
+ # = Action View Base
+ #
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
# If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
- # = ERb
+ # == ERb
#
# You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
@@ -32,7 +35,7 @@ module ActionView #:nodoc:
#
# <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>.
#
- # == Using sub templates
+ # === Using sub templates
#
# Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The
# classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts):
@@ -54,7 +57,7 @@ module ActionView #:nodoc:
#
# <title><%= @page_title %></title>
#
- # == Passing local variables to sub templates
+ # === Passing local variables to sub templates
#
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
#
@@ -74,7 +77,7 @@ module ActionView #:nodoc:
#
# Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction.
#
- # == Template caching
+ # === Template caching
#
# By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will
# check the file's modification time and recompile it.
@@ -202,8 +205,12 @@ module ActionView #:nodoc:
value.dup : ActionView::PathSet.new(Array.wrap(value))
end
+ def assign(new_assigns) # :nodoc:
+ self.assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
+ end
+
def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
- self.assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
+ assign(assigns_for_first_render)
self.helpers = self.class.helpers || Module.new
if @_controller = controller
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index 88efd4b34f..39d88333e8 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -2,13 +2,12 @@ module ActionView
module CompiledTemplates #:nodoc:
# holds compiled template code
end
-
- # Action View contexts are supplied to Action Controller
- # to render template. The default Action View context
- # is ActionView::Base.
+ # = Action View Context
+ #
+ # Action View contexts are supplied to Action Controller to render template.
+ # The default Action View context is ActionView::Base.
#
- # In order to work with ActionController, a Context
- # must implement:
+ # In order to work with ActionController, a Context must implement:
#
# Context#render_partial[options]
# - responsible for setting options[:_template]
@@ -21,16 +20,14 @@ module ActionView
# options<Hash>:: See _render_template_with_layout in ActionView::Base
# partial<Boolean>:: Whether or not the template to render is a partial
#
- # An Action View context can also mix in Action View's
- # helpers. In order to mix in helpers, a context must
- # implement:
+ # An Action View context can also mix in Action View's helpers. In order to
+ # mix in helpers, a context must implement:
#
# Context#controller
# - Returns an instance of AbstractController
#
- # In any case, a context must mix in ActionView::Context,
- # which stores compiled template and provides the output
- # buffer.
+ # In any case, a context must mix in ActionView::Context, which stores compiled
+ # template and provides the output buffer.
module Context
include CompiledTemplates
attr_accessor :output_buffer
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 8054de0af6..6bb0875bc3 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Active Model Helpers
module Helpers
module ActiveModelHelper
%w(input form error_messages_for error_message_on).each do |method|
@@ -35,12 +36,16 @@ module ActionView
end
end
- %w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
+ %w(content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
module_eval "def #{meth}(*) error_wrapping(super) end", __FILE__, __LINE__
end
+ def tag(type, options, *)
+ tag_generate_errors?(options) ? error_wrapping(super) : super
+ end
+
def error_wrapping(html_tag)
- if object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && object.errors[@method_name].any?
+ if object_has_errors?
Base.field_error_proc.call(html_tag, self)
else
html_tag
@@ -50,6 +55,16 @@ module ActionView
def error_message
object.errors[@method_name]
end
+
+ private
+
+ def object_has_errors?
+ object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any?
+ end
+
+ def tag_generate_errors?(options)
+ options['type'] != 'hidden'
+ end
end
class FormBuilder
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 25426a5547..a3c43d3e93 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -6,6 +6,7 @@ require 'active_support/core_ext/file'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Asset Tag Helpers
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
@@ -193,7 +194,19 @@ module ActionView
# RewriteEngine On
# RewriteRule ^/release-\d+/(images|javascripts|stylesheets)/(.*)$ /$1/$2 [L]
module AssetTagHelper
- JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls', 'rails'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
+ mattr_reader :javascript_expansions
+ @@javascript_expansions = { }
+
+ mattr_reader :stylesheet_expansions
+ @@stylesheet_expansions = {}
+
+ # You can enable or disable the asset tag timestamps cache.
+ # With the cache enabled, the asset tag helper methods will make fewer
+ # expensive file system calls. However this prevents you from modifying
+ # any asset files while the server is running.
+ #
+ # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
+ mattr_accessor :cache_asset_timestamps
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
@@ -350,8 +363,6 @@ module ActionView
end
end
- @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
-
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@@ -367,8 +378,6 @@ module ActionView
@@javascript_expansions.merge!(expansions)
end
- @@stylesheet_expansions = {}
-
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@@ -384,18 +393,6 @@ module ActionView
@@stylesheet_expansions.merge!(expansions)
end
- # Register one or more additional JavaScript files to be included when
- # <tt>javascript_include_tag :defaults</tt> is called. This method is
- # typically intended to be called from plugin initialization to register additional
- # .js files that the plugin installed in <tt>public/javascripts</tt>.
- def self.register_javascript_include_default(*sources)
- @@javascript_expansions[:defaults].concat(sources)
- end
-
- def self.reset_javascript_include_default #:nodoc:
- @@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
- end
-
# Computes the path to a stylesheet asset in the public stylesheets directory.
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
@@ -706,23 +703,8 @@ module ActionView
tag("audio", options)
end
- def self.cache_asset_timestamps
- @@cache_asset_timestamps
- end
-
- # You can enable or disable the asset tag timestamps cache.
- # With the cache enabled, the asset tag helper methods will make fewer
- # expensive file system calls. However this prevents you from modifying
- # any asset files while the server is running.
- #
- # ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
- def self.cache_asset_timestamps=(value)
- @@cache_asset_timestamps = value
- end
-
- @@cache_asset_timestamps = true
-
private
+
def rewrite_extension?(source, dir, ext)
source_ext = File.extname(source)[1..-1]
ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index 52806f206a..cb5a1404ff 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -1,10 +1,12 @@
require 'set'
-# Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERb or any other
-# template languages).
module ActionView
+ # = Action View Atom Feed Helpers
module Helpers #:nodoc:
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).
+ #
# Full usage example:
#
# config/routes.rb:
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 8251ed18f4..f544a9d147 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -1,8 +1,10 @@
module ActionView
+ # = Action View Cache Helper
module Helpers
- # This helper to exposes a method for caching of view fragments.
- # See ActionController::Caching::Fragments for usage instructions.
module CacheHelper
+ # This helper to exposes a method for caching of view fragments.
+ # See ActionController::Caching::Fragments for usage instructions.
+ #
# A method for caching fragments of a view rather than an entire
# action or page. This technique is useful caching pieces like
# menus, lists of news topics, static HTML fragments, and so on.
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index edc6afc622..f9105ca364 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -1,9 +1,11 @@
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Capture Helper
module Helpers
# CaptureHelper exposes methods to let you extract generated markup which
# can be used in other parts of a template or layout file.
+ #
# It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through content_for.
module CaptureHelper
@@ -176,9 +178,7 @@ module ActionView
def flush_output_buffer #:nodoc:
if output_buffer && !output_buffer.empty?
response.body_parts << output_buffer
- new = ''
- new.force_encoding(output_buffer.encoding) if new.respond_to?(:force_encoding)
- self.output_buffer = new
+ self.output_buffer = output_buffer[0,0]
nil
end
end
diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
index 41c6b67f91..3d03f6aac6 100644
--- a/actionpack/lib/action_view/helpers/csrf_helper.rb
+++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
@@ -1,7 +1,9 @@
module ActionView
+ # = Action View CSRF Helper
module Helpers
module CsrfHelper
- # Returns a meta tag with the request forgery protection token for forms to use. Put this in your head.
+ # Returns a meta tag with the cross-site request forgery protection token
+ # for forms to use. Place this in your head.
def csrf_meta_tag
if protect_against_forgery?
%(<meta name="csrf-param" content="#{Rack::Utils.escape_html(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{Rack::Utils.escape_html(form_authenticity_token)}"/>).html_safe
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 7d846a01dd..f097b9a5a3 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -4,6 +4,8 @@ require 'active_support/core_ext/hash/slice'
module ActionView
module Helpers
+ # = Action View Date Helpers
+ #
# The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the
# select-type methods share a number of common options that are as follows:
#
@@ -580,10 +582,10 @@ module ActionView
extend ActiveSupport::Memoizable
include ActionView::Helpers::TagHelper
- DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX')
+ DEFAULT_PREFIX = 'date'.freeze
POSITION = {
:year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
- }.freeze unless const_defined?('POSITION')
+ }.freeze
def initialize(datetime, options = {}, html_options = {})
@options = options.dup
@@ -894,8 +896,10 @@ module ActionView
# Returns the separator for a given datetime component
def separator(type)
case type
- when :month, :day
- @options[:date_separator]
+ when :month
+ @options[:discard_month] ? "" : @options[:date_separator]
+ when :day
+ @options[:discard_day] ? "" : @options[:date_separator]
when :hour
(@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
when :minute
diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb
index e637dc1474..1491cb073f 100644
--- a/actionpack/lib/action_view/helpers/debug_helper.rb
+++ b/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -1,6 +1,8 @@
module ActionView
+ # = Action View Debug Helper
+ #
+ # Provides a set of methods for making it easier to debug Rails objects.
module Helpers
- # Provides a set of methods for making it easier to debug Rails objects.
module DebugHelper
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
# If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead.
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index b3db3151d3..d1b10a9281 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -7,6 +7,7 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Form Helpers
module Helpers
# Form helpers are designed to make working with resources much easier
# compared to using vanilla HTML.
@@ -92,7 +93,7 @@ module ActionView
# # error handling
# end
#
- # That's how you tipically work with resources.
+ # That's how you typically work with resources.
module FormHelper
extend ActiveSupport::Concern
@@ -123,7 +124,6 @@ module ActionView
# model:
#
# <%= form_for :person do |f| %>
- # <%= f.error_messages %>
# First name: <%= f.text_field :first_name %><br />
# Last name : <%= f.text_field :last_name %><br />
# Biography : <%= f.text_area :biography %><br />
@@ -220,15 +220,15 @@ module ActionView
# <% end %>
#
# === Unobtrusive JavaScript
- #
- # Specifying:
- #
+ #
+ # Specifying:
+ #
# :remote => true
#
# in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
- # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular
+ # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular
# POST arrangement, but ultimately the behaviour is the choice of the JavaScript driver implementor.
- # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
+ # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
# a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
#
# Example:
@@ -269,7 +269,7 @@ module ActionView
# <tt>labelling_form</tt>.
#
# The custom FormBuilder class is automatically merged with the options
- # of a nested fields_for call, unless it's explicitely set.
+ # of a nested fields_for call, unless it's explicitly set.
#
# In many cases you will want to wrap the above in another helper, so you
# could do something like the following:
@@ -302,7 +302,7 @@ module ActionView
args.unshift object
end
- options[:html][:remote] = true if options.delete(:remote)
+ (options[:html] ||= {})[:remote] = true if options.delete(:remote)
output = form_tag(options.delete(:url) || {}, options.delete(:html) || {})
output << fields_for(object_name, *(args << options), &proc)
@@ -717,7 +717,7 @@ module ActionView
#
# 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 mimick an unchecked check box.
+ # 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
@@ -838,9 +838,9 @@ module ActionView
attr_reader :method_name, :object_name
- DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
- DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
- DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze
+ DEFAULT_RADIO_OPTIONS = { }.freeze
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze
def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
@@ -897,7 +897,7 @@ module ActionView
options.delete("size")
end
options["type"] ||= field_type
- options["value"] ||= value_before_type_cast(object) unless field_type == "file"
+ options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
options["value"] &&= html_escape(options["value"])
add_default_name_and_id(options)
tag("input", options)
@@ -1030,7 +1030,7 @@ module ActionView
private
def add_default_name_and_id_for_value(tag_value, options)
unless tag_value.nil?
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
specified_id = options["id"]
add_default_name_and_id(options)
options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
@@ -1042,14 +1042,14 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"])
- options["id"] = options.fetch("id", tag_id_with_index(options["index"]))
+ options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
options.delete("index")
elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index)
- options["id"] = options.fetch("id", tag_id_with_index(@auto_index))
+ options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
else
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
- options["id"] = options.fetch("id", tag_id)
+ options["id"] = options.fetch("id"){ tag_id }
end
end
@@ -1180,7 +1180,7 @@ module ActionView
# <%= form_for @post do |f| %>
# <%= f.submit %>
# <% end %>
- #
+ #
# In the example above, if @post is a new record, it will use "Create Post" as
# submit button label, otherwise, it uses "Update Post".
#
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index fe71d2cdf7..6f9d14de8b 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -4,6 +4,7 @@ require 'action_view/helpers/form_helper'
require 'active_support/core_ext/object/blank'
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
@@ -398,7 +399,7 @@ module ActionView
options_for_select += "<optgroup label=\"#{html_escape(group_label_string)}\">"
options_for_select += options_from_collection_for_select(eval("group.#{group_method}"), option_key_method, option_value_method, selected_key)
options_for_select += '</optgroup>'
- end
+ end.html_safe
end
# Returns a string of <tt><option></tt> tags, like <tt>options_for_select</tt>, but
@@ -412,8 +413,8 @@ module ActionView
# * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags,
# which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options
# as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>.
- # * +prompt+ - 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.
+ # * +prompt+ - 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.
#
# Sample usage (Array):
# grouped_options = [
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 796268628a..4c1b751160 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Form Tag Helpers
module Helpers
# Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually.
@@ -528,23 +529,34 @@ module ActionView
def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
+ # The following URL is unescaped, this is just a hash of options, and it is the
+ # responsability of the caller to escape all the values.
html_options["action"] = url_for(url_for_options, *parameters_for_url)
+ html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
end
end
def extra_tags_for_form(html_options)
- case method = html_options.delete("method").to_s
- when /^get$/i # must be case-insentive, but can't use downcase as might be nil
+ snowman_tag = tag(:input, :type => "hidden",
+ :name => "_snowman", :value => "&#9731;".html_safe)
+
+ method = html_options.delete("method").to_s
+
+ method_tag = case method
+ when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
html_options["method"] = "get"
''
when /^post$/i, "", nil
html_options["method"] = "post"
- protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : ''
+ token_tag
else
html_options["method"] = "post"
- content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline')
+ tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
end
+
+ tags = snowman_tag << method_tag
+ content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline')
end
def form_tag_html(html_options)
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index b0a7718f22..84f53b842c 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,6 +1,7 @@
require 'action_view/helpers/tag_helper'
module ActionView
+ # = Action View JavaScript Helpers
module Helpers
# Provides functionality for working with JavaScript in your views.
#
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 3b0dfb561c..37e5d91d8b 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/float/rounding'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Number Helpers
module Helpers #:nodoc:
# Provides methods for converting numbers into formatted strings.
@@ -332,7 +333,7 @@ module ActionView
# number_to_human_size(483989, :precision => 2) # => 470 KB
# number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
#
- # Unsignificant zeros after the fractional separator are stripped out by default (set
+ # 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"
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index a798c3eaef..5c0ff5d59c 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -1,9 +1,9 @@
require 'set'
require 'active_support/json'
-require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Prototype Helpers
module Helpers
# Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
# DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
@@ -94,14 +94,12 @@ module ActionView
# See JavaScriptGenerator for information on updating multiple elements
# on the page in an Ajax response.
module PrototypeHelper
- unless const_defined? :CALLBACKS
- CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
- :interactive, :complete, :failure, :success ] +
- (100..599).to_a)
- AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
- :asynchronous, :method, :insertion, :position,
- :form, :with, :update, :script, :type ]).merge(CALLBACKS)
- end
+ CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded,
+ :interactive, :complete, :failure, :success ] +
+ (100..599).to_a)
+ AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url,
+ :asynchronous, :method, :insertion, :position,
+ :form, :with, :update, :script, :type ]).merge(CALLBACKS)
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
@@ -133,7 +131,7 @@ module ActionView
url_options = options[:url]
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
- function << "'#{escape_javascript(url_for(url_options))}'"
+ function << "'#{html_escape(escape_javascript(url_for(url_options)))}'"
function << ", #{javascript_options})"
function = "#{options[:before]}; #{function}" if options[:before]
@@ -229,7 +227,7 @@ module ActionView
# <script> tag.
module GeneratorMethods
def to_s #:nodoc:
- returning javascript = @lines * $/ do
+ (@lines * $/).tap do |javascript|
if ActionView::Base.debug_rjs
source = javascript.dup
javascript.replace "try {\n#{source}\n} catch (e) "
@@ -531,9 +529,9 @@ module ActionView
end
def record(line)
- returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
- self << line
- end
+ line = "#{line.to_s.chomp.gsub(/\;\z/, '')};"
+ self << line
+ line
end
def render(*options)
diff --git a/actionpack/lib/action_view/helpers/raw_output_helper.rb b/actionpack/lib/action_view/helpers/raw_output_helper.rb
index 8c7f41177d..da7599fa8f 100644
--- a/actionpack/lib/action_view/helpers/raw_output_helper.rb
+++ b/actionpack/lib/action_view/helpers/raw_output_helper.rb
@@ -1,6 +1,15 @@
module ActionView #:nodoc:
+ # = Action View Raw Output Helper
module Helpers #:nodoc:
module RawOutputHelper
+ # This method outputs without escaping a string. Since escaping tags is
+ # now default, this can be used when you don't want Rails to automatically
+ # escape tags. This is not recommended if the data is coming from the user's
+ # input.
+ #
+ # For example:
+ #
+ # <%=raw @user.name %>
def raw(stringish)
stringish.to_s.html_safe
end
diff --git a/actionpack/lib/action_view/helpers/record_identification_helper.rb b/actionpack/lib/action_view/helpers/record_identification_helper.rb
index 6c235bff3d..372f1cb8aa 100644
--- a/actionpack/lib/action_view/helpers/record_identification_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_identification_helper.rb
@@ -1,4 +1,7 @@
module ActionView
+ # = Action View Record Identification Helpers
+ #
+ # See ActionController::RecordIdentifier for documentation on these methods.
module Helpers
module RecordIdentificationHelper
# See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view.
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index a9cf15f418..7433f08777 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -1,4 +1,5 @@
module ActionView
+ # = Action View Record Tag Helpers
module Helpers
module RecordTagHelper
# Produces a wrapper DIV element with id and class parameters that
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index f173523f6a..b47818a22a 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -2,19 +2,25 @@ require 'action_controller/vendor/html-scanner'
require 'action_view/helpers/tag_helper'
module ActionView
+ # = Action View Sanitize Helpers
module Helpers #:nodoc:
# 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
- # This +sanitize+ helper will html encode all tags and strip all attributes that aren't specifically allowed.
- # It also strips href/src tags with invalid protocols, like javascript: especially. It does its best to counter any
- # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters. Check out
+ # This +sanitize+ helper will html encode all tags and strip all attributes that
+ # aren't specifically allowed.
+ #
+ # It also strips href/src tags with invalid protocols, like javascript: especially.
+ # It does its best to counter any tricks that hackers may use, like throwing in
+ # unicode/ascii/hex values to get past the javascript: filters. Check out
# the extensive test suite.
#
# <%= sanitize @article.body %>
#
- # You can add or remove tags/attributes if you want to customize it a bit. See ActionView::Base for full docs on the
- # available options. You can add tags/attributes for single uses of +sanitize+ by passing either the <tt>:attributes</tt> or <tt>:tags</tt> options:
+ # You can add or remove tags/attributes if you want to customize it a bit.
+ # See ActionView::Base for full docs on the available options. You can add
+ # tags/attributes for single uses of +sanitize+ by passing either the
+ # <tt>:attributes</tt> or <tt>:tags</tt> options:
#
# Normal Use
#
diff --git a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
index 37319cca1b..8610c2469e 100644
--- a/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
+++ b/actionpack/lib/action_view/helpers/scriptaculous_helper.rb
@@ -2,9 +2,11 @@ require 'action_view/helpers/javascript_helper'
require 'active_support/json'
module ActionView
+ # = Action View Scriptaculous Helpers
module Helpers
- # Provides a set of helpers for calling Scriptaculous JavaScript
- # functions, including those which create Ajax controls and visual effects.
+ # Provides a set of helpers for calling Scriptaculous[http://script.aculo.us/]
+ # JavaScript functions, including those which create Ajax controls and visual
+ # effects.
#
# To be able to use these helpers, you must include the Prototype
# JavaScript framework and the Scriptaculous JavaScript library in your
@@ -12,12 +14,11 @@ module ActionView
# for more information on including the necessary JavaScript.
#
# The Scriptaculous helpers' behavior can be tweaked with various options.
+ #
# See the documentation at http://script.aculo.us for more information on
# using these helpers in your application.
module ScriptaculousHelper
- unless const_defined? :TOGGLE_EFFECTS
- TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
- end
+ TOGGLE_EFFECTS = [:toggle_appear, :toggle_slide, :toggle_blind]
# Returns a JavaScript snippet to be used on the Ajax callbacks for
# starting visual effects.
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 66277f79fe..5d032b32a7 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -2,6 +2,7 @@ require 'active_support/core_ext/object/blank'
require 'set'
module ActionView
+ # = Action View Tag Helpers
module Helpers #:nodoc:
# Provides methods to generate HTML tags programmatically when you can't use
# a Builder. By default, they output XHTML compliant tags.
@@ -121,7 +122,7 @@ module ActionView
attrs << %(#{key}="#{key}") if value
elsif !value.nil?
final_value = value.is_a?(Array) ? value.join(" ") : value
- final_value = escape_once(final_value) if escape
+ final_value = html_escape(final_value) if escape
attrs << %(#{key}="#{final_value}")
end
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index c7f96597b9..0be8a2c36e 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/string/filters'
require 'action_view/helpers/tag_helper'
module ActionView
+ # = Action View Text Helpers
module Helpers #:nodoc:
# The TextHelper module provides a set of methods for filtering, formatting
# and transforming strings, which can reduce the amount of inline Ruby code in
@@ -52,7 +53,7 @@ module ActionView
# truncate("Once upon a time in a world far far away", :length => 17)
# # => "Once upon a ti..."
#
- # truncate("Once upon a time in a world far far away", :lenght => 17, :separator => ' ')
+ # truncate("Once upon a time in a world far far away", :length => 17, :separator => ' ')
# # => "Once upon a..."
#
# truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)')
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 0d2b2aa7b1..dac9c28ab7 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -1,21 +1,28 @@
require 'action_view/helpers/tag_helper'
module ActionView
+ # = Action View Translation Helpers
module Helpers
module TranslationHelper
- # Delegates to I18n#translate but also performs three additional functions. First, it'll catch MissingTranslationData exceptions
- # and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where.
+ # Delegates to I18n#translate but also performs three additional functions.
+ # First, it'll catch MissingTranslationData exceptions and turn them into
+ # inline spans that contains the missing key, such that you can see in a
+ # view what is missing where.
#
- # Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the
- # people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive
- # to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't
- # prepend the key with a period, nothing is converted.
+ # Second, it'll scope the key by the current partial if the key starts
+ # with a period. So if you call <tt>translate(".foo")</tt> from the
+ # <tt>people/index.html.erb</tt> template, you'll actually be calling
+ # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
+ # to translate many keys within the same partials and gives you a simple framework
+ # for scoping them consistently. If you don't prepend the key with a period,
+ # nothing is converted.
#
- # Third, it’ll mark the translation as safe HTML if the key has the suffix "_html" or the last element of the key is the word
- # "html". For example, calling translate("footer_html") or translate("footer.html") will return a safe HTML string that won’t
- # be escaped by other HTML helper methods. This naming convention helps to identify translations that include HTML tags so that
+ # Third, it'll mark the translation as safe HTML if the key has the suffix
+ # "_html" or the last element of the key is the word "html". For example,
+ # calling translate("footer_html") or translate("footer.html") will return
+ # a safe HTML string that won't be escaped by other HTML helper methods. This
+ # naming convention helps to identify translations that include HTML tags so that
# you know what kind of output to expect when you call translate in a template.
-
def translate(key, options = {})
translation = I18n.translate(scope_key_by_partial(key), options.merge!(:raise => true))
if html_safe_translation_key?(key) && translation.respond_to?(:html_safe)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 210f148c02..b8d6dc22f2 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/keys'
require 'action_dispatch'
module ActionView
+ # = Action View URL Helpers
module Helpers #:nodoc:
# Provides a set of methods for making links and getting URLs that
# depend on the routing subsystem (see ActionDispatch::Routing).
@@ -12,7 +13,7 @@ module ActionView
module UrlHelper
# This helper may be included in any class that includes the
# URL helpers of a router (router.url_helpers). Some methods
- # provided here will only work in the context of a request
+ # provided here will only work in the4 context of a request
# (link_to_unless_current, for instance), which must be provided
# as a method called #request on the context.
@@ -37,9 +38,6 @@ module ActionView
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action"
# instead of the fully qualified URL like "http://example.com/controller/action".
#
- # When called from a view, +url_for+ returns an HTML escaped url. If you
- # need an unescaped url, pass <tt>:escape => false</tt> in the +options+.
- #
# ==== Options
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
@@ -49,7 +47,6 @@ module ActionView
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
- # * <tt>:escape</tt> - Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default).
#
# ==== Relying on named routes
#
@@ -71,10 +68,7 @@ module ActionView
# <%= url_for(:action => 'play', :anchor => 'player') %>
# # => /messages/play/#player
#
- # <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %>
- # # => /testing/jump/#tax&amp;ship
- #
- # <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %>
+ # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %>
# # => /testing/jump/#tax&ship
#
# <%= url_for(Workshop.new) %>
@@ -99,21 +93,17 @@ module ActionView
options ||= {}
url = case options
when String
- escape = true
options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
- escape = options.key?(:escape) ? options.delete(:escape) : false
super
when :back
- escape = false
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
- escape = false
polymorphic_path(options)
end
- escape ? escape_once(url).html_safe : url
+ url
end
# Creates a link tag of the given +name+ using a URL created by the set
@@ -253,8 +243,8 @@ module ActionView
tag_options = nil
end
- href_attr = "href=\"#{url}\"" unless href
- "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe
+ href_attr = "href=\"#{html_escape(url)}\"" unless href
+ "<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
@@ -338,7 +328,7 @@ module ActionView
html_options.merge!("type" => "submit", "value" => name)
- ("<form method=\"#{form_method}\" action=\"#{escape_once url}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
+ ("<form method=\"#{form_method}\" action=\"#{html_escape(url)}\" #{"data-remote=\"true\"" if remote} class=\"button_to\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
@@ -484,24 +474,27 @@ module ActionView
# :subject => "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
def mail_to(email_address, name = nil, html_options = {})
+ email_address = html_escape(email_address)
+
html_options = html_options.stringify_keys
encode = html_options.delete("encode").to_s
cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
- string = ''
- extras = ''
- extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}&" unless cc.nil?
- extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
- extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}&" unless body.nil?
- extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}&" unless subject.nil?
- extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
-
- email_address_obfuscated = html_escape(email_address)
+ extras = []
+ extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}" unless cc.nil?
+ extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}" unless bcc.nil?
+ extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}" unless body.nil?
+ extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}" unless subject.nil?
+ extras = extras.empty? ? '' : '?' + html_escape(extras.join('&'))
+
+ email_address_obfuscated = email_address.dup
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
+ string = ''
+
if encode == "javascript"
- "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
+ "document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
@@ -518,9 +511,9 @@ module ActionView
char = c.chr
string << (char =~ /\w/ ? sprintf("%%%x", c) : char)
end
- content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" })
+ 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}" })
+ content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
end
end
@@ -573,7 +566,7 @@ module ActionView
"in a #request method"
end
- url_string = CGI.unescapeHTML(url_for(options))
+ url_string = url_for(options)
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function
diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb
new file mode 100644
index 0000000000..443a0eafd1
--- /dev/null
+++ b/actionpack/lib/action_view/log_subscriber.rb
@@ -0,0 +1,28 @@
+module ActionView
+ # = Action View Log Subscriber
+ #
+ # Provides functionality so that Rails can output logs from Action View.
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def render_template(event)
+ message = "Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << (" (%.1fms)" % event.duration)
+ info(message)
+ end
+ alias :render_partial :render_template
+ alias :render_collection :render_template
+
+ # TODO: Ideally, ActionView should have its own logger so it does not depend on AC.logger
+ def logger
+ ActionController::Base.logger if defined?(ActionController::Base)
+ end
+
+ protected
+
+ def from_rails_root(string)
+ string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "")
+ end
+ end
+end
+
+ActionView::LogSubscriber.attach_to :action_view
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 823226cb9c..3ea8b86af1 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Lookup Context
+ #
# LookupContext is the object responsible to hold all information required to lookup
# templates, i.e. view paths and details. The LookupContext is also responsible to
# generate a key, given to view paths, used in the resolver cache lookup. Since
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 7f5e5d11b8..9857d688d2 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -1,4 +1,5 @@
module ActionView #:nodoc:
+ # = Action View PathSet
class PathSet < Array #:nodoc:
%w(initialize << concat insert push unshift).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index c606a71e18..33dfcbb803 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -2,11 +2,11 @@ require "action_view"
require "rails"
module ActionView
+ # = Action View Railtie
class Railtie < Rails::Railtie
config.action_view = ActiveSupport::OrderedOptions.new
-
- require "action_view/railties/log_subscriber"
- log_subscriber :action_view, ActionView::Railties::LogSubscriber.new
+ config.action_view.stylesheet_expansions = {}
+ config.action_view.javascript_expansions = { :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails'] }
initializer "action_view.cache_asset_timestamps" do |app|
unless app.config.cache_classes
@@ -16,6 +16,18 @@ module ActionView
end
end
+ initializer "action_view.javascript_expansions" do |app|
+ ActiveSupport.on_load(:action_view) do
+ ActionView::Helpers::AssetTagHelper.register_javascript_expansion(
+ app.config.action_view.delete(:javascript_expansions)
+ )
+
+ ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion(
+ app.config.action_view.delete(:stylesheet_expansions)
+ )
+ end
+ end
+
initializer "action_view.set_configs" do |app|
ActiveSupport.on_load(:action_view) do
app.config.action_view.each do |k,v|
diff --git a/actionpack/lib/action_view/railties/log_subscriber.rb b/actionpack/lib/action_view/railties/log_subscriber.rb
deleted file mode 100644
index 9487a10706..0000000000
--- a/actionpack/lib/action_view/railties/log_subscriber.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module ActionView
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def render_template(event)
- message = "Rendered #{from_rails_root(event.payload[:identifier])}"
- message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
- message << (" (%.1fms)" % event.duration)
- info(message)
- end
- alias :render_partial :render_template
- alias :render_collection :render_template
-
- def logger
- ActionController::Base.logger
- end
-
- protected
-
- def from_rails_root(string)
- string.sub("#{Rails.root}/", "").sub(/^app\/views\//, "")
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb
index a9dfc0cced..a474783a20 100644
--- a/actionpack/lib/action_view/render/layouts.rb
+++ b/actionpack/lib/action_view/render/layouts.rb
@@ -1,4 +1,5 @@
module ActionView
+ # = Action View Layouts
module Layouts
# Returns the contents that are yielded to a layout, given a name or a block.
#
@@ -55,7 +56,7 @@ module ActionView
end
# This is the method which actually finds the layout using details in the lookup
- # context object. If no layout is found, it checkes if at least a layout with
+ # context object. If no layout is found, it checks if at least a layout with
# the given name exists across all details before raising the error.
def find_layout(layout)
begin
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index 85f67d4f14..459aae94a2 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/object/blank'
module ActionView
+ # = Action View Partials
+ #
# There's also a convenience method for rendering sub templates within the current controller that depends on a
# single object (we call this kind of sub templates for partials). It relies on the fact that partials should
# follow the naming convention of being prefixed with an underscore -- as to separate them from regular
@@ -316,7 +318,7 @@ module ActionView
object.class.model_name.partial_path.dup.tap do |partial|
path = @view.controller_path
- partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
+ partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/)
end
end
end
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index 4d35296932..5320245173 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/try'
module ActionView
+ # = Action View Rendering
module Rendering
# Returns the result of a render that's dictated by the options hash. The primary options are:
#
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 53ad24fdc6..40ff1f2182 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/kernel/singleton_class'
module ActionView
+ # = Action View Template
class Template
extend ActiveSupport::Autoload
@@ -155,11 +156,12 @@ module ActionView
end
def inspect
- if defined?(Rails.root)
- identifier.sub("#{Rails.root}/", '')
- else
- identifier
- end
+ @inspect ||=
+ if defined?(Rails.root)
+ identifier.sub("#{Rails.root}/", '')
+ else
+ identifier
+ end
end
private
@@ -266,9 +268,11 @@ module ActionView
end
def build_method_name(locals)
- # TODO: is locals.keys.hash reliably the same?
- @method_names[locals.keys.hash] ||=
- "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
+ @method_names[locals.keys.hash] ||= "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
+ end
+
+ def identifier_method_name
+ @identifier_method_name ||= inspect.gsub(/[^a-z_]/, '_')
end
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index e50de7e5af..b1839b65e5 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -1,6 +1,7 @@
require "active_support/core_ext/enumerable"
module ActionView
+ # = Action View Errors
class ActionViewError < StandardError #:nodoc:
end
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 6228d7ac39..84d6474dd1 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -1,4 +1,5 @@
module ActionView #:nodoc:
+ # = Action View Template Handlers
class Template
module Handlers #:nodoc:
autoload :ERB, 'action_view/template/handlers/erb'
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index ef44925951..c9e20ca14e 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -3,6 +3,7 @@ require "active_support/core_ext/class"
require "action_view/template"
module ActionView
+ # = Action View Resolver
class Resolver
def initialize
@cached = Hash.new { |h1,k1| h1[k1] =
@@ -98,7 +99,7 @@ module ActionView
def initialize(path)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
super()
- @path = Pathname.new(path).expand_path
+ @path = File.expand_path(path)
end
def eql?(resolver)
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index 269340514c..51be831dfb 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -1,4 +1,5 @@
module ActionView #:nodoc:
+ # = Action View Text Template
class Template
class Text < String #:nodoc:
attr_accessor :mime_type
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 15d424be74..e5614c9e3b 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -4,6 +4,7 @@ require 'action_controller/test_case'
require 'action_view'
module ActionView
+ # = Action View Test Case
class TestCase < ActiveSupport::TestCase
class TestController < ActionController::Base
include ActionDispatch::TestProcess
@@ -84,6 +85,7 @@ module ActionView
def setup_with_controller
@controller = ActionView::TestCase::TestController.new
+ @request = @controller.request
@output_buffer = ActiveSupport::SafeBuffer.new
@rendered = ''
@@ -97,10 +99,15 @@ module ActionView
end
def render(options = {}, local_assigns = {}, &block)
- @rendered << output = _view.render(options, local_assigns, &block)
+ view.assign(_assigns)
+ @rendered << output = view.render(options, local_assigns, &block)
output
end
+ def locals
+ @locals ||= {}
+ end
+
included do
setup :setup_with_controller
end
@@ -130,36 +137,51 @@ module ActionView
end
end
- def _view
- @_view ||= begin
- view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
+ module Locals
+ attr_accessor :locals
+
+ def _render_partial(options)
+ locals[options[:partial]] = options[:locals]
+ super(options)
+ end
+ end
+
+ # The instance of ActionView::Base that is used by +render+.
+ def view
+ @view ||= begin
+ view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
view.singleton_class.send :include, _helpers
view.singleton_class.send :include, @controller._router.url_helpers
view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
+ view.extend(Locals)
+ view.locals = self.locals
view.output_buffer = self.output_buffer
view
end
end
+ alias_method :_view, :view
+
EXCLUDE_IVARS = %w{
+ @_assertion_wrapped
@_result
+ @controller
+ @layouts
+ @locals
+ @method_name
@output_buffer
+ @partials
@rendered
+ @request
+ @routes
@templates
- @view_context_class
- @layouts
- @partials
- @controller
-
- @method_name
- @fixture_cache
- @loaded_fixtures
@test_passed
+ @view
+ @view_context_class
}
def _instance_variables
- instance_variables - EXCLUDE_IVARS
- instance_variables
+ instance_variables.map(&:to_s) - EXCLUDE_IVARS
end
def _assigns
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 0ce1dc506b..232a1679e0 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -47,8 +47,12 @@ module AbstractController
end
def index
- self.response_body = @text
- end
+ self.response_body = @text.to_s
+ end
+ end
+
+ class Callback2Overwrite < Callback2
+ before_filter :first, :except => :index
end
class TestCallbacks2 < ActiveSupport::TestCase
@@ -70,6 +74,12 @@ module AbstractController
@controller.process(:index)
assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz")
end
+
+ test "before_filter with overwritten condition" do
+ @controller = Callback2Overwrite.new
+ result = @controller.process(:index)
+ assert_equal "", @controller.response_body
+ end
end
class Callback3 < ControllerWithCallbacks
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 84c5395610..c8477fb83f 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -38,6 +38,17 @@ end
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
+module Rails
+end
+
+# Monkey patch the old router initialization to be silenced.
+class ActionDispatch::Routing::DeprecatedMapper
+ def initialize_with_silencer(*args)
+ ActiveSupport::Deprecation.silence { initialize_without_silencer(*args) }
+ end
+ alias_method_chain :initialize, :silencer
+end
+
ActiveSupport::Dependencies.hook!
# Show backtraces for deprecated behavior for quicker cleanup.
@@ -202,6 +213,21 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase
self.class.app = old_app
silence_warnings { Object.const_set(:SharedTestRoutes, old_routes) }
end
+
+ def with_autoload_path(path)
+ path = File.join(File.dirname(__FILE__), "fixtures", path)
+ if ActiveSupport::Dependencies.autoload_paths.include?(path)
+ yield
+ else
+ begin
+ ActiveSupport::Dependencies.autoload_paths << path
+ yield
+ ensure
+ ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
+ ActiveSupport::Dependencies.clear
+ end
+ end
+ end
end
# Temporary base class
diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb
index 6d4b8e1e40..bdd1a0a15c 100644
--- a/actionpack/test/activerecord/active_record_store_test.rb
+++ b/actionpack/test/activerecord/active_record_store_test.rb
@@ -17,7 +17,6 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end
def get_session_id
- session[:foo]
render :text => "#{request.session_options[:id]}"
end
@@ -58,6 +57,10 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
get '/get_session_value'
assert_response :success
assert_equal 'foo: "baz"', response.body
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
end
end
end
@@ -92,6 +95,34 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
end
end
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
def test_getting_session_id
with_test_route_set do
get '/set_session_value'
@@ -101,7 +132,19 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
get '/get_session_id'
assert_response :success
- assert_equal session_id, response.body
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
end
diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb
index 331f861d8f..cfd86d704d 100644
--- a/actionpack/test/activerecord/controller_runtime_test.rb
+++ b/actionpack/test/activerecord/controller_runtime_test.rb
@@ -1,8 +1,8 @@
require 'active_record_unit'
require 'active_record/railties/controller_runtime'
require 'fixtures/project'
-require 'rails/log_subscriber/test_helper'
-require 'action_controller/railties/log_subscriber'
+require 'active_support/log_subscriber/test_helper'
+require 'action_controller/log_subscriber'
ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime
@@ -13,18 +13,18 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase
end
end
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
tests LogSubscriberController
def setup
super
@old_logger = ActionController::Base.logger
- Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new)
+ ActionController::LogSubscriber.attach_to :action_controller
end
def teardown
super
- Rails::LogSubscriber.log_subscribers.clear
+ ActiveSupport::LogSubscriber.log_subscribers.clear
ActionController::Base.logger = @old_logger
end
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 9f5e8ec657..90a1ef982c 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -381,6 +381,7 @@ class PolymorphicRoutesTest < ActionController::TestCase
with_test_routes do
@series.save
assert_equal "http://example.com/series/#{@series.id}", polymorphic_url(@series)
+ assert_equal "http://example.com/series", polymorphic_url(Series.new)
end
end
@@ -407,18 +408,18 @@ class PolymorphicRoutesTest < ActionController::TestCase
def with_admin_test_routes(options = {})
with_routing do |set|
- set.draw do |map|
- map.namespace :admin do |admin|
- admin.resources :projects do |projects|
- projects.resources :tasks
- projects.resource :bid do |bid|
- bid.resources :tasks
+ set.draw do
+ namespace :admin do
+ resources :projects do
+ resources :tasks
+ resource :bid do
+ resources :tasks
end
end
- admin.resources :taxes do |taxes|
- taxes.resources :faxes
+ resources :taxes do
+ resources :faxes
end
- admin.resources :series
+ resources :series
end
end
@@ -429,12 +430,12 @@ class PolymorphicRoutesTest < ActionController::TestCase
def with_admin_and_site_test_routes(options = {})
with_routing do |set|
- set.draw do |map|
- map.namespace :admin do |admin|
- admin.resources :projects do |projects|
- projects.namespace :site do |site|
- site.resources :tasks do |tasks|
- tasks.resources :steps
+ set.draw do
+ namespace :admin do
+ resources :projects do
+ namespace :site do
+ resources :tasks do
+ resources :steps
end
end
end
diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb
index 4f58b5d968..ae270b751e 100644
--- a/actionpack/test/controller/base_test.rb
+++ b/actionpack/test/controller/base_test.rb
@@ -2,9 +2,6 @@ require 'abstract_unit'
require 'logger'
require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
-module Rails
-end
-
# Provide some controller to run the tests on.
module Submodule
class ContainedEmptyController < ActionController::Base
diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb
index 5c636cbab8..4be09f8c83 100644
--- a/actionpack/test/controller/flash_test.rb
+++ b/actionpack/test/controller/flash_test.rb
@@ -236,6 +236,15 @@ class FlashIntegrationTest < ActionController::IntegrationTest
end
end
+ def test_just_using_flash_does_not_stream_a_cookie_back
+ with_test_route_set do
+ get '/use_flash'
+ assert_response :success
+ assert_nil @response.headers["Set-Cookie"]
+ assert_equal "flash: ", @response.body
+ end
+ end
+
private
# Overwrite get to send SessionSecret in env hash
@@ -247,10 +256,15 @@ class FlashIntegrationTest < ActionController::IntegrationTest
def with_test_route_set
with_routing do |set|
set.draw do |map|
- match ':action', :to => ActionDispatch::Session::CookieStore.new(
- FlashIntegrationTest::TestController, :key => FlashIntegrationTest::SessionKey, :secret => FlashIntegrationTest::SessionSecret
- )
+ match ':action', :to => FlashIntegrationTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::Session::CookieStore, :key => SessionKey
+ middleware.use ActionDispatch::Flash
+ middleware.delete "ActionDispatch::ShowExceptions"
end
+
yield
end
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index b11eba2f89..0a18741f0c 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -1,6 +1,6 @@
require "abstract_unit"
-require "rails/log_subscriber/test_helper"
-require "action_controller/railties/log_subscriber"
+require "active_support/log_subscriber/test_helper"
+require "action_controller/log_subscriber"
module Another
class LogSubscribersController < ActionController::Base
@@ -37,7 +37,7 @@ end
class ACLogSubscriberTest < ActionController::TestCase
tests Another::LogSubscribersController
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@@ -47,12 +47,12 @@ class ACLogSubscriberTest < ActionController::TestCase
@cache_path = File.expand_path('../temp/test_cache', File.dirname(__FILE__))
ActionController::Base.page_cache_directory = @cache_path
@controller.cache_store = :file_store, @cache_path
- Rails::LogSubscriber.add(:action_controller, ActionController::Railties::LogSubscriber.new)
+ ActionController::LogSubscriber.attach_to :action_controller
end
def teardown
super
- Rails::LogSubscriber.log_subscribers.clear
+ ActiveSupport::LogSubscriber.log_subscribers.clear
FileUtils.rm_rf(@cache_path)
ActionController::Base.logger = @old_logger
end
diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb
index 0b40f8ce95..8fa5d20372 100644
--- a/actionpack/test/controller/new_base/base_test.rb
+++ b/actionpack/test/controller/new_base/base_test.rb
@@ -31,9 +31,17 @@ module Dispatching
end
class EmptyController < ActionController::Base ; end
+ class SubEmptyController < EmptyController ; end
+ class NonDefaultPathController < ActionController::Base
+ def self.controller_path; "i_am_not_default"; end
+ end
module Submodule
class ContainedEmptyController < ActionController::Base ; end
+ class ContainedSubEmptyController < ContainedEmptyController ; end
+ class ContainedNonDefaultPathController < ActionController::Base
+ def self.controller_path; "i_am_extremly_not_default"; end
+ end
end
class BaseTest < Rack::TestCase
@@ -65,16 +73,46 @@ module Dispatching
assert_equal EmptyController.controller_path, EmptyController.new.controller_path
end
+ test "non-default controller path" do
+ assert_equal 'i_am_not_default', NonDefaultPathController.controller_path
+ assert_equal NonDefaultPathController.controller_path, NonDefaultPathController.new.controller_path
+ end
+
+ test "sub controller path" do
+ assert_equal 'dispatching/sub_empty', SubEmptyController.controller_path
+ assert_equal SubEmptyController.controller_path, SubEmptyController.new.controller_path
+ end
+
test "namespaced controller path" do
assert_equal 'dispatching/submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
end
+ test "namespaced non-default controller path" do
+ assert_equal 'i_am_extremly_not_default', Submodule::ContainedNonDefaultPathController.controller_path
+ assert_equal Submodule::ContainedNonDefaultPathController.controller_path, Submodule::ContainedNonDefaultPathController.new.controller_path
+ end
+
+ test "namespaced sub controller path" do
+ assert_equal 'dispatching/submodule/contained_sub_empty', Submodule::ContainedSubEmptyController.controller_path
+ assert_equal Submodule::ContainedSubEmptyController.controller_path, Submodule::ContainedSubEmptyController.new.controller_path
+ end
+
test "controller name" do
assert_equal 'empty', EmptyController.controller_name
assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
end
+ test "non-default path controller name" do
+ assert_equal 'non_default_path', NonDefaultPathController.controller_name
+ assert_equal 'contained_non_default_path', Submodule::ContainedNonDefaultPathController.controller_name
+ end
+
+ test "sub controller name" do
+ assert_equal 'sub_empty', SubEmptyController.controller_name
+ assert_equal 'contained_sub_empty', Submodule::ContainedSubEmptyController.controller_name
+ end
+
test "action methods" do
assert_equal Set.new(%w(
index
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index 813dedc80d..6a84475758 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -13,6 +13,19 @@ class Comment
end
end
+class Sheep
+ extend ActiveModel::Naming
+ include ActiveModel::Conversion
+
+ attr_reader :id
+ def to_key; id ? [id] : nil end
+ def save; @id = 1 end
+ def new_record?; @id.nil? end
+ def name
+ @id.nil? ? 'new sheep' : "sheep ##{@id}"
+ end
+end
+
class Comment::Nested < Comment; end
class Test::Unit::TestCase
@@ -20,7 +33,7 @@ class Test::Unit::TestCase
def comments_url
'http://www.example.com/comments'
end
-
+
def comment_url(comment)
"http://www.example.com/comments/#{comment.id}"
end
@@ -35,6 +48,7 @@ class RecordIdentifierTest < Test::Unit::TestCase
@record = @klass.new
@singular = 'comment'
@plural = 'comments'
+ @uncountable = Sheep
end
def test_dom_id_with_new_record
@@ -58,7 +72,7 @@ class RecordIdentifierTest < Test::Unit::TestCase
def test_dom_class
assert_equal @singular, dom_class(@record)
end
-
+
def test_dom_class_with_prefix
assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix)
end
@@ -79,6 +93,11 @@ class RecordIdentifierTest < Test::Unit::TestCase
assert_equal @plural, plural_class_name(@klass)
end
+ def test_uncountable
+ assert_equal true, uncountable?(@uncountable)
+ assert_equal false, uncountable?(@klass)
+ end
+
private
def method_missing(method, *args)
RecordIdentifier.send(method, *args)
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index e3ed097c67..a57a12f271 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -7,6 +7,10 @@ module Fun
# :ported:
def hello_world
end
+
+ def nested_partial_with_form_builder
+ render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}, Proc.new {})
+ end
end
end
@@ -1230,6 +1234,13 @@ class RenderTest < ActionController::TestCase
assert_match(/<label/, @response.body)
assert_template('test/_labelling_form')
end
+
+ def test_nested_partial_with_form_builder
+ @controller = Fun::GamesController.new
+ get :nested_partial_with_form_builder
+ assert_match(/<label/, @response.body)
+ assert_template('fun/games/_form')
+ end
def test_partial_collection
get :partial_collection
diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb
index 907acf9573..4b30b0af36 100644
--- a/actionpack/test/controller/url_for_test.rb
+++ b/actionpack/test/controller/url_for_test.rb
@@ -34,9 +34,15 @@ module AbstractController
)
end
- def test_anchor_should_be_cgi_escaped
- assert_equal('/c/a#anc%2Fhor',
- W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('anc/hor'))
+ def test_anchor_should_escape_unsafe_pchar
+ assert_equal('/c/a#%23anchor',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('#anchor'))
+ )
+ end
+
+ def test_anchor_should_not_escape_safe_pchar
+ assert_equal('/c/a#name=user&email=user@domain.com',
+ W.new.url_for(:only_path => true, :controller => 'c', :action => 'a', :anchor => Struct.new(:to_param).new('name=user&email=user@domain.com'))
)
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 40c5ac2d09..e3ec5cf182 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -89,15 +89,21 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 19512, file.size
end
+ # Pending fix in Rack 1.2.2
+ # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload
test "parses mixed files" do
- params = parse_multipart('mixed_files')
- assert_equal %w(files foo), params.keys.sort
- assert_equal 'bar', params['foo']
-
- # Rack doesn't handle multipart/mixed for us.
- files = params['files']
- files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding)
- assert_equal 19756, files.size
+ if Rack.release <= '1.2.1'
+ $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2'
+ else
+ params = parse_multipart('mixed_files')
+ assert_equal %w(files foo), params.keys.sort
+ assert_equal 'bar', params['foo']
+
+ # Rack doesn't handle multipart/mixed for us.
+ files = params['files']
+ files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding)
+ assert_equal 19756, files.size
+ end
end
test "does not create tempfile if no file has been selected" do
diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
index 69dbd7f528..0bcef81534 100644
--- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb
@@ -141,6 +141,29 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
post "/parse", actual
assert_response :ok
assert_equal(expected, TestController.last_request_parameters)
+ assert_utf8(TestController.last_request_parameters)
+ end
+ end
+
+ def assert_utf8(object)
+ return unless "ruby".encoding_aware?
+
+ correct_encoding = Encoding.default_internal
+
+ unless object.is_a?(Hash)
+ assert_equal correct_encoding, object.encoding, "#{object.inspect} should have been UTF-8"
+ return
+ end
+
+ object.each do |k,v|
+ case v
+ when Hash
+ assert_utf8(v)
+ when Array
+ v.each {|el| assert_utf8(el) }
+ else
+ assert_utf8(v)
+ end
end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index e294703e72..26bd641cd6 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -16,6 +16,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
Routes = routes
Routes.draw do
default_url_options :host => "rubyonrails.org"
+ resources_path_names :correlation_indexes => "info_about_correlation_indexes"
controller :sessions do
get 'login' => :new
@@ -34,6 +35,13 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
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
+
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")
@@ -68,12 +76,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get 'admin/accounts' => "queenbee#accounts"
end
- scope 'pt', :name_prefix => 'pt' do
+ 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' }, :path => 'administrador' do
+ 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
@@ -84,6 +97,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :projects, :controller => :project do
resources :involvements, :attachments
+ get :correlation_indexes, :on => :collection
resources :participants do
put :update_all, :on => :collection
@@ -110,6 +124,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
member do
+ get :some_path_with_name
put :accessible_projects
post :resend, :generate_new_password
end
@@ -142,6 +157,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :comments, :except => :destroy
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
@@ -174,6 +195,36 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ 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
+ resource :avatar do
+ get "thumbnail(.:format)" => "avatars#thumbnail", :as => :thumbnail, :on => :member
+ end
+ resources :invoices do
+ get "outstanding" => "invoices#outstanding", :as => :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
+ scope(':version', :version => /.+/) do
+ resources :users, :id => /.+?/, :format => /json|xml/
+ end
+ end
+
match 'sprockets.js' => ::TestRoutingMapper::SprocketsApp
match 'people/:id/update', :to => 'people#update', :as => :update_person
@@ -207,10 +258,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ namespace :users, :path => 'usuarios' do
+ root :to => 'home#index'
+ end
+
controller :articles do
- scope '/articles', :name_prefix => 'article' do
+ scope '/articles', :as => 'article' do
scope :path => '/:title', :title => /[a-z]+/, :as => :with_title do
- match '/:id', :to => :with_id
+ match '/:id', :to => :with_id, :as => ""
end
end
end
@@ -243,8 +298,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resource :dashboard, :constraints => { :ip => /192\.168\.1\.\d{1,3}/ }
- scope :module => 'api' do
+ scope :module => :api do
resource :token
+ resources :errors, :shallow => true do
+ resources :notices
+ end
end
scope :path => 'api' do
@@ -252,6 +310,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match '/' => 'mes#index'
end
+ namespace :private do
+ root :to => redirect('/private/index')
+ match "index", :to => 'private#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
+
match "whatever/:controller(/:action(/:id))"
resource :profile do
@@ -261,6 +334,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
post :preview
end
end
+
+ resources :content
+
+ 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
+
+ match '/:locale/*file.:format', :to => 'files#show', :file => /path\/to\/existing\/file/
end
end
@@ -373,6 +461,15 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_namespace_redirect
+ with_test_routes do
+ get '/private'
+ assert_equal 301, @response.status
+ assert_equal 'http://www.example.com/private/index', @response.headers['Location']
+ assert_equal 'Moved Permanently', @response.body
+ end
+ end
+
def test_session_singleton_resource
with_test_routes do
get '/session'
@@ -455,6 +552,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_bookmarks
+ with_test_routes do
+ get '/bookmark/build'
+ assert_equal 'bookmarks#new', @response.body
+ assert_equal '/bookmark/build', new_bookmark_path
+
+ post '/bookmark/create'
+ assert_equal 'bookmarks#create', @response.body
+ assert_equal '/bookmark/create', bookmark_path
+
+ put '/bookmark'
+ assert_equal 'bookmarks#update', @response.body
+ assert_equal '/bookmark', update_bookmark_path
+
+ get '/bookmark/remove'
+ assert_equal 'bookmarks#destroy', @response.body
+ assert_equal '/bookmark/remove', bookmark_remove_path
+ end
+ end
+
def test_admin
with_test_routes do
get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'}
@@ -468,6 +585,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'}
assert_equal 'pass', @response.headers['X-Cascade']
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'}
+ assert_equal 'queenbee#passwords', @response.body
+
+ get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'}
+ assert_equal 'pass', @response.headers['X-Cascade']
end
end
@@ -662,6 +785,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_projects_with_resources_path_names
+ with_test_routes do
+ get '/projects/info_about_correlation_indexes'
+ assert_equal 'project#correlation_indexes', @response.body
+ assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path
+ end
+ end
+
def test_projects_posts
with_test_routes do
get '/projects/1/posts'
@@ -729,6 +860,38 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_resource_routes_only_create_update_destroy
+ with_test_routes do
+ delete '/past'
+ assert_equal 'pasts#destroy', @response.body
+ assert_equal '/past', past_path
+
+ put '/present'
+ assert_equal 'presents#update', @response.body
+ assert_equal '/present', present_path
+
+ post '/future'
+ assert_equal 'futures#create', @response.body
+ assert_equal '/future', future_path
+ end
+ end
+
+ def test_resources_routes_only_create_update_destroy
+ with_test_routes do
+ post '/relationships'
+ assert_equal 'relationships#create', @response.body
+ assert_equal '/relationships', relationships_path
+
+ delete '/relationships/1'
+ assert_equal 'relationships#destroy', @response.body
+ assert_equal '/relationships/1', relationship_path(1)
+
+ put '/friendships/1'
+ assert_equal 'friendships#update', @response.body
+ assert_equal '/friendships/1', friendship_path(1)
+ end
+ end
+
def test_resource_with_slugs_in_ids
with_test_routes do
get '/posts/rails-rocks'
@@ -763,6 +926,22 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
get '/pt/administrador/novo'
assert_equal 'admins#new', @response.body
assert_equal '/pt/administrador/novo', new_pt_admin_path
+
+ put '/pt/administrador/ativar'
+ assert_equal 'admins#activate', @response.body
+ assert_equal '/pt/administrador/ativar', activate_pt_admin_path
+ end
+ end
+
+ def test_path_option_override
+ with_test_routes do
+ get '/pt/projetos/novo/abrir'
+ assert_equal 'projects#open', @response.body
+ assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path
+
+ put '/pt/projetos/1/fechar'
+ assert_equal 'projects#close', @response.body
+ assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1)
end
end
@@ -843,7 +1022,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal '/account/admin/subscription', account_admin_subscription_path
end
end
-
+
def test_namespace_nested_in_resources
with_test_routes do
get '/clients/1/google/account'
@@ -856,6 +1035,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_namespace_with_options
+ with_test_routes do
+ get '/usuarios'
+ assert_equal '/usuarios', users_root_path
+ assert_equal 'users/home#index', @response.body
+ end
+ end
+
def test_articles_with_id
with_test_routes do
get '/articles/rails/1'
@@ -1257,6 +1444,190 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_custom_resource_routes_are_scoped
+ with_test_routes do
+ assert_equal '/customers/recent', recent_customers_path
+ assert_equal '/customers/1/profile', profile_customer_path(:id => '1')
+ assert_equal '/customers/new/preview', preview_new_customer_path
+ assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg)
+ assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1')
+ assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2')
+ assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1')
+ assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1')
+ assert_equal '/notes/1/print', print_note_path(:id => '1')
+ assert_equal '/api/customers/recent', recent_api_customers_path
+ assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1')
+ assert_equal '/api/customers/new/preview', preview_new_api_customer_path
+
+ get '/customers/1/invoices/overdue'
+ assert_equal 'invoices#overdue', @response.body
+ end
+ end
+
+ def test_shallow_nested_routes_ignore_module
+ with_test_routes do
+ get '/errors/1/notices'
+ assert_equal 'api/notices#index', @response.body
+ assert_equal '/errors/1/notices', error_notices_path(:error_id => '1')
+
+ get '/notices/1'
+ assert_equal 'api/notices#show', @response.body
+ assert_equal '/notices/1', notice_path(:id => '1')
+ end
+ end
+
+ def test_non_greedy_regexp
+ with_test_routes do
+ get '/api/1.0/users'
+ assert_equal 'api/users#index', @response.body
+ assert_equal '/api/1.0/users', api_users_path(:version => '1.0')
+
+ get '/api/1.0/users.json'
+ assert_equal 'api/users#index', @response.body
+ assert_equal true, @request.format.json?
+ assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json)
+
+ get '/api/1.0/users/first.last'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last')
+
+ get '/api/1.0/users/first.last.xml'
+ assert_equal 'api/users#show', @response.body
+ assert_equal 'first.last', @request.params[:id]
+ assert_equal true, @request.format.xml?
+ assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml)
+ end
+ end
+
+ def test_glob_parameter_accepts_regexp
+ with_test_routes do
+ get '/en/path/to/existing/file.html'
+ assert_equal 200, @response.status
+ end
+ end
+
+ def test_resources_controller_name_is_not_pluralized
+ with_test_routes do
+ get '/content'
+ assert_equal 'content#index', @response.body
+ end
+ end
+
+ def test_url_generator_for_optional_prefix_dynamic_segment
+ with_test_routes do
+ get '/bob/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/bob/followers',
+ url_for(:controller => "followers", :action => "index", :username => "bob")
+
+ get '/followers'
+ assert_equal 'followers#index', @response.body
+ assert_equal 'http://www.example.com/followers',
+ url_for(:controller => "followers", :action => "index", :username => nil)
+ end
+ end
+
+ def test_url_generator_for_optional_suffix_static_and_dynamic_segment
+ with_test_routes do
+ get '/groups/user/bob'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups/user/bob',
+ url_for(:controller => "groups", :action => "index", :username => "bob")
+
+ get '/groups'
+ assert_equal 'groups#index', @response.body
+ assert_equal 'http://www.example.com/groups',
+ url_for(:controller => "groups", :action => "index", :username => nil)
+ end
+ end
+
+ def test_url_generator_for_optional_prefix_static_and_dynamic_segment
+ with_test_routes do
+ get 'user/bob/photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/user/bob/photos',
+ url_for(:controller => "photos", :action => "index", :username => "bob")
+
+ get 'photos'
+ assert_equal 'photos#index', @response.body
+ assert_equal 'http://www.example.com/photos',
+ url_for(:controller => "photos", :action => "index", :username => nil)
+ end
+ end
+
+ def test_url_recognition_for_optional_static_segments
+ with_test_routes do
+ get '/groups/discussions/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/groups/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/groups/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/groups/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/discussions/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/discussions/messages/1'
+ assert_equal 'messages#show', @response.body
+
+ get '/messages'
+ assert_equal 'messages#index', @response.body
+
+ get '/messages/1'
+ assert_equal 'messages#show', @response.body
+ end
+ end
+
+ def test_router_removes_invalid_conditions
+ with_test_routes do
+ get '/tickets'
+ assert_equal 'tickets#index', @response.body
+ assert_equal '/tickets', tickets_path
+ end
+ end
+
+ def test_constraints_are_merged_from_scope
+ with_test_routes do
+ get '/movies/0001'
+ assert_equal 'movies#show', @response.body
+ assert_equal '/movies/0001', movie_path(:id => '0001')
+
+ get '/movies/00001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') }
+
+ get '/movies/0001/reviews'
+ assert_equal 'reviews#index', @response.body
+ assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001')
+
+ get '/movies/00001/reviews'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') }
+
+ get '/movies/0001/reviews/0001'
+ assert_equal 'reviews#show', @response.body
+ assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001')
+
+ get '/movies/00001/reviews/0001'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') }
+
+ get '/movies/0001/trailer'
+ assert_equal 'trailers#show', @response.body
+ assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001')
+
+ get '/movies/00001/trailer'
+ assert_equal 'Not Found', @response.body
+ assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') }
+ end
+ end
+
private
def with_test_routes
yield
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index b4380f7818..fd63f5ad5e 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -83,7 +83,7 @@ class CookieStoreTest < ActionController::IntegrationTest
get '/get_session_id'
assert_response :success
- assert_equal "id: #{session_id}", response.body
+ assert_equal "id: #{session_id}", response.body, "should be able to read session id without accessing the session hash"
end
end
@@ -96,6 +96,31 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ # {:foo=>#<SessionAutoloadTest::Foo bar:"baz">, :session_id=>"ce8b0752a6ab7c7af3cdb8a80e6b9e46"}
+ SignedSerializedCookie = "BAh7BzoIZm9vbzodU2Vzc2lvbkF1dG9sb2FkVGVzdDo6Rm9vBjoJQGJhciIIYmF6Og9zZXNzaW9uX2lkIiVjZThiMDc1MmE2YWI3YzdhZjNjZGI4YTgwZTZiOWU0Ng==--2bf3af1ae8bd4e52b9ac2099258ace0c380e601c"
+
+ def test_deserializes_unloaded_classes_on_get_id
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ cookies[SessionKey] = SignedSerializedCookie
+ get '/get_session_id'
+ assert_response :success
+ assert_equal 'id: ce8b0752a6ab7c7af3cdb8a80e6b9e46', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_deserializes_unloaded_classes_on_get_value
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ cookies[SessionKey] = SignedSerializedCookie
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
def test_close_raises_when_data_overflows
with_test_route_set do
assert_raise(ActionDispatch::Cookies::CookieOverflow) {
@@ -141,6 +166,15 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil headers['Set-Cookie'], "should only create session on write, not read"
+ end
+ end
+
def test_persistent_session_id
with_test_route_set do
cookies[SessionKey] = SignedBar
@@ -196,21 +230,21 @@ class CookieStoreTest < ActionController::IntegrationTest
def test_session_store_without_domain
with_test_route_set do
get '/set_session_value'
- assert_no_match /domain\=/, headers['Set-Cookie']
+ assert_no_match(/domain\=/, headers['Set-Cookie'])
end
end
def test_session_store_with_nil_domain
with_test_route_set(:domain => nil) do
get '/set_session_value'
- assert_no_match /domain\=/, headers['Set-Cookie']
+ assert_no_match(/domain\=/, headers['Set-Cookie'])
end
end
def test_session_store_with_all_domains
with_test_route_set(:domain => :all) do
get '/set_session_value'
- assert_match /domain=\.example\.com/, headers['Set-Cookie']
+ assert_match(/domain=\.example\.com/, headers['Set-Cookie'])
end
end
@@ -238,4 +272,5 @@ class CookieStoreTest < ActionController::IntegrationTest
yield
end
end
+
end
diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb
index 8858a398e0..9bd6f9b8c4 100644
--- a/actionpack/test/dispatch/session/mem_cache_store_test.rb
+++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb
@@ -11,13 +11,17 @@ class MemCacheStoreTest < ActionController::IntegrationTest
session[:foo] = "bar"
head :ok
end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
def get_session_value
render :text => "foo: #{session[:foo].inspect}"
end
def get_session_id
- session[:foo]
render :text => "#{request.session_options[:id]}"
end
@@ -56,6 +60,34 @@ class MemCacheStoreTest < ActionController::IntegrationTest
end
end
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from memcached"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
def test_setting_session_value_after_session_reset
with_test_route_set do
get '/set_session_value'
@@ -86,7 +118,38 @@ class MemCacheStoreTest < ActionController::IntegrationTest
get '/get_session_id'
assert_response :success
- assert_equal session_id, response.body
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
end
end
diff --git a/actionpack/test/fixtures/fun/games/_form.erb b/actionpack/test/fixtures/fun/games/_form.erb
new file mode 100644
index 0000000000..01107f1cb2
--- /dev/null
+++ b/actionpack/test/fixtures/fun/games/_form.erb
@@ -0,0 +1 @@
+<%= form.label :title %>
diff --git a/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
new file mode 100644
index 0000000000..4ee7a24561
--- /dev/null
+++ b/actionpack/test/fixtures/session_autoload_test/session_autoload_test/foo.rb
@@ -0,0 +1,10 @@
+module SessionAutoloadTest
+ class Foo
+ def initialize(bar='baz')
+ @bar = bar
+ end
+ def inspect
+ "#<#{self.class} bar:#{@bar.inspect}>"
+ end
+ end
+end
diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb
index b1705072c2..6ab244d178 100644
--- a/actionpack/test/template/active_model_helper_test.rb
+++ b/actionpack/test/template/active_model_helper_test.rb
@@ -39,6 +39,13 @@ class ActiveModelHelperTest < ActionView::TestCase
)
end
+ def test_hidden_field_does_not_render_errors
+ assert_dom_equal(
+ %(<input id="post_author_name" name="post[author_name]" type="hidden" value="" />),
+ hidden_field("post", "author_name")
+ )
+ end
+
def test_field_error_proc
old_proc = ActionView::Base.field_error_proc
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 633641514e..6d5e4893c4 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -44,7 +44,7 @@ class AssetTagHelperTest < ActionView::TestCase
@controller.request = @request
- ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
+ ActionView::Helpers::AssetTagHelper::register_javascript_expansion :defaults => ['prototype', 'effects', 'dragdrop', 'controls', 'rails']
end
def url_for(*args)
@@ -256,19 +256,6 @@ class AssetTagHelperTest < ActionView::TestCase
assert javascript_include_tag("prototype").html_safe?
end
- def test_register_javascript_include_default
- ENV["RAILS_ASSET_ID"] = ""
- ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank'
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
- end
-
- def test_register_javascript_include_default_mixed_defaults
- ENV["RAILS_ASSET_ID"] = ""
- ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'bank'
- ActionView::Helpers::AssetTagHelper::register_javascript_include_default 'robber', '/elsewhere/cools.js'
- assert_dom_equal %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/rails.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>), javascript_include_tag(:defaults)
- end
-
def test_custom_javascript_expansions
ENV["RAILS_ASSET_ID"] = ""
ActionView::Helpers::AssetTagHelper::register_javascript_expansion :robbery => ["bank", "robber"]
@@ -286,6 +273,11 @@ class AssetTagHelperTest < ActionView::TestCase
assert_raise(ArgumentError) { javascript_include_tag('first', :monkey, 'last') }
end
+ def test_reset_javascript_expansions
+ ActionView::Helpers::AssetTagHelper.javascript_expansions.clear
+ assert_raise(ArgumentError) { javascript_include_tag(:defaults) }
+ end
+
def test_stylesheet_path
ENV["RAILS_ASSET_ID"] = ""
StylePathToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
@@ -923,7 +915,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
@request = Struct.new(:protocol).new("gopher://")
@controller.request = @request
- ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
+ ActionView::Helpers::AssetTagHelper.javascript_expansions.clear
end
def url_for(options)
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 053fcc4d24..a1db49d4d0 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -882,6 +882,33 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
end
+ def test_select_date_with_separator_and_discard_day
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << " / "
+
+ expected << %(<select id="date_first_month" name="date[first][month]">\n)
+ expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
+ end
+
+ def test_select_date_with_separator_discard_month_and_day
+ expected = %(<select id="date_first_year" name="date[first][year]">\n)
+ expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
+ expected << "</select>\n"
+
+ expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n)
+ expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="16" />\n)
+
+ assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"})
+ end
+
def test_select_datetime
expected = %(<select id="date_first_year" name="date[first][year]">\n)
expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n)
diff --git a/actionpack/test/template/erb/form_for_test.rb b/actionpack/test/template/erb/form_for_test.rb
index ec6e872735..e722b40a9a 100644
--- a/actionpack/test/template/erb/form_for_test.rb
+++ b/actionpack/test/template/erb/form_for_test.rb
@@ -5,7 +5,7 @@ module ERBTest
class TagHelperTest < BlockTestCase
test "form_for works" do
output = render_content "form_for(:staticpage, :url => {:controller => 'blah', :action => 'update'})", ""
- assert_equal "<form action=\"/blah/update\" method=\"post\"></form>", output
+ assert_match %r{<form.*action="/blah/update".*method="post">.*</form>}, output
end
end
end
diff --git a/actionpack/test/template/erb/tag_helper_test.rb b/actionpack/test/template/erb/tag_helper_test.rb
index 64a88bde1d..d073100986 100644
--- a/actionpack/test/template/erb/tag_helper_test.rb
+++ b/actionpack/test/template/erb/tag_helper_test.rb
@@ -28,8 +28,8 @@ module ERBTest
end
test "percent equals works with form tags" do
- expected_output = "<form action=\"foo\" method=\"post\">hello</form>"
- maybe_deprecated { assert_equal expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") }
+ expected_output = %r{<form.*action="foo".*method="post">.*hello*</form>}
+ maybe_deprecated { assert_match expected_output, render_content("form_tag('foo')", "<%= 'hello' %>") }
end
test "percent equals works with fieldset tags" do
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 2f3869994c..4b9e41803f 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -188,6 +188,11 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, text_field("post", "title", :maxlength => 35, :size => nil)
end
+ def test_text_field_with_nil_value
+ expected = '<input id="post_title" name="post[title]" size="30" type="text" />'
+ assert_dom_equal expected, text_field("post", "title", :value => nil)
+ end
+
def test_text_field_doesnt_change_param_values
object_name = 'post[]'
expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />'
@@ -208,6 +213,11 @@ class FormHelperTest < ActionView::TestCase
hidden_field("post", "title")
end
+ def test_hidden_field_with_nil_value
+ expected = '<input id="post_title" name="post[title]" type="hidden" />'
+ assert_dom_equal expected, hidden_field("post", "title", :value => nil)
+ end
+
def test_text_field_with_options
assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Something Else" />',
hidden_field("post", "title", :value => "Something Else")
@@ -300,6 +310,11 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_radio_button_with_negative_integer_value
+ assert_dom_equal('<input id="post_secret_-1" name="post[secret]" type="radio" value="-1" />',
+ radio_button("post", "secret", "-1"))
+ end
+
def test_radio_button_respects_passed_in_id
assert_dom_equal('<input checked="checked" id="foo" name="post[secret]" type="radio" value="1" />',
radio_button("post", "secret", "1", :id=>"foo")
@@ -568,7 +583,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
+ snowman +
"<label for='post_title'>The Title</label>" +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
@@ -589,15 +605,14 @@ class FormHelperTest < ActionView::TestCase
concat f.submit('Create post')
end
- expected =
- "<form class='other_name_edit' method='post' action='/posts/123' id='create-post'>" +
- "<div style='margin:0;padding:0;display:inline'><input name='_method' value='put' type='hidden' /></div>" +
+ expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do
"<label for='other_name_title'>Title</label>" +
"<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
"<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
"<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" +
- "<input name='commit' id='other_name_submit' value='Create post' type='submit' /></form>"
+ "<input name='commit' id='other_name_submit' value='Create post' type='submit' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -611,14 +626,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' id='create-post' method='post'>" +
- "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" +
+ expected = whole_form("http://www.example.com", "create-post", nil, "put") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
- "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
- "</form>"
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -632,14 +645,31 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' id='create-post' method='post' data-remote='true'>" +
- "<div style='margin:0;padding:0;display:inline'><input name='_method' type='hidden' value='put' /></div>" +
+ expected = whole_form("http://www.example.com", "create-post", nil, :method => "put", :remote => true) do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
- "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
- "</form>"
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
+ def test_form_for_with_remote_without_html
+ assert_deprecated do
+ form_for(:post, @post, :remote => true) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+ end
+
+ expected = whole_form("http://www.example.com", nil, nil, :remote => true) do
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -651,13 +681,12 @@ class FormHelperTest < ActionView::TestCase
concat f.check_box(:secret)
end
- expected =
- "<form action='http://www.example.com' id='create-post' method='post'>" +
+ expected = whole_form("http://www.example.com", "create-post") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
- "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" +
- "</form>"
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -672,14 +701,13 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' method='post'>" +
+ expected = whole_form do
"<label for='post_123_title'>Title</label>" +
"<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
"<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[123][secret]' type='hidden' value='0' />" +
- "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" +
- "</form>"
+ "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -693,13 +721,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' method='post'>" +
+ expected = whole_form do
"<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" +
"<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[][secret]' type='hidden' value='0' />" +
- "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" +
- "</form>"
+ "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -714,9 +741,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='commit' id='post_submit' type='submit' value='Create Post' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='commit' id='post_submit' type='submit' value='Create Post' />"
+ end
+
assert_dom_equal expected, output_buffer
ensure
I18n.locale = old_locale
@@ -731,9 +759,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='commit' id='post_submit' type='submit' value='Confirm Post changes' />"
+ end
+
assert_dom_equal expected, output_buffer
ensure
I18n.locale = old_locale
@@ -746,9 +775,10 @@ class FormHelperTest < ActionView::TestCase
concat f.submit :class => "extra"
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='commit' class='extra' id='post_submit' type='submit' value='Save changes' />"
+ end
+
assert_dom_equal expected, output_buffer
ensure
I18n.locale = old_locale
@@ -763,9 +793,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='commit' id='another_post_submit' type='submit' value='Update your Post' />"
+ end
+
assert_dom_equal expected, output_buffer
ensure
I18n.locale = old_locale
@@ -780,9 +811,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[comment][title]' size='30' type='text' id='post_comment_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -797,10 +828,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
- "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" +
+ "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -815,10 +846,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
- "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" +
+ "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -832,9 +863,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -848,9 +879,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -864,9 +895,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -880,9 +911,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -896,9 +927,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -917,12 +948,11 @@ class FormHelperTest < ActionView::TestCase
}
end
- expected = "<form action='http://www.example.com' method='post'>" +
- "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" +
- "</form>" +
- "<form action='http://www.example.com' method='post'>" +
- "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" +
- "</form>"
+ expected = whole_form do
+ "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />"
+ end + whole_form do
+ "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -940,10 +970,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -971,11 +1001,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
- '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -993,11 +1023,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
- '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' +
+ '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1016,13 +1046,13 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1042,13 +1072,13 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1067,11 +1097,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1090,12 +1120,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1110,9 +1140,9 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1129,13 +1159,13 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1153,13 +1183,13 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
- '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' +
+ '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1178,12 +1208,12 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' +
- '</form>'
+ expected = whole_form do
+ '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' +
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />'
+ end
assert_dom_equal expected, output_buffer
assert_equal yielded_comments, @post.comments
@@ -1200,10 +1230,10 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' +
- '</form>'
+ expected = whole_form do
+ '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1238,20 +1268,20 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected = '<form action="http://www.example.com" method="post">' +
- '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
- '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
- '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
- '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
- '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
- '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
- '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
- '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
- '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' +
- '</form>'
+ expected = whole_form do
+ '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' +
+ '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' +
+ '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' +
+ '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' +
+ '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' +
+ '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' +
+ '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' +
+ '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' +
+ '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' +
+ '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' +
+ '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' +
+ '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />'
+ end
assert_dom_equal expected, output_buffer
end
@@ -1391,7 +1421,8 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<form action='http://www.example.com' id='create-post' method='post'>" +
+ "<form accept-charset='UTF-8' action='http://www.example.com' id='create-post' method='post'>" +
+ snowman +
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='parent_post[secret]' type='hidden' value='0' />" +
@@ -1414,11 +1445,11 @@ class FormHelperTest < ActionView::TestCase
end
expected =
- "<form action='http://www.example.com' id='create-post' method='post'>" +
- "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
- "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
- "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" +
- "</form>"
+ whole_form("http://www.example.com", "create-post") do
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />"
+ end
assert_dom_equal expected, output_buffer
end
@@ -1442,16 +1473,42 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' method='post'>" +
- "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
- "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
- "</form>"
+ expected = whole_form do
+ "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
+ "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
+ end
assert_dom_equal expected, output_buffer
end
+ def snowman(method = nil)
+ txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
+ txt << %{</div>}
+ end
+
+ def form_text(action = "http://www.example.com", id = nil, html_class = nil, remote = nil)
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt << %{ data-remote="true"} if remote
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ txt << %{ method="post">}
+ end
+
+ def whole_form(action = "http://www.example.com", id = nil, html_class = nil, options = nil)
+ contents = block_given? ? yield : ""
+
+ if options.is_a?(Hash)
+ method, remote = options.values_at(:method, :remote)
+ else
+ method = options
+ end
+
+ form_text(action, id, html_class, remote) + snowman(method) + contents + "</form>"
+ end
+
def test_default_form_builder
old_default_form_builder, ActionView::Base.default_form_builder =
ActionView::Base.default_form_builder, LabelledFormBuilder
@@ -1464,12 +1521,11 @@ class FormHelperTest < ActionView::TestCase
end
end
- expected =
- "<form action='http://www.example.com' method='post'>" +
+ expected = whole_form do
"<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" +
"<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" +
- "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" +
- "</form>"
+ "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"
+ end
assert_dom_equal expected, output_buffer
ensure
@@ -1542,7 +1598,7 @@ class FormHelperTest < ActionView::TestCase
assert_deprecated do
form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end
end
- expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>"
+ expected = whole_form("http://www.example.com", "some_form", "some_class")
assert_dom_equal expected, output_buffer
end
@@ -1552,7 +1608,8 @@ class FormHelperTest < ActionView::TestCase
form_for(:post, @post, :url => 'http://www.otherdomain.com') do |f| end
end
- assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer
+ assert_equal whole_form("http://www.otherdomain.com"), output_buffer
+ # assert_equal '<form action="http://www.otherdomain.com" method="post"></form>', output_buffer
end
def test_form_for_with_hash_url_option
@@ -1569,14 +1626,15 @@ class FormHelperTest < ActionView::TestCase
form_for(:post, @post, :url => @post) do |f| end
end
- expected = "<form action=\"/posts/123\" method=\"post\"></form>"
+ expected = whole_form("/posts/123")
+ # expected = "<form action=\"/posts/123\" method=\"post\"></form>"
assert_equal expected, output_buffer
end
def test_form_for_with_existing_object
form_for(@post) do |f| end
- expected = "<form action=\"/posts/123\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
+ expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put")
assert_equal expected, output_buffer
end
@@ -1587,7 +1645,7 @@ class FormHelperTest < ActionView::TestCase
form_for(post) do |f| end
- expected = "<form action=\"/posts\" class=\"new_post\" id=\"new_post\" method=\"post\"></form>"
+ expected = whole_form("/posts", "new_post", "new_post")
assert_equal expected, output_buffer
end
@@ -1595,14 +1653,14 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([@post, @comment]) {}
- expected = %(<form action="#{comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>)
+ expected = whole_form(comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
assert_dom_equal expected, output_buffer
end
def test_form_for_with_new_object_in_list
form_for([@post, @comment]) {}
- expected = %(<form action="#{comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
+ expected = whole_form(comments_path(@post), "new_comment", "new_comment")
assert_dom_equal expected, output_buffer
end
@@ -1610,21 +1668,21 @@ class FormHelperTest < ActionView::TestCase
@comment.save
form_for([:admin, @post, @comment]) {}
- expected = %(<form action="#{admin_comment_path(@post, @comment)}" class="edit_comment" id="edit_comment_1" method="post"><div style="margin:0;padding:0;display:inline"><input name="_method" type="hidden" value="put" /></div></form>)
+ expected = whole_form(admin_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put")
assert_dom_equal expected, output_buffer
end
def test_form_for_with_new_object_and_namespace_in_list
form_for([:admin, @post, @comment]) {}
- expected = %(<form action="#{admin_comments_path(@post)}" class="new_comment" id="new_comment" method="post"></form>)
+ expected = whole_form(admin_comments_path(@post), "new_comment", "new_comment")
assert_dom_equal expected, output_buffer
end
def test_form_for_with_existing_object_and_custom_url
form_for(@post, :url => "/super_posts") do |f| end
- expected = "<form action=\"/super_posts\" class=\"edit_post\" id=\"edit_post_123\" method=\"post\"><div style=\"margin:0;padding:0;display:inline\"><input name=\"_method\" type=\"hidden\" value=\"put\" /></div></form>"
+ expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put")
assert_equal expected, output_buffer
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 19b73aa810..65b5f5ccc1 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -177,17 +177,16 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_option_groups_from_collection_for_select
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
assert_dom_equal(
"<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
- option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
+ option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk")
)
end
+ def test_option_groups_from_collection_for_select_returns_html_safe_string
+ assert option_groups_from_collection_for_select(dummy_continents, "countries", "continent_name", "country_id", "country_name", "dk").html_safe?
+ end
+
def test_grouped_options_for_select_with_array
assert_dom_equal(
"<optgroup label=\"North America\"><option value=\"US\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup><optgroup label=\"Europe\"><option value=\"GB\">Great Britain</option>\n<option value=\"Germany\">Germany</option></optgroup>",
@@ -824,31 +823,21 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_grouped_collection_select
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
@post = Post.new
@post.origin = 'dk'
assert_dom_equal(
%Q{<select id="post_origin" name="post[origin]"><optgroup label="&lt;Africa&gt;"><option value="&lt;sa&gt;">&lt;South Africa&gt;</option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>},
- grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name)
)
end
def test_grouped_collection_select_under_fields_for
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
-
@post = Post.new
@post.origin = 'dk'
output_buffer = fields_for :post, @post do |f|
- concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name)
+ concat f.grouped_collection_select("origin", dummy_continents, :countries, :continent_name, :country_id, :country_name)
end
assert_dom_equal(
@@ -864,4 +853,9 @@ class FormOptionsHelperTest < ActionView::TestCase
Post.new("Babe went home", "Babe", "To a little house", "shh!"),
Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ]
end
+
+ def dummy_continents
+ [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ]
+ end
end
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index b75863bb6a..8a0f352bc0 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -8,6 +8,36 @@ class FormTagHelperTest < ActionView::TestCase
@controller = BasicController.new
end
+ def snowman(options = {})
+ method = options[:method]
+
+ txt = %{<div style="margin:0;padding:0;display:inline">}
+ txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
+ txt << %{</div>}
+ end
+
+ def form_text(action = "http://www.example.com", options = {})
+ remote, enctype, html_class, id = options.values_at(:remote, :enctype, :html_class, :id)
+
+ txt = %{<form accept-charset="UTF-8" action="#{action}"}
+ txt << %{ enctype="multipart/form-data"} if enctype
+ txt << %{ data-remote="true"} if remote
+ txt << %{ class="#{html_class}"} if html_class
+ txt << %{ id="#{id}"} if id
+ txt << %{ method="post">}
+ end
+
+ def whole_form(action = "http://www.example.com", options = {})
+ out = form_text(action, options) + snowman(options)
+
+ if block_given?
+ out << yield << "</form>"
+ end
+
+ out
+ end
+
def url_for(options)
if options.is_a?(Hash)
"http://www.example.com"
@@ -31,51 +61,57 @@ class FormTagHelperTest < ActionView::TestCase
def test_form_tag
actual = form_tag
- expected = %(<form action="http://www.example.com" method="post">)
+ expected = whole_form
assert_dom_equal expected, actual
end
def test_form_tag_multipart
actual = form_tag({}, { 'multipart' => true })
- expected = %(<form action="http://www.example.com" enctype="multipart/form-data" method="post">)
+ expected = whole_form("http://www.example.com", :enctype => true)
assert_dom_equal expected, actual
end
def test_form_tag_with_method_put
actual = form_tag({}, { :method => :put })
- expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>)
+ expected = whole_form("http://www.example.com", :method => :put)
assert_dom_equal expected, actual
end
def test_form_tag_with_method_delete
actual = form_tag({}, { :method => :delete })
- expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="delete" /></div>)
+
+ expected = whole_form("http://www.example.com", :method => :delete)
assert_dom_equal expected, actual
end
def test_form_tag_with_remote
actual = form_tag({}, :remote => true)
- expected = %(<form action="http://www.example.com" method="post" data-remote="true">)
+
+ expected = whole_form("http://www.example.com", :remote => true)
assert_dom_equal expected, actual
end
def test_form_tag_with_remote_false
actual = form_tag({}, :remote => false)
- expected = %(<form action="http://www.example.com" method="post">)
+
+ expected = whole_form
assert_dom_equal expected, actual
end
def test_form_tag_with_block_in_erb
- output_buffer = form_tag("http://example.com") { concat "Hello world!" }
+ output_buffer = form_tag("http://www.example.com") { concat "Hello world!" }
- expected = %(<form action="http://example.com" method="post">Hello world!</form>)
+ expected = whole_form { "Hello world!" }
assert_dom_equal expected, output_buffer
end
def test_form_tag_with_block_and_method_in_erb
- output_buffer = form_tag("http://example.com", :method => :put) { concat "Hello world!" }
+ output_buffer = form_tag("http://www.example.com", :method => :put) { concat "Hello world!" }
+
+ expected = whole_form("http://www.example.com", :method => "put") do
+ "Hello world!"
+ end
- expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0;display:inline'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>)
assert_dom_equal expected, output_buffer
end
diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb
index a6e760b0b6..c9edde8892 100644
--- a/actionpack/test/template/html-scanner/sanitizer_test.rb
+++ b/actionpack/test/template/html-scanner/sanitizer_test.rb
@@ -257,6 +257,10 @@ class SanitizerTest < ActionController::TestCase
assert_sanitized %{<a href=\"http://www.domain.com?var1=1&amp;var2=2\">my link</a>}
end
+ def test_should_sanitize_neverending_attribute
+ assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
+ end
+
protected
def assert_sanitized(input, expected = nil)
@sanitizer ||= HTML::WhiteListSanitizer.new
diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb
index 5076dfa70f..eb1e548672 100644
--- a/actionpack/test/template/log_subscriber_test.rb
+++ b/actionpack/test/template/log_subscriber_test.rb
@@ -1,22 +1,22 @@
require "abstract_unit"
-require "rails/log_subscriber/test_helper"
-require "action_view/railties/log_subscriber"
+require "active_support/log_subscriber/test_helper"
+require "action_view/log_subscriber"
require "controller/fake_models"
class AVLogSubscriberTest < ActiveSupport::TestCase
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@old_logger = ActionController::Base.logger
@view = ActionView::Base.new(ActionController::Base.view_paths, {})
Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH))
- Rails::LogSubscriber.add(:action_view, ActionView::Railties::LogSubscriber.new)
+ ActionView::LogSubscriber.attach_to :action_view
end
def teardown
super
- Rails::LogSubscriber.log_subscribers.clear
+ ActiveSupport::LogSubscriber.log_subscribers.clear
ActionController::Base.logger = @old_logger
end
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index ec5fe3d1d7..507cdca8d0 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -95,9 +95,9 @@ class TagHelperTest < ActionView::TestCase
assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
end
- def test_double_escaping_attributes
+ def test_tag_honors_html_safe_for_param_values
['1&amp;2', '1 &lt; 2', '&#8220;test&#8220;'].each do |escaped|
- assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped)
+ assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped.html_safe)
end
end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index 9b50ea8a42..a0c46f8a59 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -37,8 +37,12 @@ module ActionView
include SharedTests
test_case = self
- test "memoizes the _view" do
- assert_same _view, _view
+ test "memoizes the view" do
+ assert_same view, view
+ end
+
+ test "exposes view as _view for backwards compatibility" do
+ assert_same _view, view
end
test "works without testing a helper module" do
@@ -61,13 +65,13 @@ module ActionView
end
test "delegates notice to request.flash" do
- _view.request.flash.expects(:notice).with("this message")
- _view.notice("this message")
+ view.request.flash.expects(:notice).with("this message")
+ view.notice("this message")
end
test "delegates alert to request.flash" do
- _view.request.flash.expects(:alert).with("this message")
- _view.alert("this message")
+ view.request.flash.expects(:alert).with("this message")
+ view.alert("this message")
end
end
@@ -136,7 +140,7 @@ module ActionView
helper HelperThatInvokesProtectAgainstForgery
test "protect_from_forgery? in any helpers returns false" do
- assert !_view.help_me
+ assert !view.help_me
end
end
@@ -200,6 +204,15 @@ module ActionView
assert_match /Hello: EloyHello: Manfred/, render(:file => 'test/list')
end
+ test "is able to render partials from templates and also use instance variables after view has been referenced" do
+ @controller.controller_path = "test"
+
+ view
+
+ @customers = [stub(:name => 'Eloy'), stub(:name => 'Manfred')]
+ assert_match /Hello: EloyHello: Manfred/, render(:file => 'test/list')
+ end
+
end
class AssertionsTest < ActionView::TestCase
@@ -218,4 +231,26 @@ module ActionView
end
end
end
+
+ class RenderTemplateTest < ActionView::TestCase
+ test "supports specifying partials" do
+ controller.controller_path = "test"
+ render(:template => "test/calling_partial_with_layout")
+ assert_template :partial => "_partial_for_use_in_layout"
+ end
+
+ test "supports specifying locals (passing)" do
+ controller.controller_path = "test"
+ render(:template => "test/calling_partial_with_layout")
+ assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "David" }
+ end
+
+ test "supports specifying locals (failing)" do
+ controller.controller_path = "test"
+ render(:template => "test/calling_partial_with_layout")
+ assert_raise ActiveSupport::TestCase::Assertion, /Somebody else.*David/m do
+ assert_template :partial => "_partial_for_use_in_layout", :locals => { :name => "Somebody Else" }
+ end
+ end
+ end
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index d173c5be0a..d22b9fe406 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -435,6 +435,7 @@ class TextHelperTest < ActionView::TestCase
z39_scheme = 'z39.50r://host:696/db'
chrome_scheme = 'chrome://package/section/path'
view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme'
+ assert_equal generate_result(file_scheme), auto_link(file_scheme)
assert_equal generate_result(z39_scheme), auto_link(z39_scheme)
assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme)
assert_equal generate_result(view_source), auto_link(view_source)
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 299d6dd5bd..765beebfa3 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -40,19 +40,8 @@ class UrlHelperTest < ActiveSupport::TestCase
end
alias url_hash hash_for
- def test_url_for_escapes_urls
+ def test_url_for_does_not_escape_urls
assert_equal "/?a=b&c=d", url_for(abcd)
- assert_equal "/?a=b&amp;c=d", url_for(abcd(:escape => true))
- assert_equal "/?a=b&c=d", url_for(abcd(:escape => false))
- end
-
- def test_url_for_escaping_is_safety_aware
- assert url_for(abcd(:escape => true)).html_safe?, "escaped urls should be html_safe?"
- assert !url_for(abcd(:escape => false)).html_safe?, "non-escaped urls should not be html_safe?"
- end
-
- def test_url_for_escapes_url_once
- assert_equal "/?a=b&amp;c=d", url_for("/?a=b&amp;c=d")
end
def test_url_for_with_back
@@ -67,11 +56,6 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal 'javascript:history.back()', url_for(:back)
end
- def test_url_for_from_hash_doesnt_escape_ampersand
- path = url_for(hash_for(:foo => :bar, :baz => :quux))
- assert_equal '/?baz=quux&foo=bar', sort_query_string_params(path)
- end
-
# todo: missing test cases
def test_button_to_with_straight_url
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com")
@@ -81,8 +65,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
end
- def test_button_to_with_escaped_query
- assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2")
+ def test_button_to_with_html_safe_URL
+ assert_dom_equal "<form method=\"post\" action=\"http://www.example.com/q1=v1&amp;q2=v2\" class=\"button_to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
end
def test_button_to_with_query_and_no_name
@@ -151,13 +135,12 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_link_tag_with_query
expected = %{<a href="http://www.example.com?q1=v1&amp;q2=v2">Hello</a>}
- assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&amp;q2=v2")
+ assert_dom_equal expected, link_to("Hello", "http://www.example.com?q1=v1&q2=v2")
end
def test_link_tag_with_query_and_no_name
- link = link_to(nil, "http://www.example.com?q1=v1&amp;q2=v2")
expected = %{<a href="http://www.example.com?q1=v1&amp;q2=v2">http://www.example.com?q1=v1&amp;q2=v2</a>}
- assert_dom_equal expected, link
+ assert_dom_equal expected, link_to(nil, "http://www.example.com?q1=v1&q2=v2")
end
def test_link_tag_with_back
@@ -312,7 +295,7 @@ class UrlHelperTest < ActiveSupport::TestCase
@request = request_for_url("/?order=desc&page=1")
assert current_page?(hash_for(:order => "desc", :page => "1"))
- assert current_page?("http://www.example.com/?order=desc&amp;page=1")
+ assert current_page?("http://www.example.com/?order=desc&page=1")
end
def test_link_unless_current
@@ -345,7 +328,7 @@ class UrlHelperTest < ActiveSupport::TestCase
link_to_unless_current("Showing", "http://www.example.com/?order=asc")
@request = request_for_url("/?order=desc")
- assert_equal %{<a href="/?order=desc&page=2\">Showing</a>},
+ assert_equal %{<a href="/?order=desc&amp;page=2\">Showing</a>},
link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2))
assert_equal %{<a href="http://www.example.com/?order=desc&amp;page=2">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2")
@@ -415,7 +398,7 @@ class UrlHelperTest < ActiveSupport::TestCase
private
def sort_query_string_params(uri)
path, qs = uri.split('?')
- qs = qs.split('&').sort.join('&') if qs
+ qs = qs.split('&amp;').sort.join('&amp;') if qs
qs ? "#{path}?#{qs}" : path
end
end
@@ -429,6 +412,10 @@ class UrlHelperControllerTest < ActionController::TestCase
map.connect ":controller/:action/:id"
# match "/:controller(/:action(/:id))"
+
+ match 'url_helper_controller_test/url_helper/normalize_recall_params',
+ :to => UrlHelperController.action(:normalize_recall),
+ :as => :normalize_recall_params
end
def show_url_for
@@ -447,6 +434,14 @@ class UrlHelperControllerTest < ActionController::TestCase
render :inline => '<%= url_for(nil) %>'
end
+ def normalize_recall_params
+ render :inline => '<%= normalize_recall_params_path %>'
+ end
+
+ def recall_params_not_changed
+ render :inline => '<%= url_for(:action => :show_url_for) %>'
+ end
+
def rescue_action(e) raise e end
end
@@ -488,6 +483,16 @@ class UrlHelperControllerTest < ActionController::TestCase
get :show_named_route, :kind => 'url'
assert_equal 'http://testtwo.host/url_helper_controller_test/url_helper/show_named_route', @response.body
end
+
+ def test_recall_params_should_be_normalized_when_using_block_route
+ get :normalize_recall_params
+ assert_equal '/url_helper_controller_test/url_helper/normalize_recall_params', @response.body
+ end
+
+ def test_recall_params_should_not_be_changed_when_using_normal_route
+ get :recall_params_not_changed
+ assert_equal '/url_helper_controller_test/url_helper/show_url_for', @response.body
+ end
end
class TasksController < ActionController::Base
diff --git a/activemodel/README b/activemodel/README
index 3945a6da06..6f162ef408 100644
--- a/activemodel/README
+++ b/activemodel/README
@@ -177,7 +177,8 @@ functionality from the following modules:
end
- person = Person.new(:first_name => 'zoolander')
+ person = Person.new
+ person.first_name = 'zoolander'
person.valid? #=> false
{Learn more}[link:classes/ActiveModel/Validations.html]
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 1dba664539..1dba664539 100755..100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index a7690ba5b9..817640b178 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -46,7 +46,7 @@ module ActiveModel
# end
# end
#
- # Notice that whenever you include ActiveModel::AtributeMethods in your class,
+ # Notice that whenever you include ActiveModel::AttributeMethods in your class,
# it requires you to implement a <tt>attributes</tt> methods which returns a hash
# with each attribute name in your model as hash key and the attribute value as
# hash value.
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 257644b3fa..e7aad17021 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -2,11 +2,11 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/callbacks'
module ActiveModel
- # == Active Model Call Backs
+ # == Active Model Callbacks
#
# Provides an interface for any class to have Active Record like callbacks.
#
- # Like the Active Record methods, the call back chain is aborted as soon as
+ # Like the Active Record methods, the callback chain is aborted as soon as
# one of the methods in the chain returns false.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
@@ -15,13 +15,13 @@ module ActiveModel
# extend ActiveModel::Callbacks
# end
#
- # Then define a list of methods that you want call backs attached to:
+ # Then define a list of methods that you want callbacks attached to:
#
# define_model_callbacks :create, :update
#
# This will provide all three standard callbacks (before, around and after) around
# both the :create and :update methods. To implement, you need to wrap the methods
- # you want call backs on in a block so that the call backs get a chance to fire:
+ # you want callbacks on in a block so that the callbacks get a chance to fire:
#
# def create
# _run_create_callbacks do
@@ -61,7 +61,7 @@ module ActiveModel
#
# define_model_callbacks :initializer, :only => :after
#
- # Note, the <tt>:only => <type></tt> hash will apply to all call backs defined on
+ # Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
# that method call. To get around this you can call the define_model_callbacks
# method as many times as you need.
#
@@ -72,8 +72,8 @@ module ActiveModel
# Would create +after_create+, +before_update+ and +around_destroy+ methods only.
#
# You can pass in a class to before_<type>, after_<type> and around_<type>, in which
- # case the call back will call that class's <action>_<type> method passing the object
- # that the call back is being called on.
+ # case the callback will call that class's <action>_<type> method passing the object
+ # that the callback is being called on.
#
# class MyModel
# extend ActiveModel::Callbacks
@@ -84,7 +84,7 @@ module ActiveModel
#
# class AnotherClass
# def self.before_create( obj )
- # # obj is the MyModel instance that the call back is being called on
+ # # obj is the MyModel instance that the callback is being called on
# end
# end
#
diff --git a/activemodel/lib/active_model/deprecated_error_methods.rb b/activemodel/lib/active_model/deprecated_error_methods.rb
index dd8050c549..adc50773d9 100644
--- a/activemodel/lib/active_model/deprecated_error_methods.rb
+++ b/activemodel/lib/active_model/deprecated_error_methods.rb
@@ -16,7 +16,7 @@ module ActiveModel
end
def add_to_base(msg)
- ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
+ ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#add(:base, msg) instead"
self[:base] << msg
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index a82ce1bee0..4c80863e3a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -4,7 +4,7 @@ require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
module ActiveModel
- # == Active Model Call Backs
+ # == Active Model Dirty
#
# Provides a way to track changes in your object in the same way as
# Active Record does.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index f4c7400621..d42fc5291d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -3,6 +3,7 @@
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/reverse_merge'
require 'active_support/ordered_hash'
module ActiveModel
@@ -61,6 +62,8 @@ module ActiveModel
class Errors < ActiveSupport::OrderedHash
include DeprecatedErrorMethods
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
+
# Pass in the instance of the object that is using the errors object.
#
# class Person
@@ -162,15 +165,12 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- require 'builder' unless defined? ::Builder
- options[:root] ||= "errors"
- options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
-
- options[:builder].instruct! unless options.delete(:skip_instruct)
- options[:builder].errors do |e|
- to_a.each { |error| e.error(error) }
- end
+ to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
+ end
+
+ # Returns an array as JSON representation for this object.
+ def as_json(options=nil)
+ to_a
end
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
@@ -182,25 +182,44 @@ module ActiveModel
# If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
def add(attribute, message = nil, options = {})
message ||= :invalid
- message = generate_message(attribute, message, options) if message.is_a?(Symbol)
- message = message.call if message.is_a?(Proc)
+
+ if message.is_a?(Symbol)
+ message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
+ elsif message.is_a?(Proc)
+ message = message.call
+ end
+
self[attribute] << message
end
# Will add an error message to each of the attributes in +attributes+ that is empty.
- def add_on_empty(attributes, custom_message = nil)
+ def add_on_empty(attributes, options = {})
+ if options && !options.is_a?(Hash)
+ options = { :message => options }
+ ActiveSupport::Deprecation.warn \
+ "ActiveModel::Errors#add_on_empty(attributes, custom_message) has been deprecated.\n" +
+ "Instead of passing a custom_message pass an options Hash { :message => custom_message }."
+ end
+
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
- add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
+ add(attribute, :empty, options) if value.nil? || is_empty
end
end
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
- def add_on_blank(attributes, custom_message = nil)
+ def add_on_blank(attributes, options = {})
+ if options && !options.is_a?(Hash)
+ options = { :message => options }
+ ActiveSupport::Deprecation.warn \
+ "ActiveModel::Errors#add_on_blank(attributes, custom_message) has been deprecated.\n" +
+ "Instead of passing a custom_message pass an options Hash { :message => custom_message }."
+ end
+
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
- add(attribute, :blank, :default => custom_message) if value.blank?
+ add(attribute, :blank, options) if value.blank?
end
end
@@ -246,7 +265,7 @@ module ActiveModel
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model name,
# translated attribute name and the value are available for interpolation.
#
- # When using inheritence in your models, it will check all the inherited
+ # When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
# <tt>class Admin < User; end</tt> and you wanted the translation for
# the <tt>:blank</tt> error +message+ for the <tt>title</tt> +attribute+,
@@ -262,24 +281,31 @@ module ActiveModel
# <li><tt>errors.attributes.title.blank</tt></li>
# <li><tt>errors.messages.blank</tt></li>
# </ol>
- def generate_message(attribute, message = :invalid, options = {})
- message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
+ def generate_message(attribute, type = :invalid, options = {})
+ type = options.delete(:message) if options[:message].is_a?(Symbol)
+
+ if options[:default]
+ ActiveSupport::Deprecation.warn \
+ "ActiveModel::Errors#generate_message(attributes, custom_message) has been deprecated.\n" +
+ "Use ActiveModel::Errors#generate_message(attributes, :message => 'your message') instead."
+ options[:message] = options.delete(:default)
+ end
defaults = @base.class.lookup_ancestors.map do |klass|
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{message}",
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{message}" ]
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}",
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ]
end
- defaults << options.delete(:default)
- defaults << :"#{@base.class.i18n_scope}.errors.messages.#{message}"
- defaults << :"errors.attributes.#{attribute}.#{message}"
- defaults << :"errors.messages.#{message}"
+ defaults << options.delete(:message)
+ defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
+ defaults << :"errors.attributes.#{attribute}.#{type}"
+ defaults << :"errors.messages.#{type}"
defaults.compact!
defaults.flatten!
key = defaults.shift
- value = @base.send(:read_attribute_for_validation, attribute)
+ value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
:default => defaults,
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index a5977bf3cc..fe650053d9 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -56,7 +56,7 @@ module ActiveModel
# Returns a boolean that specifies whether the object has been persisted yet.
# This is used when calculating the URL for an object. If the object is
# not persisted, a form for that object, for instance, will be POSTed to the
- # collection. If it is persisted, a form for the object will put PUTed to the
+ # collection. If it is persisted, a form for the object will be PUT to the
# URL for the object.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
@@ -65,7 +65,7 @@ module ActiveModel
# == Naming
#
- # Model.model_name must returns a string with some convenience methods as
+ # Model.model_name must return a string with some convenience methods as
# :human and :partial_path. Check ActiveModel::Naming for more information.
#
def test_model_naming
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 14f8bf72dc..d0f36ce3b1 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -2,7 +2,6 @@ require 'singleton'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/string/conversions'
module ActiveModel
module Observing
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 3228cfed9a..0554677296 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -13,7 +13,7 @@ module ActiveModel
# extend ActiveModel::Translation
# end
#
- # TranslatedPerson.human_attribute_name('my_attribue')
+ # TranslatedPerson.human_attribute_name('my_attribute')
# #=> "My attribute"
#
# This also provides the required class methods for hooking into the
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 57487cf75a..5779ba3b29 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_model/errors'
+require 'active_model/validations/callbacks'
module ActiveModel
@@ -91,7 +92,7 @@ module ActiveModel
end
# Adds a validation method or block to the class. This is useful when
- # overriding the +validate+ instance method becomes too unwieldly and
+ # overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
#
# This can be done with a symbol pointing to a method:
@@ -164,8 +165,7 @@ module ActiveModel
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
- _run_validate_callbacks
- errors.empty?
+ run_validations!
ensure
self.validation_context = current_context
end
@@ -176,7 +176,7 @@ module ActiveModel
!valid?(context)
end
- # Hook method defining how an attribute value should be retieved. By default
+ # Hook method defining how an attribute value should be retrieved. By default
# this is assumed to be an instance named after the attribute. Override this
# method in subclasses should you need to retrieve the value for a given
# attribute differently:
@@ -194,6 +194,13 @@ module ActiveModel
# end
#
alias :read_attribute_for_validation :send
+
+ protected
+
+ def run_validations!
+ _run_validate_callbacks
+ errors.empty?
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 26b12b504b..99b8966def 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -1,4 +1,6 @@
module ActiveModel
+
+ # == Active Model Acceptance Validator
module Validations
class AcceptanceValidator < EachValidator
def initialize(options)
@@ -7,10 +9,10 @@ module ActiveModel
def validate_each(record, attribute, value)
unless value == options[:accept]
- record.errors.add(attribute, :accepted, :default => options[:message])
+ record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
end
end
-
+
def setup(klass)
# Note: instance_methods.map(&:to_s) is important for 1.9 compatibility
# as instance_methods returns symbols unlike 1.8 which returns strings.
@@ -22,29 +24,42 @@ module ActiveModel
end
module HelperMethods
- # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
+ # Encapsulates the pattern of wanting to validate the acceptance of a
+ # terms of service check box (or similar agreement). Example:
#
# class Person < ActiveRecord::Base
# validates_acceptance_of :terms_of_service
# validates_acceptance_of :eula, :message => "must be abided"
# end
#
- # If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
- # performed only if +terms_of_service+ is not +nil+ and by default on save.
+ # If the database column does not exist, the +terms_of_service+ attribute
+ # is entirely virtual. This check is performed only if +terms_of_service+
+ # is not +nil+ and by default on save.
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "must be accepted").
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
- # * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
- # makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
- # column, since the attribute is typecast from "1" to +true+ before validation.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:message</tt> - A custom error message (default is: "must be
+ # accepted").
+ # * <tt>:on</tt> - Specifies when this validation is active (default is
+ # <tt>:save</tt>, other options are <tt>:create</tt> and
+ # <tt>:update</tt>).
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
+ # is true).
+ # * <tt>:accept</tt> - Specifies value that is considered accepted.
+ # The default value is a string "1", which makes it easy to relate to
+ # an HTML checkbox. This should be set to +true+ if you are validating
+ # a database column, since the attribute is typecast from "1" to +true+
+ # before validation.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false
+ # value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (for example,
+ # <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or
+ # false value.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
new file mode 100644
index 0000000000..afd65d3dd5
--- /dev/null
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -0,0 +1,57 @@
+require 'active_support/callbacks'
+
+module ActiveModel
+ module Validations
+ module Callbacks
+ # == Active Model Validation callbacks
+ #
+ # Provides an interface for any class to have <tt>before_validation</tt> and
+ # <tt>after_validation</tt> callbacks.
+ #
+ # First, extend ActiveModel::Callbacks from the class you are creating:
+ #
+ # class MyModel
+ # include ActiveModel::Validations::Callbacks
+ #
+ # before_validation :do_stuff_before_validation
+ # after_validation :do_tuff_after_validation
+ # end
+ #
+ # Like other before_* callbacks if <tt>before_validation</tt> returns false
+ # then <tt>valid?</tt> will not be called.
+ extend ActiveSupport::Concern
+
+ included do
+ include ActiveSupport::Callbacks
+ define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ end
+
+ module ClassMethods
+ def before_validation(*args, &block)
+ options = args.last
+ if options.is_a?(Hash) && options[:on]
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "self.validation_context == :#{options[:on]}"
+ end
+ set_callback(:validation, :before, *args, &block)
+ end
+
+ def after_validation(*args, &block)
+ options = args.extract_options!
+ options[:prepend] = true
+ options[:if] = Array.wrap(options[:if])
+ options[:if] << "!halted && value != false"
+ options[:if] << "self.validation_context == :#{options[:on]}" if options[:on]
+ set_callback(:validation, :after, *(args << options), &block)
+ end
+ end
+
+ protected
+
+ # Overwrite run validations to include callbacks.
+ def run_validations!
+ _run_validation_callbacks { super }
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 51445343f2..3a80893866 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -1,45 +1,59 @@
module ActiveModel
+
+ # == Active Model Confirmation Validator
module Validations
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
confirmed = record.send(:"#{attribute}_confirmation")
return if confirmed.nil? || value == confirmed
- record.errors.add(attribute, :confirmation, :default => options[:message])
+ record.errors.add(attribute, :confirmation, options)
end
-
+
def setup(klass)
- klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
+ klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" })
end
end
module HelperMethods
- # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
+ # Encapsulates the pattern of wanting to validate a password or email
+ # address field with a confirmation. For example:
#
# Model:
# class Person < ActiveRecord::Base
# validates_confirmation_of :user_name, :password
- # validates_confirmation_of :email_address, :message => "should match confirmation"
+ # validates_confirmation_of :email_address,
+ # :message => "should match confirmation"
# end
#
# View:
# <%= password_field "person", "password" %>
# <%= password_field "person", "password_confirmation" %>
#
- # The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password.
- # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed
- # only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence
- # check for the confirmation attribute:
+ # The added +password_confirmation+ attribute is virtual; it exists only
+ # as an in-memory attribute for validating the password. To achieve this,
+ # the validation adds accessors to the model for the confirmation
+ # attribute.
+ #
+ # NOTE: This check is performed only if +password_confirmation+ is not
+ # +nil+, and by default only on save. To require confirmation, make sure
+ # to add a presence check for the confirmation attribute:
#
# validates_presence_of :password_confirmation, :if => :password_changed?
#
# Configuration options:
- # * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation").
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # * <tt>:message</tt> - A custom error message (default is: "doesn't match
+ # confirmation").
+ # * <tt>:on</tt> - Specifies when this validation is active (default is
+ # <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false
+ # value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (e.g.
+ # <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 2ee78f5dd2..4138892786 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,4 +1,6 @@
module ActiveModel
+
+ # == Active Model Exclusion Validator
module Validations
class ExclusionValidator < EachValidator
def check_validity!
@@ -7,8 +9,9 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- return unless options[:in].include?(value)
- record.errors.add(attribute, :exclusion, :default => options[:message], :value => value)
+ if options[:in].include?(value)
+ record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index c34c860d4d..104f403492 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,14 +1,16 @@
module ActiveModel
+
+ # == Active Model Format Validator
module Validations
class FormatValidator < EachValidator
def validate_each(record, attribute, value)
if options[:with] && value.to_s !~ options[:with]
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ record.errors.add(attribute, :invalid, options.except(:with).merge!(:value => value))
elsif options[:without] && value.to_s =~ options[:without]
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ record.errors.add(attribute, :invalid, options.except(:without).merge!(:value => value))
end
end
-
+
def check_validity!
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 446646d247..049b093618 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,4 +1,6 @@
module ActiveModel
+
+ # == Active Model Inclusion Validator
module Validations
class InclusionValidator < EachValidator
def check_validity!
@@ -7,8 +9,9 @@ module ActiveModel
end
def validate_each(record, attribute, value)
- return if options[:in].include?(value)
- record.errors.add(attribute, :inclusion, :default => options[:message], :value => value)
+ unless options[:in].include?(value)
+ record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index d7218f4f52..c8a77ad666 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,10 +1,13 @@
module ActiveModel
+
+ # == Active Model Length Validator
module Validations
class LengthValidator < EachValidator
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
+ RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
@@ -37,7 +40,8 @@ module ActiveModel
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- custom_message = options[:message] || options[MESSAGES[key]]
+ default_message = options[MESSAGES[key]]
+ options[:message] ||= default_message if default_message
valid_value = if key == :maximum
value.nil? || value.size.send(validity_check, check_value)
@@ -46,7 +50,9 @@ module ActiveModel
end
next if valid_value
- record.errors.add(attribute, MESSAGES[key], :default => custom_message, :count => check_value)
+
+ record.errors.add(attribute, MESSAGES[key],
+ options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
end
end
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 062b4cd17f..b6aff7aa6b 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -1,10 +1,14 @@
module ActiveModel
+
+ # == Active Model Numericality Validator
module Validations
class NumericalityValidator < EachValidator
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
:odd => :odd?, :even => :even? }.freeze
+ RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
+
def initialize(options)
super(options.reverse_merge(:only_integer => false, :allow_nil => false))
end
@@ -26,13 +30,13 @@ module ActiveModel
return if options[:allow_nil] && raw_value.nil?
unless value = parse_raw_value_as_a_number(raw_value)
- record.errors.add(attr_name, :not_a_number, :value => raw_value, :default => options[:message])
+ record.errors.add(attr_name, :not_a_number, filtered_options(raw_value))
return
end
if options[:only_integer]
unless value = parse_raw_value_as_an_integer(raw_value)
- record.errors.add(attr_name, :not_an_integer, :value => raw_value, :default => options[:message])
+ record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
return
end
end
@@ -41,14 +45,14 @@ module ActiveModel
case option
when :odd, :even
unless value.to_i.send(CHECKS[option])
- record.errors.add(attr_name, option, :value => value, :default => options[:message])
+ record.errors.add(attr_name, option, filtered_options(value))
end
else
option_value = option_value.call(record) if option_value.is_a?(Proc)
option_value = record.send(option_value) if option_value.is_a?(Symbol)
unless value.send(CHECKS[option], option_value)
- record.errors.add(attr_name, option, :default => options[:message], :value => value, :count => option_value)
+ record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
end
end
end
@@ -73,6 +77,9 @@ module ActiveModel
raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
end
+ def filtered_options(value)
+ options.except(*RESERVED_OPTIONS).merge!(:value => value)
+ end
end
module HelperMethods
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index b319f4834b..28c4640b17 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,10 +1,12 @@
require 'active_support/core_ext/object/blank'
module ActiveModel
+
+ # == Active Model Presence Validator
module Validations
class PresenceValidator < EachValidator
def validate(record)
- record.errors.add_on_blank(attributes, options[:message])
+ record.errors.add_on_blank(attributes, options)
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 90b244228a..0674640925 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/hash/slice'
module ActiveModel
+
+ # == Active Model validates method
module Validations
module ClassMethods
# This method is a shortcut to all default validators and any custom
@@ -20,7 +22,7 @@ module ActiveModel
# validates :username, :presence => true
# validates :username, :uniqueness => true
#
- # The power of the +validates+ method comes when using cusom validators
+ # The power of the +validates+ method comes when using custom validators
# and default validators in one call for a given attribute e.g.
#
# class EmailValidator < ActiveModel::EachValidator
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 6dbde5bfad..200efd4eb5 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -9,7 +9,6 @@ module ActiveModel
end
module ClassMethods
-
# Passes the record off to the class or classes specified and allows them
# to add errors based on more complex conditions.
#
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index dd9e39843e..689c617177 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -109,7 +109,7 @@ module ActiveModel #:nodoc:
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
end
- # Accepts options that will be made availible through the +options+ reader.
+ # Accepts options that will be made available through the +options+ reader.
def initialize(options)
@options = options
end
@@ -155,7 +155,7 @@ module ActiveModel #:nodoc:
end
end
- # Override this method in subclasses with the validation logic, adding
+ # Override this method in subclasses with the validation logic, adding
# errors to the records +errors+ array where necessary.
def validate_each(record, attribute, value)
raise NotImplementedError
diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb
index a81584bbad..a32f11484a 100644
--- a/activemodel/test/cases/helper.rb
+++ b/activemodel/test/cases/helper.rb
@@ -6,6 +6,7 @@ $:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
require 'config'
require 'active_model'
require 'active_support/core_ext/string/access'
+require 'active_support/core_ext/hash/except'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb
new file mode 100644
index 0000000000..1cf09758f9
--- /dev/null
+++ b/activemodel/test/cases/validations/callbacks_test.rb
@@ -0,0 +1,78 @@
+# encoding: utf-8
+require 'cases/helper'
+
+class Dog
+ include ActiveModel::Validations
+ include ActiveModel::Validations::Callbacks
+
+ attr_accessor :name
+ attr_writer :history
+
+ def history
+ @history ||= []
+ end
+end
+
+class DogWithMethodCallbacks < Dog
+ before_validation :set_before_validation_marker
+ after_validation :set_after_validation_marker
+
+ def set_before_validation_marker; self.history << 'before_validation_marker'; end
+ def set_after_validation_marker; self.history << 'after_validation_marker' ; end
+end
+
+class DogValidtorsAreProc < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ after_validation { self.history << 'after_validation_marker' }
+end
+
+class DogWithTwoValidators < Dog
+ before_validation { self.history << 'before_validation_marker1' }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogValidatorReturningFalse < Dog
+ before_validation { false }
+ before_validation { self.history << 'before_validation_marker2' }
+end
+
+class DogWithMissingName < Dog
+ before_validation { self.history << 'before_validation_marker' }
+ validates_presence_of :name
+end
+
+class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called
+ d = DogWithMethodCallbacks.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_with_proc
+ d = DogValidtorsAreProc.new
+ d.valid?
+ assert_equal ['before_validation_marker', 'after_validation_marker'], d.history
+ end
+
+ def test_before_validation_and_after_validation_callbacks_should_be_called_in_declared_order
+ d = DogWithTwoValidators.new
+ d.valid?
+ assert_equal ['before_validation_marker1', 'before_validation_marker2'], d.history
+ end
+
+ def test_further_callbacks_should_not_be_called_if_before_validation_returns_false
+ d = DogValidatorReturningFalse.new
+ output = d.valid?
+ assert_equal [], d.history
+ assert_equal false, output
+ end
+
+ def test_validation_test_should_be_done
+ d = DogWithMissingName.new
+ output = d.valid?
+ assert_equal ['before_validation_marker'], d.history
+ assert_equal false, output
+ end
+
+end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 58a8d179ad..0679e67f84 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -8,131 +8,131 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
@person = Person.new
end
- # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value)
+ # validates_inclusion_of: generate_message(attr_name, :inclusion, :message => custom_message, :value => value)
def test_generate_message_inclusion_with_default_message
- assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title')
+ assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :value => 'title')
end
def test_generate_message_inclusion_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :message => 'custom message %{value}', :value => 'title')
end
- # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value)
+ # validates_exclusion_of: generate_message(attr_name, :exclusion, :message => custom_message, :value => value)
def test_generate_message_exclusion_with_default_message
- assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title')
+ assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :value => 'title')
end
def test_generate_message_exclusion_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :message => 'custom message %{value}', :value => 'title')
end
- # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_format_of: generate_message(attr_name, :invalid, :message => custom_message, :value => value)
def test_generate_message_invalid_with_default_message
- assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :value => 'title')
end
def test_generate_message_invalid_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title')
end
- # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message])
+ # validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message)
def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation, :default => nil)
+ assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation)
end
def test_generate_message_confirmation_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :default => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :message => 'custom message')
end
- # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message])
+ # validates_acceptance_of: generate_message(attr_name, :accepted, :message => custom_message)
def test_generate_message_accepted_with_default_message
- assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted, :default => nil)
+ assert_equal "must be accepted", @person.errors.generate_message(:title, :accepted)
end
def test_generate_message_accepted_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :default => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :message => 'custom message')
end
- # add_on_empty: generate_message(attr, :empty, :default => custom_message)
+ # add_on_empty: generate_message(attr, :empty, :message => custom_message)
def test_generate_message_empty_with_default_message
- assert_equal "can't be empty", @person.errors.generate_message(:title, :empty, :default => nil)
+ assert_equal "can't be empty", @person.errors.generate_message(:title, :empty)
end
def test_generate_message_empty_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :default => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :message => 'custom message')
end
- # add_on_blank: generate_message(attr, :blank, :default => custom_message)
+ # add_on_blank: generate_message(attr, :blank, :message => custom_message)
def test_generate_message_blank_with_default_message
- assert_equal "can't be blank", @person.errors.generate_message(:title, :blank, :default => nil)
+ assert_equal "can't be blank", @person.errors.generate_message(:title, :blank)
end
def test_generate_message_blank_with_custom_message
- assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :default => 'custom message')
+ assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :message => 'custom message')
end
- # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end)
+ # validates_length_of: generate_message(attr, :too_long, :message => custom_message, :count => option_value.end)
def test_generate_message_too_long_with_default_message
- assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :default => nil, :count => 10)
+ assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :count => 10)
end
def test_generate_message_too_long_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :default => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :message => 'custom message %{count}', :count => 10)
end
- # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
+ # validates_length_of: generate_message(attr, :too_short, :default => custom_message, :count => option_value.begin)
def test_generate_message_too_short_with_default_message
- assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :default => nil, :count => 10)
+ assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :count => 10)
end
def test_generate_message_too_short_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :default => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :message => 'custom message %{count}', :count => 10)
end
- # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value)
+ # validates_length_of: generate_message(attr, :wrong_length, :message => custom_message, :count => option_value)
def test_generate_message_wrong_length_with_default_message
- assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10)
+ assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :count => 10)
end
def test_generate_message_wrong_length_with_custom_message
- assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :default => 'custom message %{count}', :count => 10)
+ assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :message => 'custom message %{count}', :count => 10)
end
- # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message])
+ # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :message => custom_message)
def test_generate_message_not_a_number_with_default_message
- assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title')
+ assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :value => 'title')
end
def test_generate_message_not_a_number_with_custom_message
- assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :message => 'custom message %{value}', :value => 'title')
end
- # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message])
+ # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => custom_message)
def test_generate_message_greater_than_with_default_message
- assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :value => 'title', :count => 10)
end
def test_generate_message_greater_than_or_equal_to_with_default_message
- assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :value => 'title', :count => 10)
end
def test_generate_message_equal_to_with_default_message
- assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :value => 'title', :count => 10)
end
def test_generate_message_less_than_with_default_message
- assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :value => 'title', :count => 10)
end
def test_generate_message_less_than_or_equal_to_with_default_message
- assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :value => 'title', :count => 10)
end
def test_generate_message_odd_with_default_message
- assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :value => 'title', :count => 10)
end
def test_generate_message_even_with_default_message
- assert_equal "must be even", @person.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10)
+ assert_equal "must be even", @person.errors.generate_message(:title, :even, :value => 'title', :count => 10)
end
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 547d80f46e..e9f0e430fe 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -22,23 +22,23 @@ class I18nValidationTest < ActiveModel::TestCase
end
def test_errors_add_on_empty_generates_message
- @person.errors.expects(:generate_message).with(:title, :empty, {:default => nil})
+ @person.errors.expects(:generate_message).with(:title, :empty, {})
@person.errors.add_on_empty :title
end
def test_errors_add_on_empty_generates_message_with_custom_default_message
- @person.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'})
- @person.errors.add_on_empty :title, 'custom'
+ @person.errors.expects(:generate_message).with(:title, :empty, {:message => 'custom'})
+ @person.errors.add_on_empty :title, :message => 'custom'
end
def test_errors_add_on_blank_generates_message
- @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
+ @person.errors.expects(:generate_message).with(:title, :blank, {})
@person.errors.add_on_blank :title
end
def test_errors_add_on_blank_generates_message_with_custom_default_message
- @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @person.errors.add_on_blank :title, 'custom'
+ @person.errors.expects(:generate_message).with(:title, :blank, {:message => 'custom'})
+ @person.errors.add_on_blank :title, :message => 'custom'
end
def test_full_message_encoding
@@ -62,441 +62,272 @@ class I18nValidationTest < ActiveModel::TestCase
end
# ActiveModel::Validations
+
+ # A set of common cases for ActiveModel::Validations message generation that
+ # are used to generate tests to keep things DRY
+ #
+ COMMON_CASES = [
+ # [ case, validation_options, generate_message_options]
+ [ "given no options", {}, {}],
+ [ "given custom message", {:message => "custom"}, {:message => "custom"}],
+ [ "given if condition", {:if => lambda { true }}, {}],
+ [ "given unless condition", {:unless => lambda { false }}, {}],
+ [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
+ ]
+
# validates_confirmation_of w/ mocha
- def test_validates_confirmation_of_generates_message
- Person.validates_confirmation_of :title
- @person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil})
- @person.valid?
- end
- def test_validates_confirmation_of_generates_message_with_custom_default_message
- Person.validates_confirmation_of :title, :message => 'custom'
- @person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_confirmation_of on generated message #{name}" do
+ Person.validates_confirmation_of :title, validation_options
+ @person.title_confirmation = 'foo'
+ @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options)
+ @person.valid?
+ end
end
# validates_acceptance_of w/ mocha
- def test_validates_acceptance_of_generates_message
- Person.validates_acceptance_of :title, :allow_nil => false
- @person.errors.expects(:generate_message).with(:title, :accepted, {:default => nil})
- @person.valid?
- end
-
- def test_validates_acceptance_of_generates_message_with_custom_default_message
- Person.validates_acceptance_of :title, :message => 'custom', :allow_nil => false
- @person.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_acceptance_of on generated message #{name}" do
+ Person.validates_acceptance_of :title, validation_options.merge(:allow_nil => false)
+ @person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options)
+ @person.valid?
+ end
end
# validates_presence_of w/ mocha
- def test_validates_presence_of_generates_message
- Person.validates_presence_of :title
- @person.errors.expects(:generate_message).with(:title, :blank, {:default => nil})
- @person.valid?
- end
-
- def test_validates_presence_of_generates_message_with_custom_default_message
- Person.validates_presence_of :title, :message => 'custom'
- @person.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_presence_of on generated message #{name}" do
+ Person.validates_presence_of :title, validation_options
+ @person.errors.expects(:generate_message).with(:title, :blank, generate_message_options)
+ @person.valid?
+ end
end
- # validates_length_of :within w/ mocha
+ # validates_length_of :within too short w/ mocha
- def test_validates_length_of_within_generates_message_with_title_too_short
- Person.validates_length_of :title, :within => 3..5
- @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_length_of for :withing on generated message when too short #{name}" do
+ Person.validates_length_of :title, validation_options.merge(:within => 3..5)
+ @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(:count => 3))
+ @person.valid?
+ end
end
- def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message
- Person.validates_length_of :title, :within => 3..5, :too_short => 'custom'
- @person.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'})
- @person.valid?
- end
-
- def test_validates_length_of_within_generates_message_with_title_too_long
- Person.validates_length_of :title, :within => 3..5
- @person.title = 'this title is too long'
- @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil})
- @person.valid?
- end
+ # validates_length_of :within too long w/ mocha
- def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message
- Person.validates_length_of :title, :within => 3..5, :too_long => 'custom'
- @person.title = 'this title is too long'
- @person.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_length_of for :too_long generated message #{name}" do
+ Person.validates_length_of :title, validation_options.merge(:within => 3..5)
+ @person.title = 'this title is too long'
+ @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(:count => 5))
+ @person.valid?
+ end
end
# validates_length_of :is w/ mocha
- def test_validates_length_of_is_generates_message
- Person.validates_length_of :title, :is => 5
- @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil})
- @person.valid?
- end
-
- def test_validates_length_of_is_generates_message_with_custom_default_message
- Person.validates_length_of :title, :is => 5, :message => 'custom'
- @person.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_length_of for :is on generated message #{name}" do
+ Person.validates_length_of :title, validation_options.merge(:is => 5)
+ @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(:count => 5))
+ @person.valid?
+ end
end
# validates_format_of w/ mocha
- def test_validates_format_of_generates_message
- Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @person.title = '72x'
- @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil})
- @person.valid?
- end
-
- def test_validates_format_of_generates_message_with_custom_default_message
- Person.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom'
- @person.title = '72x'
- @person.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_format_of on generated message #{name}" do
+ Person.validates_format_of :title, validation_options.merge(:with => /^[1-9][0-9]*$/)
+ @person.title = '72x'
+ @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x'))
+ @person.valid?
+ end
end
# validates_inclusion_of w/ mocha
- def test_validates_inclusion_of_generates_message
- Person.validates_inclusion_of :title, :in => %w(a b c)
- @person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil})
- @person.valid?
- end
-
- def test_validates_inclusion_of_generates_message_with_custom_default_message
- Person.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom'
- @person.title = 'z'
- @person.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_inclusion_of on generated message #{name}" do
+ Person.validates_inclusion_of :title, validation_options.merge(:in => %w(a b c))
+ @person.title = 'z'
+ @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z'))
+ @person.valid?
+ end
end
# validates_exclusion_of w/ mocha
- def test_validates_exclusion_of_generates_message
- Person.validates_exclusion_of :title, :in => %w(a b c)
- @person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil})
- @person.valid?
- end
-
- def test_validates_exclusion_of_generates_message_with_custom_default_message
- Person.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom'
- @person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_exclusion_of generated message #{name}" do
+ Person.validates_exclusion_of :title, validation_options.merge(:in => %w(a b c))
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a'))
+ @person.valid?
+ end
end
# validates_numericality_of without :only_integer w/ mocha
- def test_validates_numericality_of_generates_message
- Person.validates_numericality_of :title
- @person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil})
- @person.valid?
- end
-
- def test_validates_numericality_of_generates_message_with_custom_default_message
- Person.validates_numericality_of :title, :message => 'custom'
- @person.title = 'a'
- @person.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_numericality_of generated message #{name}" do
+ Person.validates_numericality_of :title, validation_options
+ @person.title = 'a'
+ @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(:value => 'a'))
+ @person.valid?
+ end
end
# validates_numericality_of with :only_integer w/ mocha
- def test_validates_numericality_of_only_integer_generates_message
- Person.validates_numericality_of :title, :only_integer => true
- @person.title = '0.0'
- @person.errors.expects(:generate_message).with(:title, :not_an_integer, {:value => '0.0', :default => nil})
- @person.valid?
- end
-
- def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message
- Person.validates_numericality_of :title, :only_integer => true, :message => 'custom'
- @person.title = '0.0'
- @person.errors.expects(:generate_message).with(:title, :not_an_integer, {:value => '0.0', :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_numericality_of for :only_integer on generated message #{name}" do
+ Person.validates_numericality_of :title, validation_options.merge(:only_integer => true)
+ @person.title = '0.0'
+ @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(:value => '0.0'))
+ @person.valid?
+ end
end
# validates_numericality_of :odd w/ mocha
- def test_validates_numericality_of_odd_generates_message
- Person.validates_numericality_of :title, :only_integer => true, :odd => true
- @person.title = 0
- @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil})
- @person.valid?
- end
-
- def test_validates_numericality_of_odd_generates_message_with_custom_default_message
- Person.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom'
- @person.title = 0
- @person.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_numericality_of for :odd on generated message #{name}" do
+ Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :odd => true)
+ @person.title = 0
+ @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(:value => 0))
+ @person.valid?
+ end
end
# validates_numericality_of :less_than w/ mocha
- def test_validates_numericality_of_less_than_generates_message
- Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @person.title = 1
- @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil})
- @person.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_numericality_of for :less_than on generated message #{name}" do
+ Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :less_than => 0)
+ @person.title = 1
+ @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(:value => 1, :count => 0))
+ @person.valid?
+ end
end
- def test_validates_numericality_of_less_than_odd_generates_message_with_custom_default_message
- Person.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom'
- @person.title = 1
- @person.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'})
- @person.valid?
- end
- # validates_confirmation_of w/o mocha
+ # To make things DRY this macro is defined to define 3 tests for every validation case.
+ def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
+ # test "validates_confirmation_of finds custom model key translation when blank"
+ test "#{validation} finds custom model key translation when #{error_type}" do
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
- def test_validates_confirmation_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:confirmation => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}}
+ yield(@person, {})
+ @person.valid?
+ assert_equal ['custom message'], @person.errors[:title]
+ end
- Person.validates_confirmation_of :title
- @person.title_confirmation = 'foo'
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
+ # test "validates_confirmation_of finds custom model key translation with interpolation when blank"
+ test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}}
+ I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
- def test_validates_confirmation_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:confirmation => 'global message'}}
+ yield(@person, {:extra => "extra information"})
+ @person.valid?
+ assert_equal ['custom message with extra information'], @person.errors[:title]
+ end
- Person.validates_confirmation_of :title
- @person.title_confirmation = 'foo'
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
- end
+ # test "validates_confirmation_of finds global default key translation when blank"
+ test "#{validation} finds global default key translation when #{error_type}" do
+ I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
- # validates_acceptance_of w/o mocha
-
- def test_validates_acceptance_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:accepted => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}}
-
- Person.validates_acceptance_of :title, :allow_nil => false
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ yield(@person, {})
+ @person.valid?
+ assert_equal ['global message'], @person.errors[:title]
+ end
end
- def test_validates_acceptance_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:accepted => 'global message'}}
+ # validates_confirmation_of w/o mocha
- Person.validates_acceptance_of :title, :allow_nil => false
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_confirmation_of", :confirmation do |person, options_to_merge|
+ Person.validates_confirmation_of :title, options_to_merge
+ person.title_confirmation = 'foo'
end
- # validates_presence_of w/o mocha
-
- def test_validates_presence_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:blank => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}}
+ # validates_acceptance_of w/o mocha
- Person.validates_presence_of :title
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge|
+ Person.validates_acceptance_of :title, options_to_merge.merge(:allow_nil => false)
end
- def test_validates_presence_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:blank => 'global message'}}
+ # validates_presence_of w/o mocha
- Person.validates_presence_of :title
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_presence_of", :blank do |person, options_to_merge|
+ Person.validates_presence_of :title, options_to_merge
end
# validates_length_of :within w/o mocha
- def test_validates_length_of_within_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:too_short => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}}
-
- Person.validates_length_of :title, :within => 3..5
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge|
+ Person.validates_length_of :title, options_to_merge.merge(:within => 3..5)
end
- def test_validates_length_of_within_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:too_short => 'global message'}}
-
- Person.validates_length_of :title, :within => 3..5
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_length_of", :too_long do |person, options_to_merge|
+ Person.validates_length_of :title, options_to_merge.merge(:within => 3..5)
+ person.title = "too long"
end
# validates_length_of :is w/o mocha
- def test_validates_length_of_is_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}}
-
- Person.validates_length_of :title, :is => 5
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_length_of_is_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:wrong_length => 'global message'}}
-
- Person.validates_length_of :title, :is => 5
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge|
+ Person.validates_length_of :title, options_to_merge.merge(:is => 5)
end
# validates_format_of w/o mocha
- def test_validates_format_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:invalid => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
-
- Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_format_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:invalid => 'global message'}}
-
- Person.validates_format_of :title, :with => /^[1-9][0-9]*$/
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
+ Person.validates_format_of :title, options_to_merge.merge(:with => /^[1-9][0-9]*$/)
end
# validates_inclusion_of w/o mocha
- def test_validates_inclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:inclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}}
-
- Person.validates_inclusion_of :title, :in => %w(a b c)
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_inclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:inclusion => 'global message'}}
-
- Person.validates_inclusion_of :title, :in => %w(a b c)
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge|
+ Person.validates_inclusion_of :title, options_to_merge.merge(:in => %w(a b c))
end
# validates_exclusion_of w/o mocha
- def test_validates_exclusion_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:exclusion => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}}
-
- Person.validates_exclusion_of :title, :in => %w(a b c)
- @person.title = 'a'
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_exclusion_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:exclusion => 'global message'}}
-
- Person.validates_exclusion_of :title, :in => %w(a b c)
- @person.title = 'a'
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge|
+ Person.validates_exclusion_of :title, options_to_merge.merge(:in => %w(a b c))
+ person.title = 'a'
end
# validates_numericality_of without :only_integer w/o mocha
- def test_validates_numericality_of_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
-
- Person.validates_numericality_of :title
- @person.title = 'a'
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_numericality_of_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:not_a_number => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true
- @person.title = 'a'
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_numericality_of", :not_a_number do |person, options_to_merge|
+ Person.validates_numericality_of :title, options_to_merge
+ person.title = 'a'
end
# validates_numericality_of with :only_integer w/o mocha
- def test_validates_numericality_of_only_integer_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:not_an_integer => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:not_an_integer => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true
- @person.title = '1.0'
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_numericality_of_only_integer_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:not_an_integer => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true
- @person.title = '1.0'
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge|
+ Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true)
+ person.title = '1.0'
end
# validates_numericality_of :odd w/o mocha
- def test_validates_numericality_of_odd_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:odd => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true, :odd => true
- @person.title = 0
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_numericality_of_odd_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:odd => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true, :odd => true
- @person.title = 0
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge|
+ Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :odd => true)
+ person.title = 0
end
# validates_numericality_of :less_than w/o mocha
- def test_validates_numericality_of_less_than_finds_custom_model_key_translation
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:less_than => 'custom message'}}}}}}
- I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @person.title = 1
- @person.valid?
- assert_equal ['custom message'], @person.errors[:title]
- end
-
- def test_validates_numericality_of_less_than_finds_global_default_translation
- I18n.backend.store_translations 'en', :errors => {:messages => {:less_than => 'global message'}}
-
- Person.validates_numericality_of :title, :only_integer => true, :less_than => 0
- @person.title = 1
- @person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge|
+ Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :less_than => 0)
+ person.title = 1
end
# test with validates_with
diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb
index 8e77a0222e..e1d7d40c47 100644
--- a/activemodel/test/cases/validations/numericality_validation_test.rb
+++ b/activemodel/test/cases/validations/numericality_validation_test.rb
@@ -4,6 +4,8 @@ require 'cases/helper'
require 'models/topic'
require 'models/person'
+require 'bigdecimal'
+
class NumericalityValidationTest < ActiveModel::TestCase
def teardown
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index b1450586a8..510c13a7c3 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -32,7 +32,7 @@ class PresenceValidationTest < ActiveModel::TestCase
assert t.valid?
end
- test 'accepts array arguments' do
+ def test_accepts_array_arguments
Topic.validates_presence_of %w(title content)
t = Topic.new
assert t.invalid?
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index af195af080..e94d8ce88c 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -6,6 +6,9 @@ require 'models/reply'
require 'models/custom_reader'
require 'models/automobile'
+require 'active_support/json'
+require 'active_support/xml_mini'
+
class ValidationsTest < ActiveModel::TestCase
def setup
@@ -83,10 +86,9 @@ class ValidationsTest < ActiveModel::TestCase
r = Reply.new
r.content = "Mismatch"
r.valid?
- r.errors[:base] << "Reply is not dignifying"
+ r.errors.add(:base, "Reply is not dignifying")
- errors = []
- r.errors.to_a.each { |error| errors << error }
+ errors = r.errors.to_a.inject([]) { |result, error| result + [error] }
assert_equal ["Reply is not dignifying"], r.errors[:base]
@@ -95,6 +97,22 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal 2, r.errors.count
end
+ def test_errors_on_base_with_symbol_message
+ r = Reply.new
+ r.content = "Mismatch"
+ r.valid?
+ r.errors.add(:base, :invalid)
+
+ errors = r.errors.to_a.inject([]) { |result, error| result + [error] }
+
+ assert_equal ["is invalid"], r.errors[:base]
+
+ assert errors.include?("Title is Empty")
+ assert errors.include?("is invalid")
+
+ assert_equal 2, r.errors.count
+ end
+
def test_errors_empty_after_errors_on_check
t = Topic.new
assert t.errors[:id].empty?
@@ -143,12 +161,18 @@ class ValidationsTest < ActiveModel::TestCase
end
end
- def test_errors_to_xml
- r = Reply.new :title => "Wrong Create"
- assert r.invalid?
- xml = r.errors.to_xml(:skip_instruct => true)
- assert_equal "<errors>", xml.first(8)
- assert xml.include?("<error>Content is Empty</error>")
+ def test_errors_conversions
+ Topic.validates_presence_of %w(title content)
+ t = Topic.new
+ assert t.invalid?
+
+ xml = t.errors.to_xml
+ assert_match %r{<errors>}, xml
+ assert_match %r{<error>Title can't be blank</error>}, xml
+ assert_match %r{<error>Content can't be blank</error>}, xml
+
+ json = t.errors.to_json
+ assert_equal t.errors.to_a.to_json, json
end
def test_validation_order
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index def519c05d..a1a82fdff5 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,6 +1,12 @@
*Rails 3.0.0 [RC1] (unreleased)*
-* PostgreSQL: ensure the database time zone matches Ruby's time zone. #4895 [Aaron Patterson]
+* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
+
+* New rake task, db:migrate:status, displays status of migrations #4947 [Kevin Skoglund]
+
+* select and order for ActiveRecord now always concatenate nested calls. Use reorder if you want the original order to be overwritten [Santiago Pastorino]
+
+* PostgreSQL: ensure the database time zone matches Ruby's time zone #4895 [Aaron Patterson]
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
@@ -309,7 +315,7 @@
* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
-* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
+* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
* Added first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
@@ -620,7 +626,7 @@ so newlines etc are escaped #10385 [Norbert Crombach]
* Ensure that mysql quotes table names with database names correctly. Closes #9911 [crayz]
- "foo.bar" => "`foo`.`bar`"
+ "foo.bar" => "`foo`.`bar`"
* Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett]
http://errtheblog.com/post/2381
@@ -755,7 +761,7 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea
# Ensure that has_many :through associations use a count query instead of loading the target when #size is called. Closes #8800 [Pratik Naik]
-* Added :unless clause to validations #8003 [monki]. Example:
+* Added :unless clause to validations #8003 [monki]. Example:
def using_open_id?
!identity_url.blank?
@@ -775,7 +781,7 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea
OLD
belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is user_id
-
+
NEW
belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is visitor_id
@@ -881,9 +887,9 @@ single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Bea
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
-
+
...can now be written as:
-
+
create_table :products do |t|
t.integer :shop_id, :creator_id
t.string :name, :value, :default => "Untitled"
@@ -959,7 +965,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Lütke]
Task.cache { Task.find(1); Task.find(1) } #=> 1 query
-
+
* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
* Oracle: fix lob and text default handling. #7344 [gfriedrich, Michael Schoen]
@@ -1228,7 +1234,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael Schoen]
-* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.]
+* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.]
* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
@@ -1248,8 +1254,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
- assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
@@ -1327,14 +1333,14 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
- Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
...is the same as:
Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
-
+
This makes it easier to pass in the options from a form or otherwise outside.
-
+
* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com]
@@ -1362,7 +1368,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [Josh Susser]
-* Provide Association Extensions access to the instance that the association is being accessed from.
+* Provide Association Extensions access to the instance that the association is being accessed from.
Closes #4433 [Josh Susser]
* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
@@ -1381,7 +1387,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
-* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
@@ -1391,7 +1397,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
-* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
+* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
* Replace superfluous name_to_class_name variant with camelize. [Marcel Molina Jr.]
@@ -1401,7 +1407,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac <blair@orcaware.com>]
-* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com]
+* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com]
* New Frontbase connections don't start in auto-commit mode. Closes #4922. [mlaster@metavillage.com]
@@ -1413,9 +1419,9 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
-* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
+* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
-* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
+* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
@@ -1466,7 +1472,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
*1.15.3* (March 12th, 2007)
- * Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
+ * Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
* Consistently quote primary key column names. #7763 [toolmantim]
@@ -1705,8 +1711,8 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
- assert (Topic.exists?(:author_name => "David"))
- assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
assert (Topic.exists?(["parent_id = ?", 1]))
* Schema dumper quotes date :default values. [Dave Thomas]
@@ -1780,7 +1786,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [Hampton Catlin]. Example:
- Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
...is the same as:
@@ -1815,7 +1821,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [Josh Susser]
-* Provide Association Extensions access to the instance that the association is being accessed from.
+* Provide Association Extensions access to the instance that the association is being accessed from.
Closes #4433 [Josh Susser]
* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
@@ -1834,7 +1840,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
-* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
@@ -1844,7 +1850,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
-* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
+* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac <blair@orcaware.com>]
@@ -1854,9 +1860,9 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil <francois.beausoleil@gmail.com>]
-* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
+* Fix syntax error in documentation. Closes #4679. [Mislav Marohnić]
-* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
+* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
@@ -1904,7 +1910,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Add warning about the proper way to validate the presence of a foreign key. #4147 [Francois Beausoleil <francois.beausoleil@gmail.com>]
-* Fix syntax error in documentation. #4679 [Mislav Marohnić]
+* Fix syntax error in documentation. #4679 [Mislav Marohnić]
* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
@@ -2200,7 +2206,7 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
<id type="integer">1</id>
<credit-limit type="integer">50</credit-limit>
</account>
- </firm>
+ </firm>
* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
@@ -2390,17 +2396,17 @@ during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
-* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
-* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
+* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
-* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
+* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
-* made .find() work on :through relations. [Tobias Lütke]
+* made .find() work on :through relations. [Tobias Lütke]
* Fix typo in association docs. #3296. [Blair Zajac]
-* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
*1.13.2* (December 13th, 2005)
@@ -2663,7 +2669,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
-* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
@@ -2818,7 +2824,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
def self.search(q)
find(:all, :conditions => ["body = ?", q])
end
- end
+ end
class Post < AR:B
has_many :comments
@@ -2877,7 +2883,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
def evaluate_velocity(developer)
...
end
- end
+ end
..raising an exception will cause the object not to be added (or removed, with before_remove).
@@ -2913,10 +2919,10 @@ in effect. Added :readonly finder constraint. Calling an association collectio
...should instead be:
Developer.find(
- :all,
- :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
:conditions => 'project_id=1'
- )
+ )
* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina Jr.]
@@ -2953,7 +2959,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
-* Allow before/after update hooks to work on models using optimistic locking
+* Allow before/after update hooks to work on models using optimistic locking
* Eager loading of dependent has_one associations won't delete the association #1212
@@ -3133,10 +3139,10 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
- Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
- This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
@@ -3171,8 +3177,8 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
- * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
- +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
objects that should be inspected to determine which attributes triggered the errors.
* +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
@@ -3258,11 +3264,11 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
- 1. Created a new columns method that is much cleaner.
- 2. Corrected a problem with the select and select_all methods
- that didn't account for the LIMIT clause being passed into raw SQL statements.
- 3. Implemented the string_to_time method in order to create proper instances of the time class.
- 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow]
@@ -3385,9 +3391,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
-* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
- collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
- it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
it'll take one less SELECT query if you use length.
* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
@@ -3400,7 +3406,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
-* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
* Added that Observers can use the observes class method instead of overwriting self.observed_class().
@@ -3463,7 +3469,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Post < ActiveRecord::Base
has_one :tagging, :as => :taggable
end
-
+
Post.find :all, :include => :tagging
* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick Olson]
@@ -3474,7 +3480,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
has_many :photos, :as => :photographic
belongs_to :firm
end
-
+
class Firm < ActiveRecord::Base
has_many :photo_collections
has_many :photos, :through => :photo_collections
@@ -3536,7 +3542,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
In this example, :include => false disables the default eager association from loading. :select changes the standard
select clause. :joins specifies a join that is added to the end of the has_many :through query.
-
+
class Post < ActiveRecord::Base
has_many :tags, :through => :taggings, :include => :tagging do
def add_joins_and_select
@@ -3545,7 +3551,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
end
end
end
-
+
* Fixed that schema changes while the database was open would break any connections to a SQLite database (now we reconnect if that error is throw) [David Heinemeier Hansson]
* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [Jonathan Viney]
@@ -3562,12 +3568,12 @@ in effect. Added :readonly finder constraint. Calling an association collectio
has_many :clients
has_many :invoices, :through => :clients
end
-
+
class Client < ActiveRecord::Base
belongs_to :firm
has_many :invoices
end
-
+
class Invoice < ActiveRecord::Base
belongs_to :client
end
@@ -3619,19 +3625,19 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class CachedModel < ActiveRecord::Base
self.abstract_class = true
end
-
+
class Post < CachedModel
end
-
+
CachedModel.abstract_class?
=> true
-
+
Post.abstract_class?
=> false
Post.base_class
=> Post
-
+
Post.table_name
=> 'posts'
@@ -3656,9 +3662,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added Base#to_xml that'll turn the current record into a XML representation [David Heinemeier Hansson]. Example:
topic.to_xml
-
+
...returns:
-
+
<?xml version="1.0" encoding="UTF-8"?>
<topic>
<title>The First Topic</title>
@@ -3673,13 +3679,13 @@ in effect. Added :readonly finder constraint. Calling an association collectio
<parent-id></parent-id>
<last-read type="date">2004-04-15</last-read>
</topic>
-
+
...and you can configure with:
-
+
topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
-
+
...that'll return:
-
+
<topic>
<title>The First Topic</title>
<author-name>David</author-name>
@@ -3689,13 +3695,13 @@ in effect. Added :readonly finder constraint. Calling an association collectio
<parent-id></parent-id>
<last-read type="date">2004-04-15</last-read>
</topic>
-
+
You can even do load first-level associations as part of the document:
-
+
firm.to_xml :include => [ :account, :clients ]
-
+
...that'll return something like:
-
+
<?xml version="1.0" encoding="UTF-8"?>
<firm>
<id type="integer">1</id>
@@ -3715,7 +3721,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
<id type="integer">1</id>
<credit-limit type="integer">50</credit-limit>
</account>
- </firm>
+ </firm>
* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
@@ -3733,9 +3739,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* The first time a table is referenced in a join, no alias is used.
* After that, the parent class name and the reflection name are used.
-
+
Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
-
+
* Any additional join references get a numerical suffix like '_2', '_3', etc.
* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
@@ -3747,28 +3753,28 @@ in effect. Added :readonly finder constraint. Calling an association collectio
=> authors
+- posts
+- comments
-
+
# cascaded in two levels and normal association
>> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
=> authors
+- posts
+- comments
+- categorizations
-
+
# cascaded in two levels with two has_many associations
>> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
=> authors
+- posts
+- comments
+- categorizations
-
+
# cascaded in three levels
>> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
=> companies
+- groups
+- members
+- favorites
-
+
* Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
* Make migrations verbose [Jamis Buck]
@@ -3905,17 +3911,17 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
-* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
-* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
+* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Lütke]
-* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
+* made method missing delegation to class methods on relation target work on :through associations. [Tobias Lütke]
-* made .find() work on :through relations. [Tobias Lütke]
+* made .find() work on :through relations. [Tobias Lütke]
* Fix typo in association docs. #3296. [Blair Zajac]
-* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Lütke]
*1.13.2* (December 13th, 2005)
@@ -3928,7 +3934,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Post
has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
end
-
+
post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
@@ -4077,9 +4083,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
# Associated with :post_id
Comment.create :body => "Hello world"
end
-
+
This is rarely used directly, but allows for find_or_create on associations. So you can do:
-
+
# If the tag doesn't exist, a new one is created that's associated with the person
person.tags.find_or_create_by_name("Summer")
@@ -4087,7 +4093,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
# No 'Summer' tag exists
Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
-
+
# Now the 'Summer' tag does exist
Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
@@ -4178,7 +4184,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
-* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell <robby@planetargon.com>]
* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
@@ -4333,14 +4339,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
def self.search(q)
find(:all, :conditions => ["body = ?", q])
end
- end
+ end
class Post < AR:B
has_many :comments
end
Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
-
+
NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
@@ -4388,14 +4394,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Project
has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
-
+
def evaluate_velocity(developer)
...
end
- end
-
+ end
+
..raising an exception will cause the object not to be added (or removed, with before_remove).
-
+
* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry]
@@ -4424,14 +4430,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
-
+
...should instead be:
-
+
Developer.find(
- :all,
- :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
:conditions => 'project_id=1'
- )
+ )
* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina Jr.]
@@ -4450,12 +4456,12 @@ in effect. Added :readonly finder constraint. Calling an association collectio
david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
david.save
-
+
If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
project is saved when david.save is called.
-
+
Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
-
+
david.project_ids = [1, 5, 7]
* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com]
@@ -4468,7 +4474,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
-* Allow before/after update hooks to work on models using optimistic locking
+* Allow before/after update hooks to work on models using optimistic locking
* Eager loading of dependent has_one associations won't delete the association #1212
@@ -4499,7 +4505,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
Conditional validations can also solve the salted login generator problem:
validates_confirmation_of :password, :if => :new_password?
-
+
Using blocks:
validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
@@ -4562,11 +4568,11 @@ in effect. Added :readonly finder constraint. Calling an association collectio
puts "Written by: " + post.author.name
puts "Last comment on: " + post.comments.first.created_on
end
-
+
This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
-
+
for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
-
+
...and the number of database queries needed is now 1.
* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
@@ -4648,10 +4654,10 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
- Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
-
- This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
@@ -4686,8 +4692,8 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
- * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
- +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
objects that should be inspected to determine which attributes triggered the errors.
* +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
@@ -4715,16 +4721,16 @@ in effect. Added :readonly finder constraint. Calling an association collectio
Validates whether the value of the specified attribute is numeric by trying to convert it to
a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
<tt>/^[\+\-]?\d+$/</tt> (if <tt>integer</tt> is set to true).
-
+
class Person < ActiveRecord::Base
validates_numericality_of :value, :on => :create
end
-
+
Configuration options:
* <tt>message</tt> - A custom error message (default is: "is not a number")
* <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
* <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
-
+
* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
@@ -4773,17 +4779,17 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
- 1. Created a new columns method that is much cleaner.
- 2. Corrected a problem with the select and select_all methods
- that didn't account for the LIMIT clause being passed into raw SQL statements.
- 3. Implemented the string_to_time method in order to create proper instances of the time class.
- 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow]
* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
-
+
class Person < ActiveRecord::Base
validates_each :first_name, :last_name do |record, attr|
record.errors.add attr, 'starts with z.' if attr[0] == ?z
@@ -4861,19 +4867,19 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Book < ActiveRecord::Base
has_many :pages
belongs_to :library
-
+
validates_associated :pages, :library
end
-
+
* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
== Unsaved objects and associations
-
+
You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
aware of, mostly involving the saving of associated objects.
-
+
=== One-to-one associations
-
+
* Assigning an object to a has_one association automatically saves that object, and the object being replaced (if there is one), in
order to update their primary keys - except if the parent object is unsaved (new_record? == true).
* If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
@@ -4881,9 +4887,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
* Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
not save the parent either.
-
+
=== Collections
-
+
* Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
(the owner of the collection) is not yet stored in the database.
* If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
@@ -4900,9 +4906,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
-* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
- collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
- it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
it'll take one less SELECT query if you use length.
* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
@@ -4915,7 +4921,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
-* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
* Added that Observers can use the observes class method instead of overwriting self.observed_class().
@@ -4923,7 +4929,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class ListSweeper < ActiveRecord::Base
def self.observed_class() [ List, Item ]
end
-
+
After:
class ListSweeper < ActiveRecord::Base
observes List, Item
@@ -4943,7 +4949,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
page.views # => 1
page.increment!(:views) # executes an UPDATE statement
page.views # => 2
-
+
page.increment(:views).increment!(:views)
page.views # => 4
@@ -4955,32 +4961,32 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added automated optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
record increments the lock_version column and the locking facilities ensure that records instantiated twice
will let the last one saved raise a StaleObjectError if the first was also updated. Example:
-
+
p1 = Person.find(1)
p2 = Person.find(1)
-
+
p1.first_name = "Michael"
p1.save
-
+
p2.first_name = "should fail"
p2.save # Raises a ActiveRecord::StaleObjectError
-
+
You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
or otherwise apply the business logic needed to resolve the conflict.
#384 [Michael Koziarski]
-* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL.
+* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL.
They work by appending the name of an attribute to <tt>find_by_</tt>, so you get finders like <tt>Person.find_by_user_name,
Payment.find_by_transaction_id</tt>. So instead of writing <tt>Person.find_first(["user_name = ?", user_name])</tt>, you just do
<tt>Person.find_by_user_name(user_name)</tt>.
-
+
It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
<tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
- <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
+ <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
<tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
- While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like
+ While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like
<tt>Payment.find_all_by_amount(50)</tt> that is turned into <tt>Payment.find_all(["amount = ?", 50])</tt>. This is something not as equally useful,
though, as it's not possible to specify the order in which the objects are returned.
@@ -4988,13 +4994,13 @@ in effect. Added :readonly finder constraint. Calling an association collectio
Before:
before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" })
-
+
After:
before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
* Added :counter_cache option to acts_as_tree that works just like the one you can define on belongs_to #371 [Josh Peek]
-* Added Base.default_timezone accessor that determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates
+* Added Base.default_timezone accessor that determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates
and times from the database. This is set to :local by default.
* Added the possibility for adapters to overwrite add_limit! to implement a different limiting scheme than "LIMIT X" used by MySQL, PostgreSQL, and SQLite.
@@ -5013,7 +5019,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 [Tobias Lütke]
-* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it.
+* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it.
If require_association did not set the constant then const_get will call const_missing, resulting in an infinite loop #380 [Jeremy Kemper]
* Fixed broken transactions that were actually only running object-level and not db level transactions [andreas]
@@ -5033,12 +5039,12 @@ in effect. Added :readonly finder constraint. Calling an association collectio
require_association 'person'
class Employee < Person
end
-
+
after:
class Employee < Person
end
- This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
+ This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
* Added that Base.update_all and Base.delete_all return an integer of the number of affected rows #341
@@ -5056,12 +5062,12 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added more informative exceptions in establish_connection #356 [Jeremy Kemper]
-* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise).
+* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise).
Before:
person.attributes = @params["person"]
person.save
-
+
Now:
person.update_attributes(@params["person"])
@@ -5073,7 +5079,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed that options[:counter_sql] was overwritten with interpolated sql rather than original sql #355 [Jeremy Kemper]
-* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used
+* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used
the attributes[] hash instead of checking for @base.respond_to?(attr.to_s). [Marten]
* Fixed that Base.table_name would expect a parameter when used in has_and_belongs_to_many joins [Anna Lissa Cruz]
@@ -5107,10 +5113,10 @@ in effect. Added :readonly finder constraint. Calling an association collectio
belongs_to :todo_list
end
-* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
+* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
categories and the likes. [Tobias Lütke]
-* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
+* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
created_at/created_on or updated_at/updated_on are present. [Tobias Lütke]
* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Lütke]
@@ -5124,14 +5130,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example:
ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'.
- This error is raised because the column 'type' 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
+ This error is raised because the column 'type' 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 Company.inheritance_column to use another column for that information.
* Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't
have any magic side effects.
-* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin]
+* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin]
* Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples:
@@ -5145,7 +5151,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
errors.on(:name) # => "must be shorter"
errors.on("name") # => "must be shorter"
-* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
+* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
it against the regular expression provided. [Marcel Molina Jr.]
class Person < ActiveRecord::Base
@@ -5155,14 +5161,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Lütke]. Example:
Validates that the specified attribute matches the length restrictions supplied in either:
-
+
- configuration[:minimum]
- configuration[:maximum]
- configuration[:is]
- configuration[:within] (aka. configuration[:in])
-
+
Only one option can be used at a time.
-
+
class Person < ActiveRecord::Base
validates_length_of :first_name, :maximum=>30
validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
@@ -5170,53 +5176,53 @@ in effect. Added :readonly finder constraint. Calling an association collectio
validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
end
-
+
* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
-* Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
+* Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
Useful for making sure that only one user can be named "davidhh".
-
+
class Person < ActiveRecord::Base
validates_uniqueness_of :user_name
end
-
+
When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
* Added Base.validates_confirmation_of that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
-
+
Model:
class Person < ActiveRecord::Base
validates_confirmation_of :password
end
-
+
View:
<%= password_field "person", "password" %>
<%= password_field "person", "password_confirmation" %>
-
+
The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
* Added Base.validates_acceptance_of that encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
-
+
class Person < ActiveRecord::Base
validates_acceptance_of :terms_of_service
end
-
+
The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
-
+
* Added validation macros to make the stackable just like the lifecycle callbacks. Examples:
class Person < ActiveRecord::Base
validate { |record| record.errors.add("name", "too short") unless name.size > 10 }
validate { |record| record.errors.add("name", "too long") unless name.size < 20 }
validate_on_create :validate_password
-
+
private
def validate_password
errors.add("password", "too short") unless password.size > 6
@@ -5232,7 +5238,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer
columns.
-* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
+* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
[Florian Weber]
* Added counter_sql option for has_many associations [Jeremy Kemper]. Documentation:
@@ -5299,9 +5305,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
fixtures/developers/fixtures.yaml
fixtures/accounts/fixtures.yaml
-
+
...you now need to do:
-
+
fixtures/developers.yaml
fixtures/accounts.yaml
@@ -5328,13 +5334,13 @@ in effect. Added :readonly finder constraint. Calling an association collectio
name: David Heinemeier Hansson
birthday: 1979-10-15
profession: Systems development
-
+
steve:
id: 2
name: Steve Ross Kellock
birthday: 1974-09-27
profession: guy with keyboard
-
+
The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
@@ -5342,7 +5348,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
*1.1.0* (34)
-* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
+* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
instantiated in instance variables relating to their names using the following style:
class FixturesTest < Test::Unit::TestCase
@@ -5359,10 +5365,10 @@ in effect. Added :readonly finder constraint. Calling an association collectio
attributes. This is really useful when you have information that's only relevant to the join itself, such as a "added_on" column for an association
between post and category. The added attributes will automatically be injected into objects retrieved through the association similar to the piggy-back
approach:
-
+
post.categories.push_with_attributes(category, :added_on => Date.today)
post.categories.first.added_on # => Date.today
-
+
NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does!
* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations [Jeremy Kemper]
@@ -5415,8 +5421,8 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Improved the speed of respond_to? by placing the dynamic methods lookup table in a hash [geech]
-* Added that any additional fields added to the join table in a has_and_belongs_to_many association
- will be placed as attributes when pulling records out through has_and_belongs_to_many associations.
+* Added that any additional fields added to the join table in a has_and_belongs_to_many association
+ will be placed as attributes when pulling records out through has_and_belongs_to_many associations.
This is helpful when have information about the association itself that you want available on retrival.
* Added better loading exception catching and RubyGems retries to the database adapters [alexeyv]
@@ -5425,9 +5431,9 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed Base#transaction so that it returns the result of the last expression in the transaction block [alexeyv]
-* Added Fixture#find to find the record corresponding to the fixture id. The record
+* Added Fixture#find to find the record corresponding to the fixture id. The record
class name is guessed by using Inflector#classify (also new) on the fixture directory name.
-
+
Before: Document.find(@documents["first"]["id"])
After : @documents["first"].find
@@ -5437,7 +5443,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed quoting problems on SQLite by adding quote_string to the AbstractAdapter that can be overwritten by the concrete
adapters for a call to the dbm. [Andreas Schwarz]
-
+
* Removed RubyGems backup strategy for requiring SQLite-adapter -- if people want to use gems, they're already doing it with AR.
@@ -5453,19 +5459,19 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added serialize as a new class method to control when text attributes should be YAMLized or not. This means that automated
serialization of hashes, arrays, and so on WILL NO LONGER HAPPEN (#10). You need to do something like this:
-
+
class User < ActiveRecord::Base
serialize :settings
end
-
+
This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify
an optional :class_name option that'll raise an exception if a serialized object is retrieved as a descendant of a class not in
the hierarchy. Example:
-
+
class User < ActiveRecord::Base
serialize :settings, :class_name => "Hash"
end
-
+
user = User.create("settings" => %w( one two three ))
User.find(user.id).settings # => raises SerializationTypeMismatch
@@ -5480,7 +5486,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added the possibility to chain the return of what happened inside a logged block [geech]:
- This now works:
+ This now works:
log { ... }.map { ... }
Instead of doing:
@@ -5490,7 +5496,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added "socket" option for the MySQL adapter, so you can change it to something else than "/tmp/mysql.sock" [Anna Lissa Cruz]
-* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema,
+* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema,
person.respond_to? "name" will return true.
* Added Base.benchmark which can be used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
@@ -5511,7 +5517,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added compatibility with 2.x series of sqlite-ruby drivers. [Jamis Buck]
* Added type safety for association assignments, so a ActiveRecord::AssociationTypeMismatch will be raised if you attempt to
- assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on
+ assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on
mistaken association assignments. [Reported by Andreas Schwarz]
* Added the option to keep many fixtures in one single YAML document [what-a-day]
@@ -5523,7 +5529,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added the option to observer more than one class at the time by specifying observed_class as an array
-* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them
+* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them
on PostgreSQL. [Dave Steinberg]
* Changed that integer and floats set to "" through attributes= remain as NULL. This was especially a problem for scaffolding and postgresql. (#49)
@@ -5565,8 +5571,8 @@ in effect. Added :readonly finder constraint. Calling an association collectio
directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
to guess the table name from even when called on Reply. The guessing rules are as follows:
* Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
- * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies",
- so a Category class becomes a categories table.
+ * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies",
+ so a Category class becomes a categories table.
* Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
* Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
* Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
@@ -5579,14 +5585,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
-
+
You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
"mice" table. Example:
-
+
class Mouse < ActiveRecord::Base
def self.table_name() "mice" end
end
-
+
This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb.
* Added find_all_in_collection to has_many defined collections. Works like this:
@@ -5594,7 +5600,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Firm < ActiveRecord::Base
has_many :clients
end
-
+
firm.id # => 1
firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000
@@ -5610,11 +5616,11 @@ in effect. Added :readonly finder constraint. Calling an association collectio
errors.add_on_boundry_breaking "password", 3..20
end
end
-
+
This will add an error to the tune of "is too short (minimum is 3 characters)" or "is too long (minimum is 20 characters)" if
the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings.
-* Implemented a clone method that works properly with AR. It returns a clone of the record that
+* Implemented a clone method that works properly with AR. It returns a clone of the record that
hasn't been assigned an id yet and is treated as a new record.
* Allow for domain sockets in PostgreSQL by not assuming localhost when no host is specified [Scott Barron]
@@ -5645,12 +5651,12 @@ in effect. Added :readonly finder constraint. Calling an association collectio
*0.9.3*
-* Fixed bug with using a different primary key name together with has_and_belongs_to_many [Investigation by Scott]
+* Fixed bug with using a different primary key name together with has_and_belongs_to_many [Investigation by Scott]
* Added :exclusively_dependent option to the has_many association macro. The doc reads:
If set to true all the associated object are deleted in one SQL statement without having their
- before_destroy callback run. This should only be used on associations that depend solely on
+ before_destroy callback run. This should only be used on associations that depend solely on
this class and don't need to do any clean-up in before_destroy. The upside is that it's much
faster, especially if there's a counter_cache involved.
@@ -5662,7 +5668,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Fixed the use of floats (was broken since 0.9.0+)
-* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with
+* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with
Action Pack scaffolding.
* Fixed booleans support for PostgreSQL (use real true/false on boolean fields instead of 0/1 on tinyints) [radsaq]
@@ -5688,21 +5694,21 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Event < ActiveRecord::Base
has_one_and_belongs_to_many :sponsors
end
-
+
class Sponsor < ActiveRecord::Base
has_one_and_belongs_to_many :sponsors
end
Earlier, you'd have to use synthetic methods for creating associations between two objects of the above class:
-
+
roskilde_festival.add_to_sponsors(carlsberg)
roskilde_festival.remove_from_sponsors(carlsberg)
nike.add_to_events(world_cup)
nike.remove_from_events(world_cup)
-
+
Now you can use regular array-styled methods:
-
+
roskilde_festival.sponsors << carlsberg
roskilde_festival.sponsors.delete(carlsberg)
@@ -5711,7 +5717,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Added delete method for has_many associations. Using this will nullify an association between the has_many and the belonging
object by setting the foreign key to null. Consider this model:
-
+
class Post < ActiveRecord::Base
has_many :comments
end
@@ -5732,7 +5738,7 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications)
[Implementation idea by Michael Neumann, debugging assistance by Jamis Buck]
-* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute.
+* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute.
This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%.
Done by doing lazy type conversions and caching column information on the class-level.
@@ -5749,46 +5755,46 @@ in effect. Added :readonly finder constraint. Calling an association collectio
end
Iterating over 100 posts like this:
-
+
<% for post in @posts %>
<%= post.title %> has <%= post.comments_count %> comments
<% end %>
-
+
Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column
to the posts table and rewrite the comments association macro with:
class Comment < ActiveRecord::Base
belongs_to :post, :counter_cache => true
end
-
+
Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life
with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify
:dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another),
- you'll have to manage the counter yourself.
+ you'll have to manage the counter yourself.
* Added natural object-style assignment for has_one and belongs_to associations. Consider the following model:
class Project < ActiveRecord::Base
has_one :manager
end
-
+
class Manager < ActiveRecord::Base
belongs_to :project
end
-
+
Earlier, assignments would work like following regardless of which way the assignment told the best story:
-
+
active_record.manager_id = david.id
-
+
Now you can do it either from the belonging side:
david.project = active_record
-
+
...or from the having side:
-
+
active_record.manager = david
-
- If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the
+
+ If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the
project_id attribute on david would be set to the id of active_record, then david would be saved.
* Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model:
@@ -5796,36 +5802,36 @@ in effect. Added :readonly finder constraint. Calling an association collectio
class Project < ActiveRecord::Base
has_many :milestones
end
-
+
class Milestone < ActiveRecord::Base
belongs_to :project
end
-
+
Earlier, assignments would work like following regardless of which way the assignment told the best story:
-
+
deadline.project_id = active_record.id
-
+
Now you can do it either from the belonging side:
deadline.project = active_record
-
+
...or from the having side:
-
+
active_record.milestones << deadline
-
+
The milestone is automatically saved with the new foreign key.
* API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using
to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time).
Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header.
This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do:
-
+
user = User.find(1)
user.preferences = { "background" => "black", "display" => large }
user.save
-
+
User.find(1).preferences # => { "background" => "black", "display" => large }
-
+
Please note that this method should only be used when you don't care about representing the object in proper columns in
the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through
aggregations than this new option.
@@ -5833,14 +5839,14 @@ in effect. Added :readonly finder constraint. Calling an association collectio
* POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash
directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement
new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this:
-
+
class Song < ActiveRecord::Base
# Uses an integer of seconds to hold the length of the song
-
+
def length=(minutes)
write_attribute("length", minutes * 60)
end
-
+
def length
read_attribute("length") / 60
end
@@ -5876,7 +5882,7 @@ _Misc_
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
# parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
# s for String, and a for Array.
-
+
This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day.
* Fixed bug with custom primary key column name and Base.find on multiple parameters.
@@ -5898,17 +5904,17 @@ _Transactions_
_Mapping_
* Added support for non-integer primary keys [Aredridel/earlier work by Michael Neumann]
-
+
User.find "jdoe"
Product.find "PDKEY-INT-12"
* Added option to specify naming method for primary key column. ActiveRecord::Base.primary_key_prefix_type can either
be set to nil, :table_name, or :table_name_with_underscore. :table_name will assume that Product class has a primary key
of "productid" and :table_name_with_underscore will assume "product_id". The default nil will just give "id".
-
-* Added an overwriteable primary_key method that'll instruct AR to the name of the
+
+* Added an overwriteable primary_key method that'll instruct AR to the name of the
id column [Aredridele/earlier work by Guan Yang]
-
+
class Project < ActiveRecord::Base
def self.primary_key() "project_id" end
end
@@ -5929,8 +5935,8 @@ _Mapping_
_Misc_
* Added freeze call to value object assignments to ensure they remain immutable [Spotted by Gavin Sinclair]
-
-* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is
+
+* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is
observed_class() class method. This is more consistant with things like self.table_name(). Works like this:
class AuditObserver < ActiveRecord::Observer
@@ -5957,20 +5963,20 @@ _Misc_
* Added inheritable callback queues that can ensure that certain callback methods or inline fragments are
run throughout the entire inheritance hierarchy. Regardless of whether a descendant overwrites the callback
method:
-
+
class Topic < ActiveRecord::Base
before_destroy :destroy_author, 'puts "I'm an inline fragment"'
end
-
+
Learn more in link:classes/ActiveRecord/Callbacks.html
-* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when
+* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when
the holder is destroyed:
-
+
class Album < ActiveRecord::Base
has_many :tracks, :dependent => true
end
-
+
All the associated tracks are destroyed when the album is.
* Added Base.create as a factory that'll create, save, and return a new object in one step.
@@ -5980,7 +5986,7 @@ _Misc_
* Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock]
-* Modified block syntax to better follow our code standards outlined in
+* Modified block syntax to better follow our code standards outlined in
http://www.rubyonrails.org/CodingStandards
@@ -5991,7 +5997,7 @@ _Misc_
* Changed adapter-specific connection methods to use centralized ActiveRecord::Base.establish_connection,
which is parametized through a config hash with symbol keys instead of a regular parameter list.
This will allow for database connections to be opened in a more generic fashion. (Luke)
-
+
NOTE: This requires all *_connections to be updated! Read more in:
http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081
@@ -6000,7 +6006,7 @@ _Misc_
* Fixed SQLite adapter so dates are returned as Date objects, not Time objects [Spotted by Gavin Sinclair]
-* Fixed requirement of date class, so date conversions are succesful regardless of whether you
+* Fixed requirement of date class, so date conversions are succesful regardless of whether you
manually require date or not.
@@ -6008,10 +6014,10 @@ _Misc_
* Added transactions
-* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7])
+* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7])
as parameter and then return an array of objects instead of just an object
-* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a
+* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a
collection, not an association
* Fixed SQLite adapter so empty or nil values in columns of datetime, date, or time type
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 12e094b406..392b717e0a 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -49,7 +49,10 @@ end
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
t.libs << "test" << connection_path
- t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
+ t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
+ |x| x =~ /\/adapters\//
+ } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort
+
t.verbose = true
t.warning = true
}
@@ -59,7 +62,9 @@ end
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
puts [adapter, adapter_short, connection_path].inspect
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
- Dir["test/cases/**/*_test{,_#{adapter_short}}.rb"].all? do |file|
+ (Dir["test/cases/**/*_test.rb"].reject {
+ |x| x =~ /\/adapters\//
+ } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
system(ruby, "-Ilib:test:#{connection_path}", file)
end or raise "Failures"
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index f69576b240..f7d358337c 100755..100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -116,15 +116,15 @@ RBench.run(TIMES) do
end
report 'Model.all limit(100)', (TIMES / 10).ceil do
- ar { Exhibit.look Exhibit.all(:limit => 100) }
+ ar { Exhibit.look Exhibit.limit(100) }
end
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
- ar { Exhibit.feel Exhibit.all(:limit => 100, :include => :user) }
+ ar { Exhibit.feel Exhibit.limit(100).includes(:user) }
end
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
- ar { Exhibit.look Exhibit.all(:limit => 10000) }
+ ar { Exhibit.look Exhibit.limit(10000) }
end
exhibit = {
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 45aaea062d..c45400d3d9 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Aggregations
module Aggregations # :nodoc:
extend ActiveSupport::Concern
@@ -189,7 +190,7 @@ module ActiveRecord
# :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
# :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
#
- def composed_of(part_id, options = {}, &block)
+ def composed_of(part_id, options = {})
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
name = part_id.id2name
@@ -198,9 +199,7 @@ module ActiveRecord
mapping = [ mapping ] unless mapping.first.is_a?(Array)
allow_nil = options[:allow_nil] || false
constructor = options[:constructor] || :new
- converter = options[:converter] || block
-
- ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
+ converter = options[:converter]
reader_method(name, class_name, mapping, allow_nil, constructor)
writer_method(name, class_name, mapping, allow_nil, converter)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 9b59266bbc..4caa434fc0 100755..100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -88,8 +88,8 @@ module ActiveRecord
end
end
- # This error is raised when trying to destroy a parent instance in a N:1, 1:1 assosications
- # (has_many, has_one) when there is at least 1 child assosociated instance.
+ # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
+ # (has_many, has_one) when there is at least 1 child associated instance.
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
class DeleteRestrictionError < ActiveRecordError #:nodoc:
def initialize(reflection)
@@ -886,11 +886,13 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. true by default.
# [:autosave]
- # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated objects.
+ # By default, only save associated objects that are new records.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -1001,11 +1003,13 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated object when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated object.
+ # By default, only save the associated object if it's a new record.
# [:inverse_of]
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -1103,14 +1107,16 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated object.
+ # By default, only save the associated object if it's a new record.
# [:touch]
# If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
# destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
# [:inverse_of]
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt>
# association. Does not work in combination with the <tt>:polymorphic</tt> options.
- # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail.
+ # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -1290,7 +1296,9 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
# [:autosave]
- # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
+ # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If false, never save or destroy the associated objects.
+ # By default, only save associated objects that are new records.
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -2066,7 +2074,7 @@ module ActiveRecord
unless klass.descends_from_active_record?
sti_column = aliased_table[klass.inheritance_column]
sti_condition = sti_column.eq(klass.sti_name)
- klass.send(:subclasses).each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
+ klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
@join << sti_condition
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index d9903243ce..ddf4ce4058 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array/wrap'
module ActiveRecord
module Associations
+ # = Active Record Association Collection
+ #
# AssociationCollection is an abstract class that provides common stuff to
# ease the implementation of association proxies that represent
# collections. See the class hierarchy in AssociationProxy.
@@ -24,10 +26,10 @@ module ActiveRecord
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
- def select(select = nil, &block)
+ def select(select = nil)
if block_given?
load_target
- @target.select(&block)
+ @target.select.each { |e| yield e }
else
scoped.select(select)
end
@@ -121,7 +123,7 @@ module ActiveRecord
end
end
- # Add +records+ to this association. Returns +self+ so method calls may be chained.
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
def <<(*records)
result = true
@@ -166,7 +168,7 @@ module ActiveRecord
reset_target!
reset_named_scopes_cache!
end
-
+
# Calculate sum using SQL, not Enumerable
def sum(*args)
if block_given?
@@ -239,7 +241,7 @@ module ActiveRecord
if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
destroy_all
- else
+ else
delete_all
end
@@ -388,7 +390,11 @@ module ActiveRecord
begin
if !loaded?
if @target.is_a?(Array) && @target.any?
- @target = find_target + @target.find_all {|t| t.new_record? }
+ @target = find_target.map do |f|
+ i = @target.index(f)
+ t = @target.delete_at(i) if i
+ (t && t.changed?) ? t : f
+ end + @target
else
@target = find_target
end
@@ -403,6 +409,17 @@ module ActiveRecord
end
def method_missing(method, *args)
+ case method.to_s
+ when 'find_or_create'
+ return find(:first, :conditions => args.first) || create(args.first)
+ when /^find_or_create_by_(.*)$/
+ rest = $1
+ return send("find_by_#{rest}", *args) ||
+ method_missing("create_by_#{rest}", *args)
+ when /^create_by_(.*)$/
+ return create Hash[$1.split('_and_').zip(args)]
+ end
+
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
if block_given?
super { |*block_args| yield(*block_args) }
@@ -514,8 +531,8 @@ module ActiveRecord
def callbacks_for(callback_name)
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
- end
-
+ end
+
def ensure_owner_is_not_new
if @owner.new_record?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index e88618d278..f333f4d603 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -2,6 +2,8 @@ require 'active_support/core_ext/array/wrap'
module ActiveRecord
module Associations
+ # = Active Record Associations
+ #
# This is the root class of all association proxies:
#
# AssociationProxy
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index d2f2267e5c..c2a6495db5 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Associations
module Associations
class BelongsToAssociation < AssociationProxy #:nodoc:
def create(attributes = {})
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index f6edd6383c..38454ec242 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Polymorphic Association
module Associations
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
def replace(record)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 7f39a189e4..c989c3536d 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Has And Belongs To Many Association
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
def create(attributes = {})
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index 0464e8ceea..d74fb7c702 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Has Many Association
module Associations
# This is the proxy that handles a has many association.
#
@@ -109,7 +110,11 @@ module ActiveRecord
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
+ :find => { :conditions => @finder_sql,
+ :readonly => false,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :include => @reflection.options[:include]},
:create => create_scoping
}
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 5338bb099d..17f850756f 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -2,6 +2,7 @@ require "active_record/associations/through_association_scope"
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Has Many Through Association
module Associations
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
include ThroughAssociationScope
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index ea769fd48b..68b8b792ad 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Belongs To Has One Association
module Associations
class HasOneAssociation < AssociationProxy #:nodoc:
def initialize(owner, reflection)
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 a79bf943d1..fba0a2bfcc 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -1,6 +1,7 @@
require "active_record/associations/through_association_scope"
module ActiveRecord
+ # = Active Record Has One Through Association
module Associations
class HasOneThroughAssociation < HasOneAssociation
include ThroughAssociationScope
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 1d2f323112..22e1033a9d 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Through Association Scope
module Associations
module ThroughAssociationScope
@@ -91,7 +92,7 @@ module ActiveRecord
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
- # TODO: revist this to allow it for deletion, supposing dependent option is supported
+ # TODO: revisit this to allow it for deletion, supposing dependent option is supported
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index c117271c71..56e18eced0 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/enumerable'
module ActiveRecord
+ # = Active Record Attribute Methods
module AttributeMethods #:nodoc:
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 0dcadfab5f..7517896235 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/array/wrap'
module ActiveRecord
+ # = Active Record Autosave Association
+ #
# AutosaveAssociation is a module that takes care of automatically saving
# your associations when the parent is saved. In addition to saving, it
# also destroys any associations that were marked for destruction.
@@ -145,12 +147,12 @@ module ActiveRecord
# add_autosave_association_callbacks(reflect_on_association(name))
# end
ASSOCIATION_TYPES.each do |type|
- module_eval %{
+ module_eval <<-CODE, __FILE__, __LINE__ + 1
def #{type}(name, options = {})
super
add_autosave_association_callbacks(reflect_on_association(name))
end
- }
+ CODE
end
# Adds a validate and save callback for the association as specified by
@@ -375,7 +377,7 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
self[reflection.primary_key_name] = association_id
- # TODO: Removing this code doesn't seem to matter…
+ # TODO: Removing this code doesn't seem to matter...
if reflection.options[:polymorphic]
self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 3b6ffa46f2..c8795e4496 100755..100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -2,6 +2,7 @@ require 'yaml'
require 'set'
require 'active_support/benchmarkable'
require 'active_support/dependencies'
+require 'active_support/descendants_tracker'
require 'active_support/time'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
@@ -14,13 +15,17 @@ require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/string/behavior'
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/deprecation'
require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/blank'
require 'arel'
require 'active_record/errors'
+require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
+ # = Active Record
+ #
# Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
# which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
# is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
@@ -274,27 +279,17 @@ module ActiveRecord #:nodoc:
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
- def self.inherited(child) #:nodoc:
- @@subclasses[self] ||= []
- @@subclasses[self] << child
- super
- end
+ class << self
+ def reset_subclasses #:nodoc:
+ ActiveSupport::Deprecation.warn 'ActiveRecord::Base.reset_subclasses no longer does anything in Rails 3. It will be removed in the final release; please update your apps and plugins.', caller
+ end
- def self.reset_subclasses #:nodoc:
- nonreloadables = []
- subclasses.each do |klass|
- unless ActiveSupport::Dependencies.autoloaded? klass
- nonreloadables << klass
- next
- end
- klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
- klass.instance_methods(false).each { |m| klass.send :undef_method, m }
+ def subclasses
+ descendants
end
- @@subclasses = {}
- nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
- end
- @@subclasses = {}
+ deprecate :subclasses => :descendants
+ end
##
# :singleton-method:
@@ -403,7 +398,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
+ delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -725,14 +720,6 @@ module ActiveRecord #:nodoc:
end
alias :sequence_name= :set_sequence_name
- # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
- def class_name(table_name = table_name) # :nodoc:
- # remove any prefix and/or suffix from the table name
- class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
- class_name = class_name.singularize if pluralize_table_names
- class_name
- end
-
# Indicates whether the table associated with this class exists
def table_exists?
connection.table_exists?(table_name)
@@ -806,11 +793,11 @@ module ActiveRecord #:nodoc:
def reset_column_information
undefine_attribute_methods
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
- @arel_engine = @unscoped = @arel_table = nil
+ @arel_engine = @relation = @arel_table = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
- subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
+ descendants.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
end
def attribute_method?(attribute)
@@ -909,9 +896,9 @@ module ActiveRecord #:nodoc:
store_full_sti_class ? name : name.demodulize
end
- def unscoped
- @unscoped ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
+ def relation
+ @relation ||= Relation.new(self, arel_table)
+ finder_needs_type_condition? ? @relation.where(type_condition) : @relation
end
def arel_table
@@ -928,6 +915,31 @@ module ActiveRecord #:nodoc:
end
end
+ # Returns a scope for this class without taking into account the default_scope.
+ #
+ # class Post < ActiveRecord::Base
+ # default_scope :published => true
+ # end
+ #
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
+ #
+ # This method also accepts a block meaning that all queries inside the block will
+ # not use the default_scope:
+ #
+ # Post.unscoped {
+ # limit(10) # Fires "SELECT * FROM posts LIMIT 10"
+ # }
+ #
+ def unscoped
+ block_given? ? relation.scoping { yield } : relation
+ end
+
+ def scoped_methods #:nodoc:
+ key = :"#{self}_scoped_methods"
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
+ end
+
private
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
@@ -935,8 +947,8 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.instance_variable_set(:'@attributes', record)
- object.instance_variable_set(:'@attributes_cache', {})
+ object.instance_variable_set(:@attributes, record)
+ object.instance_variable_set(:@attributes_cache, {})
object.instance_variable_set(:@new_record, false)
object.instance_variable_set(:@readonly, false)
object.instance_variable_set(:@destroyed, false)
@@ -975,7 +987,7 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
- subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
+ descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
condition
end
@@ -1162,16 +1174,22 @@ module ActiveRecord #:nodoc:
# Works like with_scope, but discards any nested properties.
def with_exclusive_scope(method_scoping = {}, &block)
- with_scope(method_scoping, :overwrite, &block)
- end
+ if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
+ raise ArgumentError, <<-MSG
+New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
- # Returns a list of all subclasses of this class, meaning all descendants.
- def subclasses
- @@subclasses[self] ||= []
- @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
- end
+ User.unscoped.where(:active => true)
- public :subclasses
+Or call unscoped with a block:
+
+ User.unscoped do
+ User.where(:active => true).all
+ end
+
+MSG
+ end
+ with_scope(method_scoping, :overwrite, &block)
+ end
# Sets the default options for the model. The format of the
# <tt>options</tt> argument is the same as in find.
@@ -1183,11 +1201,6 @@ module ActiveRecord #:nodoc:
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
end
- def scoped_methods #:nodoc:
- key = :"#{self}_scoped_methods"
- Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
- end
-
def current_scoped_methods #:nodoc:
scoped_methods.last
end
@@ -1440,14 +1453,6 @@ module ActiveRecord #:nodoc:
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
- # Think the assertion which fails if the after_initialize callback goes at the end of the method is wrong. The
- # deleted clone method called new which therefore called the after_initialize callback. It then went on to copy
- # over the attributes. But if it's copying the attributes afterwards then it hasn't finished initializing right?
- # For example in the test suite the topic model's after_initialize method sets the author_email_address to
- # test@test.com. I would have thought this would mean that all cloned models would have an author email address
- # of test@test.com. However the test_clone test method seems to test that this is not the case. As a result the
- # after_initialize callback has to be run *before* the copying of the atrributes rather than afterwards in order
- # for all tests to pass. This makes no sense to me.
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@@ -1460,6 +1465,7 @@ module ActiveRecord #:nodoc:
end
clear_aggregation_cache
+ clear_association_cache
@attributes_cache = {}
@new_record = true
ensure_proper_type
@@ -1900,6 +1906,7 @@ module ActiveRecord #:nodoc:
extend ActiveModel::Naming
extend QueryCache::ClassMethods
extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
include ActiveModel::Conversion
include Validations
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 44fee12001..637dac450b 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/array/wrap'
module ActiveRecord
+ # = Active Record Callbacks
+ #
# Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
# before or after an alteration of the object state. This can be used to make sure that associated and
# dependent objects are deleted when +destroy+ is called (by overwriting +before_destroy+) or to massage attributes
@@ -234,8 +236,7 @@ module ActiveRecord
included do
extend ActiveModel::Callbacks
-
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ include ActiveModel::Validations::Callbacks
define_model_callbacks :initialize, :find, :only => :after
define_model_callbacks :save, :create, :update, :destroy
@@ -249,29 +250,6 @@ module ActiveRecord
send(meth.to_sym, meth.to_sym)
end
end
-
- def before_validation(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
- options[:if] << "@_on_validate == :#{options[:on]}"
- end
- set_callback(:validation, :before, *args, &block)
- end
-
- def after_validation(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted && value != false"
- options[:if] << "@_on_validate == :#{options[:on]}" if options[:on]
- set_callback(:validation, :after, *(args << options), &block)
- end
- end
-
- def valid?(*) #:nodoc:
- @_on_validate = new_record? ? :create : :update
- _run_validation_callbacks { super }
end
def destroy #:nodoc:
@@ -286,6 +264,7 @@ module ActiveRecord
end
private
+
def create_or_update #:nodoc:
_run_save_callbacks { super }
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 979ed52f4a..c2d79a421d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -144,7 +144,9 @@ module ActiveRecord
@connections.each do |conn|
conn.disconnect! if conn.requires_reloading?
end
- @connections = []
+ @connections.delete_if do |conn|
+ conn.requires_reloading?
+ end
end
# Verify active connections and remove and disconnect connections
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 b9fb452eee..25432e9985 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -304,7 +304,7 @@ module ActiveRecord
begin
record.rolledback!(rollback)
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
@@ -319,7 +319,7 @@ module ActiveRecord
begin
record.committed!
rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
+ record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7d58bc2adf..7691b6a788 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -582,6 +582,11 @@ module ActiveRecord
@base.add_column(@table_name, column_name, type, options)
end
+ # Checks to see if a column exists. See SchemaStatements#column_exists?
+ def column_exists?(column_name, type = nil, options = nil)
+ @base.column_exists?(@table_name, column_name, type, options)
+ end
+
# Adds a new index to the table. +column_name+ can be a single Symbol, or
# an Array of Symbols. See SchemaStatements#add_index
#
@@ -596,6 +601,11 @@ module ActiveRecord
@base.add_index(@table_name, column_name, options)
end
+ # Checks to see if an index exists. See SchemaStatements#index_exists?
+ def index_exists?(column_name, options = {})
+ @base.index_exists?(@table_name, column_name, options)
+ end
+
# Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
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 d3499cea72..76b65bf219 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -24,10 +24,53 @@ module ActiveRecord
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
+ # Checks to see if an index exists on a table for a given index definition
+ #
+ # === Examples
+ # # Check an index exists
+ # index_exists?(:suppliers, :company_id)
+ #
+ # # Check an index on multiple columns exists
+ # index_exists?(:suppliers, [:company_id, :company_type])
+ #
+ # # Check a unique index exists
+ # index_exists?(:suppliers, :company_id, :unique => true)
+ #
+ # # Check an index with a custom name exists
+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
+ def index_exists?(table_name, column_name, options = {})
+ column_names = Array.wrap(column_name)
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
+ if options[:unique]
+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
+ else
+ indexes(table_name).any?{ |i| i.name == index_name }
+ end
+ end
+
# Returns an array of Column objects for the table specified by +table_name+.
# See the concrete implementation for details on the expected parameter values.
def columns(table_name, name = nil) end
+ # Checks to see if a column exists in a given table.
+ #
+ # === Examples
+ # # Check a column exists
+ # column_exists?(:suppliers, :name)
+ #
+ # # Check a column exists of a particular type
+ # column_exists?(:suppliers, :name, :string)
+ #
+ # # Check a column exists with a specific definition
+ # column_exists?(:suppliers, :name, :string, :limit => 100)
+ def column_exists?(table_name, column_name, type = nil, options = {})
+ columns(table_name).any?{ |c| c.name == column_name.to_s &&
+ (!type || c.type == type) &&
+ (!options[:limit] || c.limit == options[:limit]) &&
+ (!options[:precision] || c.precision == options[:precision]) &&
+ (!options[:scale] || c.scale == options[:scale]) }
+ end
+
# Creates a new table with the name +table_name+. +table_name+ may either
# be a String or a Symbol.
#
@@ -205,6 +248,7 @@ module ActiveRecord
# remove_column(:suppliers, :qualification)
# remove_columns(:suppliers, :qualification, :experience)
def remove_column(table_name, *column_names)
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
end
@@ -292,7 +336,7 @@ module ActiveRecord
@logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
return
end
- if index_exists?(table_name, index_name, false)
+ if index_name_exists?(table_name, index_name, false)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
return
end
@@ -313,7 +357,7 @@ module ActiveRecord
# remove_index :accounts, :name => :by_branch_party
def remove_index(table_name, options = {})
index_name = index_name(table_name, options)
- unless index_exists?(table_name, index_name, true)
+ unless index_name_exists?(table_name, index_name, true)
@logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
return
end
@@ -321,7 +365,7 @@ module ActiveRecord
end
def remove_index!(table_name, index_name) #:nodoc:
- execute "DROP INDEX #{quote_column_name(index_name)} ON #{table_name}"
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
end
# Rename an index.
@@ -350,11 +394,11 @@ module ActiveRecord
end
end
- # Verify the existence of an index.
+ # Verify the existence of an index with a given name.
#
# The default argument is returned if the underlying implementation does not define the indexes method,
# as there's no way to determine the correct answer in that case.
- def index_exists?(table_name, index_name, default)
+ def index_name_exists?(table_name, index_name, default)
return default unless respond_to?(:indexes)
indexes(table_name).detect { |i| i.name == index_name }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 4ee9fee4a9..be8d1bd76b 100755..100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -107,7 +107,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
- def disable_referential_integrity(&block)
+ def disable_referential_integrity
yield
end
@@ -142,9 +142,10 @@ module ActiveRecord
# this should be overridden by concrete adapters
end
- # Returns true if its safe to reload the connection between requests for development mode.
+ # Returns true if its required to reload the connection between requests for development mode.
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
def requires_reloading?
- true
+ false
end
# Checks whether the connection to the database is still active (i.e. not stale).
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 7c7bc5e292..aa3626a37e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -219,7 +219,7 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def disable_referential_integrity(&block) #:nodoc:
+ def disable_referential_integrity #:nodoc:
old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
begin
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e84242601b..2fe2ae7136 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -216,7 +216,10 @@ module ActiveRecord
super(connection, logger)
@connection_parameters, @config = connection_parameters, config
+ # @local_tz is initialized as nil to avoid warnings when connect tries to use it
+ @local_tz = nil
connect
+ @local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
end
# Is this connection alive and ready for queries?
@@ -372,7 +375,7 @@ module ActiveRecord
return false
end
- def disable_referential_integrity(&block) #:nodoc:
+ 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(";"))
end
@@ -606,27 +609,22 @@ module ActiveRecord
SQL
- indexes = []
-
- indexes = result.map do |row|
+ result.map do |row|
index_name = row[0]
unique = row[1] == 't'
indkey = row[2].split(" ")
oid = row[3]
- columns = query(<<-SQL, "Columns for index #{row[0]} on #{table_name}").inject({}) {|attlist, r| attlist[r[1]] = r[0]; attlist}
- SELECT a.attname, a.attnum
+ columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
+ SELECT a.attnum, a.attname
FROM pg_attribute a
WHERE a.attrelid = #{oid}
AND a.attnum IN (#{indkey.join(",")})
SQL
- column_names = indkey.map {|attnum| columns[attnum] }
- IndexDefinition.new(table_name, index_name, unique, column_names)
-
- end
-
- indexes
+ column_names = columns.values_at(*indkey).compact
+ column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names)
+ end.compact
end
# Returns the list of all column definitions for a table.
@@ -929,9 +927,8 @@ module ActiveRecord
# TIMESTAMP WITH ZONE types in UTC.
if ActiveRecord::Base.default_timezone == :utc
execute("SET time zone 'UTC'")
- else
- offset = Time.local(2000).utc_offset / 3600
- execute("SET time zone '#{offset}'")
+ elsif @local_tz
+ execute("SET time zone '#{@local_tz}'")
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index f295af16f0..0d9a86a1ea 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -6,6 +6,10 @@ module ActiveRecord
def self.sqlite3_connection(config) # :nodoc:
parse_sqlite_config!(config)
+ unless 'sqlite3' == config[:adapter]
+ raise ArgumentError, 'adapter name should be "sqlite3"'
+ end
+
unless self.class.const_defined?(:SQLite3)
require_library_or_gem(config[:adapter])
end
@@ -24,13 +28,13 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
class SQLite3Adapter < SQLiteAdapter # :nodoc:
-
+
# Returns the current database encoding format as a string, eg: 'UTF-8'
def encoding
if @connection.respond_to?(:encoding)
- @connection.encoding[0]['encoding']
+ @connection.encoding.to_s
else
- encoding = @connection.send(:get_query_pragma, 'encoding')
+ encoding = @connection.execute('PRAGMA encoding')
encoding[0]['encoding']
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index deb62e3802..1927585c49 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -151,7 +151,7 @@ module ActiveRecord
# DATABASE STATEMENTS ======================================
def execute(sql, name = nil) #:nodoc:
- catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
+ log(sql, name) { @connection.execute(sql) }
end
def update_sql(sql, name = nil) #:nodoc:
@@ -176,15 +176,15 @@ module ActiveRecord
end
def begin_db_transaction #:nodoc:
- catch_schema_changes { @connection.transaction }
+ @connection.transaction
end
def commit_db_transaction #:nodoc:
- catch_schema_changes { @connection.commit }
+ @connection.commit
end
def rollback_db_transaction #:nodoc:
- catch_schema_changes { @connection.rollback }
+ @connection.rollback
end
# SCHEMA STATEMENTS ========================================
@@ -246,6 +246,7 @@ module ActiveRecord
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.flatten.each do |column_name|
alter_table(table_name) do |definition|
definition.columns.delete(definition[column_name])
@@ -390,17 +391,6 @@ module ActiveRecord
end
end
- def catch_schema_changes
- return yield
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message =~ /database schema has changed/
- reconnect!
- retry
- else
- raise
- end
- end
-
def sqlite_version
@sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 9601ed6afd..999322129a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,4 +1,5 @@
module ActiveRecord
+ # = Active Record Counter Cache
module CounterCache
# Resets one or more counter caches to their correct value using an SQL
# count query. This is useful when adding new counter caches, or if the
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index fa7a19487c..b39b291352 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+
+ # = Active Record Dynamic Finder Match
+ #
+ # Provides dynamic attribute-based finders such as <tt>find_by_country</tt>
+ # if, for example, the <tt>Person</tt> has an attribute with that name.
class DynamicFinderMatch
def self.match(method)
df_match = self.new(method)
diff --git a/activerecord/lib/active_record/dynamic_scope_match.rb b/activerecord/lib/active_record/dynamic_scope_match.rb
index f796ba669a..15f65be6bc 100644
--- a/activerecord/lib/active_record/dynamic_scope_match.rb
+++ b/activerecord/lib/active_record/dynamic_scope_match.rb
@@ -1,4 +1,11 @@
module ActiveRecord
+
+ # = Active Record Dynamic Scope Match
+ #
+ # Provides dynamic attribute-based scopes such as <tt>scoped_by_price(4.99)</tt>
+ # if, for example, the <tt>Product</tt> has an attribute with that name. You can
+ # chain more <tt>scoped_by_* </tt> methods after the other. It acts like a named
+ # scope except that it's dynamic.
class DynamicScopeMatch
def self.match(method)
ds_match = self.new(method)
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index cf5ddca2ba..7aa725d095 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -1,4 +1,7 @@
module ActiveRecord
+
+ # = Active Record Errors
+ #
# Generic Active Record exception class.
class ActiveRecordError < StandardError
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
new file mode 100644
index 0000000000..71065f9908
--- /dev/null
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -0,0 +1,32 @@
+module ActiveRecord
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def initialize
+ super
+ @odd_or_even = false
+ end
+
+ def sql(event)
+ name = '%s (%.1fms)' % [event.payload[:name], event.duration]
+ sql = event.payload[:sql].squeeze(' ')
+
+ if odd?
+ name = color(name, :cyan, true)
+ sql = color(sql, nil, true)
+ else
+ name = color(name, :magenta, true)
+ end
+
+ debug " #{name} #{sql}"
+ end
+
+ def odd?
+ @odd_or_even = !@odd_or_even
+ end
+
+ def logger
+ ActiveRecord::Base.logger
+ end
+ end
+end
+
+ActiveRecord::LogSubscriber.attach_to :active_record \ No newline at end of file
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 940f825038..4c5e1ae218 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/aliasing'
module ActiveRecord
# Exception that can be raised to stop migrations from going backwards.
@@ -29,11 +30,15 @@ module ActiveRecord
end
end
- # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
- # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
- # push that change to other developers and to the production server. With migrations, you can describe the transformations
- # in self-contained classes that can be checked into version control systems and executed against another database that
- # might be one, two, or five versions behind.
+ # = Active Record Migrations
+ #
+ # Migrations can manage the evolution of a schema used by several physical
+ # databases. It's a solution to the common problem of adding a field to make
+ # a new feature work in your local database, but being unsure of how to
+ # push that change to other developers and to the production server. With
+ # migrations, you can describe the transformations in self-contained classes
+ # that can be checked into version control systems and executed against
+ # another database that might be one, two, or five versions behind.
#
# Example of a simple migration:
#
@@ -47,10 +52,13 @@ module ActiveRecord
# end
# end
#
- # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
- # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
- # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
- # but may also contain regular Ruby code for generating data needed for the transformations.
+ # This migration will add a boolean flag to the accounts table and remove it
+ # if you're backing out of the migration. It shows how all migrations have
+ # two class methods +up+ and +down+ that describes the transformations
+ # required to implement or remove the migration. These methods can consist
+ # of both the migration specific methods like add_column and remove_column,
+ # but may also contain regular Ruby code for generating data needed for the
+ # transformations.
#
# Example of a more complex migration that also needs to initialize data:
#
@@ -64,7 +72,9 @@ module ActiveRecord
# t.integer :position
# end
#
- # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ # SystemSetting.create :name => "notice",
+ # :label => "Use notice?",
+ # :value => 1
# end
#
# def self.down
@@ -72,35 +82,49 @@ module ActiveRecord
# end
# end
#
- # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
- # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
- # in one block call.
+ # This migration first adds the system_settings table, then creates the very
+ # first row in it using the Active Record model that relies on the table. It
+ # also uses the more advanced create_table syntax where you can specify a
+ # complete table schema in one block call.
#
# == Available transformations
#
- # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
- # that can then add columns to it, following the same format as add_column. See example above. The options hash is for
- # fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
+ # * <tt>create_table(name, options)</tt> Creates a table called +name+ and
+ # makes the table object available to a block that can then add columns to it,
+ # following the same format as add_column. See example above. The options hash
+ # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
+ # table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
- # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
- # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
+ # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
+ # to +new_name+.
+ # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
+ # to the table called +table_name+
# named +column_name+ specified to be one of the following types:
- # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
- # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be specified by passing an
- # +options+ hash like <tt>{ :default => 11 }</tt>. Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g. <tt>{ :limit => 50, :null => false }</tt>)
- # -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
- # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
- # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
- # parameters as add_column.
- # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
- # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
- # <tt>:name</tt> and <tt>:unique</tt> (e.g. <tt>{ :name => "users_name_index", :unique => true }</tt>).
- # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
+ # <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>,
+ # <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
+ # <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be
+ # specified by passing an +options+ hash like <tt>{ :default => 11 }</tt>.
+ # Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
+ # <tt>{ :limit => 50, :null => false }</tt>) -- see
+ # ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
+ # a column but keeps the type and content.
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
+ # the column to a different type using the same parameters as add_column.
+ # * <tt>remove_column(table_name, column_name)</tt>: Removes the column named
+ # +column_name+ from the table called +table_name+.
+ # * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
+ # with the name of the column. Other options include
+ # <tt>:name</tt> and <tt>:unique</tt> (e.g.
+ # <tt>{ :name => "users_name_index", :unique => true }</tt>).
+ # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified
+ # by +index_name+.
#
# == Irreversible transformations
#
- # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
- # an <tt>ActiveRecord::IrreversibleMigration</tt> exception in their +down+ method.
+ # Some transformations are destructive in a manner that cannot be reversed.
+ # Migrations of that kind should raise an <tt>ActiveRecord::IrreversibleMigration</tt>
+ # exception in their +down+ method.
#
# == Running migrations from within Rails
#
@@ -110,13 +134,15 @@ module ActiveRecord
# rails generate migration MyNewMigration
#
# where MyNewMigration is the name of your migration. The generator will
- # create an empty migration file <tt>timestamp_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
- # directory where <tt>timestamp</tt> is the UTC formatted date and time that the migration was generated.
+ # create an empty migration file <tt>timestamp_my_new_migration.rb</tt>
+ # in the <tt>db/migrate/</tt> directory where <tt>timestamp</tt> is the
+ # UTC formatted date and time that the migration was generated.
#
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
# MyNewMigration.
#
# There is a special syntactic shortcut to generate migrations that add fields to a table.
+ #
# rails generate migration add_fieldname_to_tablename fieldname:string
#
# This will generate the file <tt>timestamp_add_fieldname_to_tablename</tt>, which will look like this:
@@ -191,9 +217,10 @@ module ActiveRecord
#
# == Using a model after changing its table
#
- # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
- # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
- # after the new column was added. Example:
+ # Sometimes you'll want to add a column in a migration and populate it
+ # immediately after. In that case, you'll need to make a call to
+ # <tt>Base#reset_column_information</tt> in order to ensure that the model has the
+ # latest column data from after the new column was added. Example:
#
# class AddPeopleSalary < ActiveRecord::Migration
# def self.up
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 3d8f4a030b..c010dac64e 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -4,11 +4,12 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Named \Scopes
module NamedScope
extend ActiveSupport::Concern
module ClassMethods
- # Returns an anonymous scope.
+ # Returns an anonymous \scope.
#
# posts = Post.scoped
# posts.size # Fires "select count(*) from posts" and returns the count
@@ -18,14 +19,15 @@ module ActiveRecord
# fruits = fruits.where(:colour => 'red') if options[:red_only]
# fruits = fruits.limit(10) if limited?
#
- # Anonymous \scopes tend to be useful when procedurally generating complex queries, where passing
- # intermediate values (scopes) around as first-class objects is convenient.
+ # Anonymous \scopes tend to be useful when procedurally generating complex
+ # queries, where passing intermediate values (\scopes) around as first-class
+ # objects is convenient.
#
- # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
- def scoped(options = {}, &block)
+ # You can define a \scope that applies to all finders using
+ # ActiveRecord::Base.default_scope.
+ def scoped(options = nil)
if options.present?
- relation = scoped.apply_finder_options(options)
- block_given? ? relation.extending(Module.new(&block)) : relation
+ scoped.apply_finder_options(options)
else
current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
end
@@ -35,7 +37,7 @@ module ActiveRecord
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end
- # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
+ # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
#
# class Shirt < ActiveRecord::Base
@@ -85,18 +87,22 @@ module ActiveRecord
# end
def scope(name, scope_options = {}, &block)
name = name.to_sym
+ valid_scope_name?(name)
- if !scopes[name] && respond_to?(name, true)
- logger.warn "Creating scope :#{name}. " \
- "Overwriting existing method #{self.name}.#{name}."
- end
+ extension = Module.new(&block) if block_given?
scopes[name] = lambda do |*args|
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
- relation = scoped
- relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options
- block_given? ? relation.extending(Module.new(&block)) : relation
+ relation = if options.is_a?(Hash)
+ scoped.apply_finder_options(options)
+ elsif options
+ scoped.merge(options)
+ else
+ scoped
+ end
+
+ extension ? relation.extending(extension) : relation
end
singleton_class.send :define_method, name, &scopes[name]
@@ -106,7 +112,15 @@ module ActiveRecord
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
scope(*args, &block)
end
- end
+ protected
+
+ def valid_scope_name?(name)
+ if !scopes[name] && respond_to?(name, true)
+ logger.warn "Creating scope :#{name}. " \
+ "Overwriting existing method #{self.name}.#{name}."
+ end
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index eb9e792dd8..cf8c5aaf84 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -15,7 +15,7 @@ module ActiveRecord
self.nested_attributes_options = {}
end
- # == Nested Attributes
+ # = Active Record Nested Attributes
#
# Nested attributes allow you to save attributes on associated records
# through the parent. By default nested attribute updating is turned off,
@@ -25,6 +25,7 @@ module ActiveRecord
#
# The attribute writer is named after the association, which means that
# in the following example, two new methods are added to your model:
+ #
# <tt>author_attributes=(attributes)</tt> and
# <tt>pages_attributes=(attributes)</tt>.
#
@@ -132,7 +133,7 @@ module ActiveRecord
# member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
# member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
#
- # Alternatively, :reject_if also accepts a symbol for using methods:
+ # Alternatively, :reject_if also accepts a symbol for using methods:
#
# class Member < ActiveRecord::Base
# has_many :posts
@@ -144,7 +145,7 @@ module ActiveRecord
# accepts_nested_attributes_for :posts, :reject_if => :reject_posts
#
# def reject_posts(attributed)
- # attributed['title].blank?
+ # attributed['title'].blank?
# end
# end
#
@@ -212,7 +213,7 @@ module ActiveRecord
# that will reject a record where all the attributes are blank.
# [:limit]
# Allows you to specify the maximum number of the associated records that
- # can be processes with the nested attributes. If the size of the
+ # can be processed with the nested attributes. If the size of the
# nested attributes array exceeds the specified limit, NestedAttributes::TooManyRecords
# exception is raised. If omitted, any number associations can be processed.
# Note that the :limit option is only applicable to one-to-many associations.
@@ -278,7 +279,7 @@ module ActiveRecord
# Assigns the given attributes to the association.
#
# If update_only is false and the given attributes include an <tt>:id</tt>
- # that matches the existing record’s id, then the existing record will be
+ # that matches the existing record's id, then the existing record will be
# modified. If update_only is true, a new record is only created when no
# object exists. Otherwise a new record will be built.
#
@@ -295,7 +296,9 @@ module ActiveRecord
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy])
elsif attributes['id']
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+ self.send(association_name.to_s+'=', existing_record)
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
@@ -365,11 +368,16 @@ module ActiveRecord
unless reject_new_record?(association_name, attributes)
association.build(attributes.except(*UNASSIGNABLE_KEYS))
end
+
+ elsif existing_records.count == 0 #Existing record but not yet associated
+ existing_record = self.class.reflect_on_association(association_name).klass.find(attributes['id'])
+ association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
+ assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
+
elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s }
association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded?
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
- else
- raise_nested_attributes_record_not_found(association_name, attributes['id'])
+
end
end
end
@@ -389,7 +397,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be built by checking for
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
@@ -405,9 +413,5 @@ module ActiveRecord
end
end
- def raise_nested_attributes_record_not_found(association_name, record_id)
- reflection = self.class.reflect_on_association(association_name)
- raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}"
- end
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 0ea7fe7365..5f80bd86df 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ # = Active Record Observer
+ #
# Observer classes respond to lifecycle 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
@@ -105,8 +107,9 @@ module ActiveRecord
end
protected
+
def observed_subclasses
- observed_classes.sum([]) { |klass| klass.send(:subclasses) }
+ observed_classes.sum([]) { |klass| klass.send(:descendants) }
end
def observe_callbacks?
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 10788630a5..50166c4385 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -1,6 +1,8 @@
module ActiveRecord
+ # = Active Record Persistence
module Persistence
- # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
+ # Returns true if this object hasn't been saved yet -- that is, a record
+ # for the object doesn't exist in the data store yet; otherwise, returns false.
def new_record?
@new_record
end
@@ -10,7 +12,8 @@ module ActiveRecord
@destroyed
end
- # Returns if the record is persisted, i.e. it's not a new record and it was not destroyed.
+ # Returns if the record is persisted, i.e. it's not a new record and it was
+ # not destroyed.
def persisted?
!(new_record? || destroyed?)
end
@@ -69,8 +72,8 @@ module ActiveRecord
freeze
end
- # Deletes the record in the database and freezes this instance to reflect that no changes should
- # be made (since they can't be persisted).
+ # Deletes the record in the database and freezes this instance to reflect
+ # that no changes should be made (since they can't be persisted).
def destroy
if persisted?
self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
@@ -80,10 +83,13 @@ module ActiveRecord
freeze
end
- # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
- # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
- # identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
- # to render that instance using the companies/company partial instead of clients/client.
+ # Returns an instance of the specified +klass+ with the attributes of the
+ # current record. This is mostly useful in relation to single-table
+ # inheritance structures where you want a subclass to appear as the
+ # superclass. This can be used along with record identification in
+ # Action Pack to allow, say, <tt>Client < Company</tt> to do something
+ # like render <tt>:partial => @client.becomes(Company)</tt> to render that
+ # instance using the companies/company partial instead of clients/client.
#
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
# instance will affect the other.
@@ -104,14 +110,15 @@ module ActiveRecord
save(:validate => false)
end
- # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
- # fail and false will be returned.
+ # Updates all the attributes from the passed-in Hash and saves the record.
+ # If the object is invalid, the saving will fail and false will be returned.
def update_attributes(attributes)
self.attributes = attributes
save
end
- # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
+ # Updates an object just like Base.update_attributes but calls save! instead
+ # of save so an exception is raised if the record is invalid.
def update_attributes!(attributes)
self.attributes = attributes
save!
@@ -175,7 +182,7 @@ module ActiveRecord
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
- @attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
+ @attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
@attributes_cache = {}
self
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index 093c6c1e55..d9f85a4e5e 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Query Cache
class QueryCache
module ClassMethods
# Enable the query cache within the block if Active Record is configured.
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a32fb7d399..2808e199fe 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -9,6 +9,7 @@ require "active_model/railtie"
require "action_controller/railtie"
module ActiveRecord
+ # = Active Record Railtie
class Railtie < Rails::Railtie
config.active_record = ActiveSupport::OrderedOptions.new
@@ -25,9 +26,6 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- require "active_record/railties/log_subscriber"
- log_subscriber :active_record, ActiveRecord::Railties::LogSubscriber.new
-
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
@@ -68,7 +66,6 @@ module ActiveRecord
unless app.config.cache_classes
ActiveSupport.on_load(:active_record) do
ActionDispatch::Callbacks.after do
- ActiveRecord::Base.reset_subclasses
ActiveRecord::Base.clear_reloadable_connections!
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 006e64b115..7882f05d07 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -57,7 +57,7 @@ namespace :db do
end
rescue
case config['adapter']
- when 'mysql'
+ when /mysql/
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
@@ -171,6 +171,36 @@ namespace :db do
ActiveRecord::Migrator.run(:down, "db/migrate/", version)
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
+
+ desc "Display status of migrations"
+ task :status => :environment do
+ config = ActiveRecord::Base.configurations[Rails.env || 'development']
+ ActiveRecord::Base.establish_connection(config)
+ unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
+ puts 'Schema migrations table does not exist yet.'
+ next # means "return" for rake task
+ end
+ db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}")
+ file_list = []
+ Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file|
+ # only files matching "20091231235959_some_name.rb" pattern
+ if match_data = /(\d{14})_(.+)\.rb/.match(file)
+ status = db_list.delete(match_data[1]) ? 'up' : 'down'
+ file_list << [status, match_data[1], match_data[2]]
+ end
+ end
+ # output
+ puts "\ndatabase: #{config['database']}\n\n"
+ puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name"
+ puts "-" * 50
+ file_list.each do |file|
+ puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
+ end
+ db_list.each do |version|
+ puts "#{'up'.center(8)} #{version.ljust(14)} *** NO FILE ***"
+ end
+ puts
+ end
end
desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
@@ -194,7 +224,7 @@ namespace :db do
task :charset => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
when 'postgresql'
@@ -212,7 +242,7 @@ namespace :db do
task :collation => :environment do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.collation
else
@@ -313,7 +343,7 @@ namespace :db do
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[Rails.env]["adapter"]
- when "mysql", "oci", "oracle"
+ when /mysql/, "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
@@ -361,7 +391,7 @@ namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
@@ -395,7 +425,7 @@ namespace :db do
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
- when "mysql"
+ when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
@@ -451,7 +481,7 @@ task 'test:prepare' => 'db:test:prepare'
def drop_database(config)
case config['adapter']
- when 'mysql'
+ when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/
diff --git a/activerecord/lib/active_record/railties/log_subscriber.rb b/activerecord/lib/active_record/railties/log_subscriber.rb
deleted file mode 100644
index 31b98bb6ed..0000000000
--- a/activerecord/lib/active_record/railties/log_subscriber.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-module ActiveRecord
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def initialize
- super
- @odd_or_even = false
- end
-
- def sql(event)
- name = '%s (%.1fms)' % [event.payload[:name], event.duration]
- sql = event.payload[:sql].squeeze(' ')
-
- if odd?
- name = color(name, :cyan, true)
- sql = color(sql, nil, true)
- else
- name = color(name, :magenta, true)
- end
-
- debug " #{name} #{sql}"
- end
-
- def odd?
- @odd_or_even = !@odd_or_even
- end
-
- def logger
- ActiveRecord::Base.logger
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0e48e229b3..a82e5d7ed1 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,12 +1,16 @@
module ActiveRecord
+ # = Active Record Reflection
module Reflection # :nodoc:
extend ActiveSupport::Concern
- # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
- # This information can, for example, be used in a form builder that took an Active Record object and created input
- # fields for all of the attributes depending on their type and displayed the associations to other objects.
+ # Reflection allows you to interrogate Active Record classes and objects
+ # about their associations and aggregations. This information can,
+ # for example, be used in a form builder that took an Active Record object
+ # and created input fields for all of the attributes depending on their type
+ # and displayed the associations to other objects.
#
- # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
+ # You can find the interface for the AggregateReflection and AssociationReflection
+ # classes in the abstract MacroReflection class.
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
@@ -43,8 +47,11 @@ module ActiveRecord
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
end
- # Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
- # certain association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>, <tt>:belongs_to</tt>) for that as the first parameter.
+ # Returns an array of AssociationReflection objects for all the
+ # associations in the class. If you only want to reflect on a certain
+ # association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
+ # <tt>:belongs_to</tt>) for that as the first parameter.
+ #
# Example:
#
# Account.reflect_on_all_associations # returns an array of all associations
@@ -71,8 +78,9 @@ module ActiveRecord
end
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
- # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ # Abstract base class for AggregateReflection and AssociationReflection that
+ # describes the interface available for both of those classes. Objects of
+ # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
attr_reader :active_record
@@ -80,13 +88,15 @@ module ActiveRecord
@macro, @name, @options, @active_record = macro, name, options, active_record
end
- # Returns the name of the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return
- # <tt>:balance</tt> or for <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
+ # Returns the name of the macro. For example, <tt>composed_of :balance,
+ # :class_name => 'Money'</tt> will return <tt>:balance</tt> or for
+ # <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
def name
@name
end
- # Returns the macro type. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
+ # Returns the macro type. For example,
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
# or for <tt>has_many :clients</tt> will return <tt>:has_many</tt>.
def macro
@macro
@@ -132,11 +142,13 @@ module ActiveRecord
end
- # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
+ # Holds all the meta-data about an aggregation as it was specified in the
+ # Active Record class.
class AggregateReflection < MacroReflection #:nodoc:
end
- # Holds all the meta-data about an association as it was specified in the Active Record class.
+ # Holds all the meta-data about an association as it was specified in the
+ # Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
# Returns the target association's class:
#
@@ -165,14 +177,14 @@ module ActiveRecord
klass.new(*options)
end
- # Creates a new instance of the associated class, and immediates saves it
+ # Creates a new instance of the associated class, and immediately saves it
# with ActiveRecord::Base#save. +options+ will be passed to the class's
# creation method. Returns the newly created object.
def create_association(*options)
klass.create(*options)
end
- # Creates a new instance of the associated class, and immediates saves it
+ # Creates a new instance of the associated class, and immediately saves it
# with ActiveRecord::Base#save!. +options+ will be passed to the class's
# creation method. If the created record doesn't pass validations, then an
# exception will be raised.
@@ -267,10 +279,10 @@ module ActiveRecord
# Returns whether or not the association should be validated as part of
# the parent's validation.
#
- # Unless you explicitely disable validation with
+ # Unless you explicitly disable validation with
# <tt>:validate => false</tt>, it will take place when:
#
- # * you explicitely enable validation; <tt>:validate => true</tt>
+ # * you explicitly enable validation; <tt>:validate => true</tt>
# * you use autosave; <tt>:autosave => true</tt>
# * the association is a +has_many+ association
def validate?
@@ -306,9 +318,12 @@ module ActiveRecord
end
end
- # Holds all the meta-data about a :through association as it was specified in the Active Record class.
+ # Holds all the meta-data about a :through association as it was specified
+ # in the Active Record class.
class ThroughReflection < AssociationReflection #:nodoc:
- # Gets the source of the through reflection. It checks both a singularized and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
+ # Gets the source of the through reflection. It checks both a singularized
+ # and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
+ #
# (The <tt>:tags</tt> association on Tagging below.)
#
# class Post < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 99c914d7fc..bc708b573f 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -1,6 +1,7 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Relation
class Relation
JoinOperation = Struct.new(:relation, :join_class, :on)
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
@@ -9,13 +10,13 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
attr_reader :table, :klass
attr_accessor :extensions
- def initialize(klass, table, &block)
+ def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
@@ -24,12 +25,10 @@ module ActiveRecord
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@extensions = []
-
- apply_modules(Module.new(&block)) if block_given?
end
def new(*args, &block)
- with_create_scope { @klass.new(*args, &block) }
+ scoping { @klass.new(*args, &block) }
end
def initialize_copy(other)
@@ -39,11 +38,11 @@ module ActiveRecord
alias build new
def create(*args, &block)
- with_create_scope { @klass.create(*args, &block) }
+ scoping { @klass.create(*args, &block) }
end
def create!(*args, &block)
- with_create_scope { @klass.create!(*args, &block) }
+ scoping { @klass.create!(*args, &block) }
end
def respond_to?(method, include_private = false)
@@ -67,7 +66,7 @@ module ActiveRecord
preload += @includes_values unless eager_loading?
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- # @readonly_value is true only if set explicity. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT.
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
@records.each { |record| record.readonly! } if readonly
@@ -75,10 +74,12 @@ module ActiveRecord
@records
end
+ # Returns size of the records.
def size
loaded? ? @records.length : count
end
+ # Returns true if there are no records.
def empty?
loaded? ? @records.empty? : count.zero?
end
@@ -99,6 +100,25 @@ module ActiveRecord
end
end
+ # Scope all queries to the current scope.
+ #
+ # ==== Example
+ #
+ # Comment.where(:post_id => 1).scoping do
+ # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
+ # end
+ #
+ # Please check unscoped if you want to remove all previous scopes (including
+ # the default_scope) during the execution of a block.
+ def scoping
+ @klass.scoped_methods << self
+ begin
+ yield
+ ensure
+ @klass.scoped_methods.pop
+ end
+ end
+
# Updates all records with details given if they match a set of conditions supplied, limits and order can
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
@@ -240,8 +260,9 @@ module ActiveRecord
# Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
# Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
#
- # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
- # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
+ # Both calls delete the affected posts all at once with a single DELETE statement.
+ # If you need to destroy dependent associations or call your <tt>before_*</tt> or
+ # +after_destroy+ callbacks, use the +destroy_all+ method instead.
def delete_all(conditions = nil)
conditions ? where(conditions).delete_all : arel.delete.tap { reset }
end
@@ -301,7 +322,6 @@ module ActiveRecord
if where.is_a?(Arel::Predicates::Equality)
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
end
-
hash
end
end
@@ -332,7 +352,7 @@ module ActiveRecord
elsif @klass.scopes[method]
merge(@klass.send(method, *args, &block))
elsif @klass.respond_to?(method)
- @klass.send(:with_scope, self) { @klass.send(method, *args, &block) }
+ scoping { @klass.send(method, *args, &block) }
elsif arel.respond_to?(method)
arel.send(method, *args, &block)
elsif match = DynamicFinderMatch.match(method)
@@ -351,10 +371,6 @@ module ActiveRecord
private
- def with_create_scope
- @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
- end
-
def references_eager_loaded_tables?
# always convert table names to downcase as in Oracle quoted table names are in uppercase
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7a0c9dc612..f39951e16c 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -87,8 +87,8 @@ module ActiveRecord
# person.visits += 1
# person.save!
# end
- def find(*args, &block)
- return to_a.find(&block) if block_given?
+ def find(*args)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
options = args.extract_options!
@@ -259,8 +259,8 @@ module ActiveRecord
record
end
- def find_with_ids(*ids, &block)
- return to_a.find(&block) if block_given?
+ def find_with_ids(*ids)
+ return to_a.find { |*block_args| yield(*block_args) } if block_given?
expects_array = ids.first.kind_of?(Array)
return ids.first if expects_array && ids.first.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index d0efa2189d..5cea2328e8 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,15 +20,13 @@ module ActiveRecord
table = Arel::Table.new(table_name, :engine => @engine)
end
- unless attribute = table[column]
- raise StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
- end
+ attribute = table[column] || Arel::Attribute.new(table, column)
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
values = value.to_a
attribute.in(values)
- when Range
+ when Range, Arel::Relation
attribute.in(value)
else
attribute.eq(value)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 7a48a6596a..4692271266 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -5,79 +5,92 @@ module ActiveRecord
module QueryMethods
extend ActiveSupport::Concern
- included do
- (ActiveRecord::Relation::ASSOCIATION_METHODS + ActiveRecord::Relation::MULTI_VALUE_METHODS).each do |query_method|
- attr_accessor :"#{query_method}_values"
-
- next if [:where, :having, :select].include?(query_method)
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.#{query_method}_values += value if value.present?
- new_relation
- end
- CEVAL
- end
+ attr_accessor :includes_values, :eager_load_values, :preload_values,
+ :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values,
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, :from_value
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def select(*args, &block)
- if block_given?
- to_a.select(&block)
- else
- new_relation = clone
- value = Array.wrap(args.flatten).reject {|x| x.blank? }
- new_relation.select_values += value if value.present?
- new_relation
- end
- end
- CEVAL
-
- [:where, :having].each do |query_method|
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(*args, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- value = build_where(*args)
- new_relation.#{query_method}_values += Array.wrap(value) if value.present?
- new_relation
- end
- CEVAL
- end
+ def includes(*args)
+ args.reject! { |a| a.blank? }
+ clone.tap { |r| r.includes_values += args if args.present? }
+ end
+
+ def eager_load(*args)
+ clone.tap { |r| r.eager_load_values += args if args.present? }
+ end
- ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method|
- attr_accessor :"#{query_method}_value"
+ def preload(*args)
+ clone.tap { |r| r.preload_values += args if args.present? }
+ end
- class_eval <<-CEVAL, __FILE__, __LINE__ + 1
- def #{query_method}(value = true, &block)
- new_relation = clone
- new_relation.send(:apply_modules, Module.new(&block)) if block_given?
- new_relation.#{query_method}_value = value
- new_relation
- end
- CEVAL
+ def select(*args)
+ if block_given?
+ to_a.select { |*block_args| yield(*block_args) }
+ else
+ clone.tap { |r| r.select_values += args if args.present? }
end
end
- def extending(*modules)
- new_relation = clone
- new_relation.send :apply_modules, *modules
- new_relation
+ def group(*args)
+ clone.tap { |r| r.group_values += args if args.present? }
end
- def lock(locks = true, &block)
- relation = clone
- relation.send(:apply_modules, Module.new(&block)) if block_given?
+ def order(*args)
+ clone.tap { |r| r.order_values += args if args.present? }
+ end
+
+ def reorder(*args)
+ clone.tap { |r| r.order_values = args if args.present? }
+ end
+ def joins(*args)
+ args.flatten!
+ clone.tap { |r| r.joins_values += args if args.present? }
+ end
+
+ def where(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
+ end
+
+ def having(*args)
+ value = build_where(*args)
+ clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ end
+
+ def limit(value = true)
+ clone.tap { |r| r.limit_value = value }
+ end
+
+ def offset(value = true)
+ clone.tap { |r| r.offset_value = value }
+ end
+
+ def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap {|new_relation| new_relation.lock_value = locks || true }
+ clone.tap { |r| r.lock_value = locks || true }
else
- clone.tap {|new_relation| new_relation.lock_value = false }
+ clone.tap { |r| r.lock_value = false }
end
end
+ def readonly(value = true)
+ clone.tap { |r| r.readonly_value = value }
+ end
+
+ def create_with(value = true)
+ clone.tap { |r| r.create_with_value = value }
+ end
+
+ def from(value = true)
+ clone.tap { |r| r.from_value = value }
+ end
+
+ def extending(*modules, &block)
+ modules << Module.new(&block) if block_given?
+ clone.tap { |r| r.send(:apply_modules, *modules) }
+ end
+
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
@@ -116,45 +129,7 @@ module ActiveRecord
def build_arel
arel = table
- joined_associations = []
- association_joins = []
-
- joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
-
- joins.each do |join|
- association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
- end
-
- stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
-
- non_association_joins = (joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
- custom_joins = custom_join_sql(*non_association_joins)
-
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
-
- join_dependency.graft(*stashed_association_joins)
-
- @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
-
- to_join = []
-
- join_dependency.join_associations.each do |association|
- if (association_relation = association.relation).is_a?(Array)
- to_join << [association_relation.first, association.join_class, association.association_join.first]
- to_join << [association_relation.last, association.join_class, association.association_join.last]
- else
- to_join << [association_relation, association.join_class, association.association_join]
- end
- end
-
- to_join.each do |tj|
- unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
- joined_associations << tj
- arel = arel.join(tj[0], tj[1]).on(*tj[2])
- end
- end
-
- arel = arel.join(custom_joins)
+ arel = build_joins(arel, @joins_values) if @joins_values.present?
@where_values.uniq.each do |where|
next if where.blank?
@@ -168,31 +143,18 @@ module ActiveRecord
end
end
- @having_values.uniq.each do |h|
- arel = h.is_a?(String) ? arel.having(h) : arel.having(*h)
- end
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
arel = arel.take(@limit_value) if @limit_value.present?
arel = arel.skip(@offset_value) if @offset_value.present?
- arel = arel.group(*@group_values.uniq.select{|g| g.present?})
-
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}.map(&:to_s))
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
- selects = @select_values.uniq
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
- quoted_table_name = @klass.quoted_table_name
-
- if selects.present?
- selects.each do |s|
- @implicit_readonly = false
- arel = arel.project(s) if s.present?
- end
- else
- arel = arel.project(quoted_table_name + '.*')
- end
+ arel = build_select(arel, @select_values.uniq)
- arel = @from_value.present? ? arel.from(@from_value) : arel.from(quoted_table_name)
+ arel = arel.from(@from_value) if @from_value.present?
case @lock_value
when TrueClass
@@ -221,6 +183,63 @@ module ActiveRecord
private
+ def build_joins(relation, joins)
+ joined_associations = []
+ association_joins = []
+
+ joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
+
+ joins.each do |join|
+ association_joins << join if [Hash, Array, Symbol].include?(join.class) && !array_of_strings?(join)
+ end
+
+ stashed_association_joins = joins.select {|j| j.is_a?(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)}
+
+ non_association_joins = (joins - association_joins - stashed_association_joins)
+ custom_joins = custom_join_sql(*non_association_joins)
+
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
+
+ join_dependency.graft(*stashed_association_joins)
+
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
+
+ to_join = []
+
+ join_dependency.join_associations.each do |association|
+ if (association_relation = association.relation).is_a?(Array)
+ to_join << [association_relation.first, association.join_class, association.association_join.first]
+ to_join << [association_relation.last, association.join_class, association.association_join.last]
+ else
+ to_join << [association_relation, association.join_class, association.association_join]
+ end
+ end
+
+ to_join.each do |tj|
+ unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
+ joined_associations << tj
+ relation = relation.join(tj[0], tj[1]).on(*tj[2])
+ end
+ end
+
+ relation.join(custom_joins)
+ end
+
+ def build_select(arel, selects)
+ if selects.present?
+ @implicit_readonly = false
+ # TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
+ # Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
+ if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
+ arel.project(*selects)
+ else
+ arel.project(selects.last)
+ end
+ else
+ arel.project(@klass.quoted_table_name + '.*')
+ end
+ end
+
def apply_modules(modules)
values = Array.wrap(modules)
@extensions += values if values.present?
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index bb1f138f5b..7712ad2569 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -6,7 +6,7 @@ module ActiveRecord
merged_relation = clone
return merged_relation unless r
- (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).reject {|m| [:joins, :where].include?(m)}.each do |method|
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method|
value = r.send(:"#{method}_values")
merged_relation.send(:"#{method}_values=", value) if value.present?
end
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index a833356d15..e2783087ec 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/object/blank'
module ActiveRecord
+ # = Active Record Schema
+ #
# Allows programmers to programmatically define a schema in a portable
# DSL. This means you can define tables, indexes, etc. without using SQL
# directly, so your applications can more easily support multiple
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index cd54653581..a4757773d8 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -2,6 +2,8 @@ require 'stringio'
require 'active_support/core_ext/big_decimal'
module ActiveRecord
+ # = Active Record Schema Dumper
+ #
# This class is used to dump the database schema for some connection to some
# output format (i.e., ActiveRecord::Schema).
class SchemaDumper #:nodoc:
@@ -39,13 +41,14 @@ module ActiveRecord
define_params = @version ? ":version => #{@version}" : ""
stream.puts <<HEADER
-# This file is auto-generated from the current state of the database. Instead of editing this file,
-# please use the migrations feature of Active Record to incrementally modify your database, and
-# then regenerate this schema definition.
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
#
-# Note that this schema.rb definition is the authoritative source for your database schema. If you need
-# to create the application database on another system, you should be using db:schema:load, not running
-# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
@@ -173,15 +176,15 @@ HEADER
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
- statment_parts = [ ('add_index ' + index.table.inspect) ]
- statment_parts << index.columns.inspect
- statment_parts << (':name => ' + index.name.inspect)
- statment_parts << ':unique => true' if index.unique
+ statement_parts = [ ('add_index ' + index.table.inspect) ]
+ statement_parts << index.columns.inspect
+ statement_parts << (':name => ' + index.name.inspect)
+ statement_parts << ':unique => true' if index.unique
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
- statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
+ statement_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present?
- ' ' + statment_parts.join(', ')
+ ' ' + statement_parts.join(', ')
end
stream.puts add_index_statements.sort.join("\n")
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index b49471f7ab..2d8bd184f3 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,4 +1,5 @@
module ActiveRecord #:nodoc:
+ # = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
@@ -22,6 +23,7 @@ module ActiveRecord #:nodoc:
private
# Add associations specified via the <tt>:includes</tt> option.
+ #
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index 931872eded..b88d550086 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -1,4 +1,6 @@
module ActiveRecord
+ # = Active Record Session Store
+ #
# A session store backed by an Active Record class. A default class is
# provided, but any object duck-typing to an Active Record Session class
# with text +session_id+ and +data+ attributes is sufficient.
@@ -7,6 +9,7 @@ module ActiveRecord
# +id+ (numeric primary key),
# +session_id+ (text, or longtext if your session data exceeds 65K), and
# +data+ (text or longtext; careful if your session data exceeds 65KB).
+ #
# The +session_id+ column should always be indexed for speedy lookups.
# Session data is marshaled to the +data+ column in Base64 format.
# If the data you write is larger than the column's size limit,
@@ -14,9 +17,11 @@ module ActiveRecord
#
# You may configure the table name, primary key, and data column.
# For example, at the end of <tt>config/environment.rb</tt>:
+ #
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
# ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
+ #
# Note that setting the primary key to the +session_id+ frees you from
# having a separate +id+ column if you don't want it. However, you must
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
@@ -29,8 +34,11 @@ module ActiveRecord
# You may provide your own session class implementation, whether a
# feature-packed Active Record or a bare-metal high-performance SQL
# store, by setting
+ #
# ActiveRecord::SessionStore.session_class = MySessionClass
+ #
# You must implement these methods:
+ #
# self.find_by_session_id(session_id)
# initialize(hash_of_session_id_and_data)
# attr_reader :session_id
@@ -310,6 +318,14 @@ module ActiveRecord
sid
end
+ def destroy(env)
+ if sid = current_session_id(env)
+ Base.silence do
+ get_session_model(env, sid).destroy
+ end
+ end
+ end
+
def get_session_model(env, sid)
if env[ENV_SESSION_OPTIONS_KEY][:id].nil?
env[SESSION_RECORD_KEY] = find_session(sid)
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 0a77ad5fd7..e61a378d17 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -1,4 +1,7 @@
module ActiveRecord
+ # = Active Record Test Case
+ #
+ # Defines some test assertions to test against SQL queries.
class TestCase < ActiveSupport::TestCase #:nodoc:
def assert_date_from_db(expected, actual, message = nil)
# SybaseAdapter doesn't have a separate column type just for dates,
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 9fba8f0aca..ffd12d2082 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,11 +1,16 @@
module ActiveRecord
- # Active Record automatically timestamps create and update operations if the table has fields
- # named created_at/created_on or updated_at/updated_on.
+ # = Active Record Timestamp
+ #
+ # Active Record automatically timestamps create and update operations if the
+ # table has fields named <tt>created_at/created_on</tt> or
+ # <tt>updated_at/updated_on</tt>.
+ #
+ # Timestamping can be turned off by setting:
#
- # Timestamping can be turned off by setting
# <tt>ActiveRecord::Base.record_timestamps = false</tt>
#
- # Timestamps are in the local timezone by default but you can use UTC by setting
+ # Timestamps are in the local timezone by default but you can use UTC by setting:
+ #
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
module Timestamp
extend ActiveSupport::Concern
@@ -16,8 +21,9 @@ module ActiveRecord
end
# Saves the record with the updated_at/on attributes set to the current time.
- # If the save fails because of validation errors, an ActiveRecord::RecordInvalid exception is raised.
- # If an attribute name is passed, that attribute is used for the touch instead of the updated_at/on attributes.
+ # If the save fails because of validation errors, an
+ # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed,
+ # that attribute is used for the touch instead of the updated_at/on attributes.
#
# Examples:
#
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 620758f5af..a7709b9489 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -11,7 +11,8 @@ module ActiveRecord
included do
define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
end
-
+ # = Active Record Transactions
+ #
# Transactions are protective blocks where SQL statements are only permanent
# if they can all succeed as one atomic action. The classic example is a
# transfer between two accounts where you can only have a deposit if the
@@ -19,7 +20,8 @@ module ActiveRecord
# the database and guard the data against program errors or database
# break-downs. So basically you should use transaction blocks whenever you
# have a number of statements that must be executed together or not at all.
- # Example:
+ #
+ # For example:
#
# ActiveRecord::Base.transaction do
# david.withdrawal(100)
@@ -320,6 +322,7 @@ module ActiveRecord
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
if restore_state
+ @attributes = @attributes.dup if @attributes.frozen?
@new_record = restore_state[:new_record]
@destroyed = restore_state[:destroyed]
if restore_state[:id]
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index be64e00bd1..b98fd353aa 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -1,6 +1,9 @@
module ActiveRecord
+ # = Active Record Validations
+ #
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
# +record+ method to retrieve the record which did not validate.
+ #
# begin
# complex_operation_that_calls_save!_internally
# rescue ActiveRecord::RecordInvalid => invalid
@@ -49,12 +52,12 @@ module ActiveRecord
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?(context = nil)
context ||= (new_record? ? :create : :update)
- super(context)
+ output = super(context)
deprecated_callback_method(:validate)
deprecated_callback_method(:"validate_on_#{context}")
- errors.empty?
+ errors.empty? && output
end
protected
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index e41635134c..0b0f5682aa 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -3,7 +3,7 @@ module ActiveRecord
class AssociatedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
- record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
end
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 6283bdd0d6..1c9ecc7b1b 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -32,7 +32,7 @@ module ActiveRecord
end
if relation.exists?
- record.errors.add(attribute, :taken, :default => options[:message], :value => value)
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
end
end
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 bbb7c53d86..8ac21c1410 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,11 +1,17 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def self.up<% attributes.each do |attribute| %>
- <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ def self.up
+<% attributes.each do |attribute| -%>
+ <%- if migration_action -%>
+ <%= migration_action %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'add' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+<%- end -%>
end
- def self.down<% attributes.reverse.each do |attribute| %>
- <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end -%>
- <%- end %>
+ def self.down
+<% attributes.reverse.each do |attribute| -%>
+ <%- if migration_action -%>
+ <%= migration_action == 'add' ? 'remove' : 'add' %>_column :<%= table_name %>, :<%= attribute.name %><% if migration_action == 'remove' %>, :<%= attribute.type %><% end %>
+ <%- end -%>
+<%- end -%>
end
end
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 539c2517ee..960c29c49c 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -22,7 +22,7 @@ module ActiveRecord
def create_module_file
return if class_path.empty?
- template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb")
+ template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
end
hook_for :test_framework
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index fc08c2178a..646aa88d80 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -65,15 +65,14 @@ class AdapterTest < ActiveRecord::TestCase
end
def test_not_specifying_database_name_for_cross_database_selects
- assert_nothing_raised do
- ActiveRecord::Base.establish_connection({
- :adapter => 'mysql',
- :username => 'rails'
- })
- ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ begin
+ assert_nothing_raised do
+ ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['arunit'].except(:database))
+ ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses"
+ end
+ ensure
+ ActiveRecord::Base.establish_connection 'arunit'
end
-
- ActiveRecord::Base.establish_connection 'arunit'
end
end
diff --git a/activerecord/test/cases/connection_test_firebird.rb b/activerecord/test/cases/adapters/firebird/connection_test.rb
index f57ea686a5..f57ea686a5 100644
--- a/activerecord/test/cases/connection_test_firebird.rb
+++ b/activerecord/test/cases/adapters/firebird/connection_test.rb
diff --git a/activerecord/test/cases/default_test_firebird.rb b/activerecord/test/cases/adapters/firebird/default_test.rb
index 713c7e11bf..713c7e11bf 100644
--- a/activerecord/test/cases/default_test_firebird.rb
+++ b/activerecord/test/cases/adapters/firebird/default_test.rb
diff --git a/activerecord/test/cases/migration_test_firebird.rb b/activerecord/test/cases/adapters/firebird/migration_test.rb
index 710661b9bd..710661b9bd 100644
--- a/activerecord/test/cases/migration_test_firebird.rb
+++ b/activerecord/test/cases/adapters/firebird/migration_test.rb
diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index d7431e5158..6e6645511c 100644
--- a/activerecord/test/cases/active_schema_test_mysql.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -17,8 +17,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
def test_add_index
- # add_index calls index_exists? which can't work since execute is stubbed
- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_exists?) do |*|
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
false
end
expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
@@ -35,7 +35,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_exists?)
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
end
def test_drop_table
diff --git a/activerecord/test/cases/connection_test_mysql.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index 8e4842a1b6..8e4842a1b6 100644
--- a/activerecord/test/cases/connection_test_mysql.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
diff --git a/activerecord/test/cases/reserved_word_test_mysql.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 90d8b0d923..90d8b0d923 100644
--- a/activerecord/test/cases/reserved_word_test_mysql.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
diff --git a/activerecord/test/cases/synonym_test_oracle.rb b/activerecord/test/cases/adapters/oracle/synonym_test.rb
index b9a422a6ca..b9a422a6ca 100644
--- a/activerecord/test/cases/synonym_test_oracle.rb
+++ b/activerecord/test/cases/adapters/oracle/synonym_test.rb
diff --git a/activerecord/test/cases/active_schema_test_postgresql.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index 4f04c6735c..f106e14319 100644
--- a/activerecord/test/cases/active_schema_test_postgresql.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -10,7 +10,10 @@ class PostgresqlActiveSchemaTest < Test::Unit::TestCase
end
def teardown
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:alias_method, :execute, :real_execute)
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :real_execute
+ end
end
def test_create_database_with_encoding
diff --git a/activerecord/test/cases/datatype_test_postgresql.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
index 3c2d9fb7bd..5bb8fa2f93 100644
--- a/activerecord/test/cases/datatype_test_postgresql.rb
+++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb
@@ -53,7 +53,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase
@connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)")
@first_oid = PostgresqlOid.find(1)
-
+
@connection.execute("INSERT INTO postgresql_timestamp_with_zones (time) VALUES ('2010-01-01 10:00:00-1')")
end
diff --git a/activerecord/test/cases/schema_authorization_test_postgresql.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
index 2860f1ad48..6f372edc38 100644
--- a/activerecord/test/cases/schema_authorization_test_postgresql.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb
@@ -66,7 +66,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase
end
end
end
-
+
def test_tables_in_current_schemas
assert !@connection.tables.include?(TABLE_NAME)
USERS.each do |u|
diff --git a/activerecord/test/cases/schema_test_postgresql.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 3ed9b1974d..a5c3e69af9 100644
--- a/activerecord/test/cases/schema_test_postgresql.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -9,9 +9,11 @@ class SchemaTest < ActiveRecord::TestCase
CAPITALIZED_TABLE_NAME = 'Things'
INDEX_A_NAME = 'a_index_things_on_name'
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
+ INDEX_C_NAME = 'c_index_full_text_search'
INDEX_A_COLUMN = 'name'
INDEX_B_COLUMN_S1 = 'email'
INDEX_B_COLUMN_S2 = 'moment'
+ INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
COLUMNS = [
'id integer',
'name character varying(50)',
@@ -45,6 +47,8 @@ class SchemaTest < ActiveRecord::TestCase
@connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_A_COLUMN});"
@connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S1});"
@connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});"
+ @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
end
def teardown
diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
index 575b4806c1..575b4806c1 100644
--- a/activerecord/test/cases/copy_table_test_sqlite.rb
+++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
new file mode 100644
index 0000000000..934cf72f72
--- /dev/null
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -0,0 +1,56 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class SQLite3AdapterTest < ActiveRecord::TestCase
+ def test_connection_no_db
+ assert_raises(ArgumentError) do
+ Base.sqlite3_connection {}
+ end
+ end
+
+ def test_connection_no_adapter
+ assert_raises(ArgumentError) do
+ Base.sqlite3_connection :database => ':memory:'
+ end
+ end
+
+ def test_connection_wrong_adapter
+ assert_raises(ArgumentError) do
+ Base.sqlite3_connection :database => ':memory:',:adapter => 'vuvuzela'
+ end
+ end
+
+ def test_bad_timeout
+ assert_raises(TypeError) do
+ Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => 'usa'
+ end
+ end
+
+ # connection is OK with a nil timeout
+ def test_nil_timeout
+ conn = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => nil
+ assert conn, 'made a connection'
+ end
+
+ def test_connect
+ conn = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => 100
+ assert conn, 'should have connection'
+ end
+
+ # sqlite3 defaults to UTF-8 encoding
+ def test_encoding
+ conn = Base.sqlite3_connection :database => ':memory:',
+ :adapter => 'sqlite3',
+ :timeout => 100
+ assert_equal 'UTF-8', conn.encoding
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index e5fc1a2046..74588b4f47 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -121,34 +121,6 @@ class AggregationsTest < ActiveRecord::TestCase
end
end
-class DeprecatedAggregationsTest < ActiveRecord::TestCase
- class Person < ActiveRecord::Base; end
-
- def test_conversion_block_is_deprecated
- assert_deprecated 'conversion block has been deprecated' do
- Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money }
- end
- end
-
- def test_conversion_block_used_when_converter_option_is_nil
- assert_deprecated 'conversion block has been deprecated' do
- Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money }
- end
- assert_raise(NoMethodError) { Person.new.balance = 5 }
- end
-
- def test_converter_option_overrides_conversion_block
- assert_deprecated 'conversion block has been deprecated' do
- Person.composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| Money.new(balance) }) { |balance| balance.to_money }
- end
-
- person = Person.new
- assert_nothing_raised { person.balance = 5 }
- assert_equal 5, person.balance.amount
- assert_kind_of Money, person.balance
- end
-end
-
class OverridingAggregationsTest < ActiveRecord::TestCase
class Name; end
class DifferentName; end
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 050b730dda..9c5dcc2ad9 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -41,9 +41,9 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
assert_nothing_raised do
- Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('primary_contacts_people_2.id').all
+ Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').all
end
- assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('primary_contacts_people_2.id').first
+ assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first
end
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 45c7498013..5e3ba778f3 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -21,6 +21,68 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
Client.destroyed_client_ids.clear
end
+ def test_create_by
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id post.id
+
+ reader = person.readers.create_by_post_id post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_create_by_multi
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+
+ reader = person.readers.create_by_post_id_and_skimmer post.id, false
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_create_by
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find_by_post_id post.id
+
+ reader = person.readers.find_or_create_by_post_id post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+ def test_find_or_create
+ person = Person.create! :first_name => 'tenderlove'
+ post = Post.find :first
+
+ assert_equal [], person.readers
+ assert_nil person.readers.find(:first, :conditions => {
+ :post_id => post.id
+ })
+
+ reader = person.readers.find_or_create :post_id => post.id
+
+ assert_equal 1, person.readers.count
+ assert_equal 1, person.readers.length
+ assert_equal post, person.readers.first.post
+ assert_equal person, person.readers.first.person
+ end
+
+
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 4e4f9c385c..3b89c12a3f 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -712,7 +712,6 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
assert_raise(RuntimeError) { assert !@pirate.save }
- assert before.first.frozen? # the first child was indeed destroyed
assert_equal before, @pirate.reload.send(association_name)
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 5c175de6d4..ba7db838ca 100755..100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -798,25 +798,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_raise(NoMethodError) { t.title2 }
end
- def test_class_name
- assert_equal "Firm", ActiveRecord::Base.class_name("firms")
- assert_equal "Category", ActiveRecord::Base.class_name("categories")
- assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
-
- ActiveRecord::Base.pluralize_table_names = false
- assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
- ActiveRecord::Base.pluralize_table_names = true
-
- ActiveRecord::Base.table_name_prefix = "test_"
- assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
- ActiveRecord::Base.table_name_suffix = "_tests"
- assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
- ActiveRecord::Base.table_name_prefix = ""
- assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
- ActiveRecord::Base.table_name_suffix = ""
- assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
- end
-
def test_null_fields
assert_nil Topic.find(1).parent_id
assert_nil Topic.create("title" => "Hey you").parent_id
@@ -1401,6 +1382,14 @@ class BasicsTest < ActiveRecord::TestCase
assert_not_equal clone.id, dev.id
end
+ def test_clone_does_not_clone_associations
+ author = authors(:david)
+ assert_not_equal [], author.posts
+
+ author_clone = author.clone
+ assert_equal [], author_clone.posts
+ end
+
def test_clone_preserves_subtype
clone = nil
assert_nothing_raised { clone = Company.find(3).clone }
@@ -2076,10 +2065,6 @@ class BasicsTest < ActiveRecord::TestCase
assert !SubStiPost.descends_from_active_record?
end
- def test_base_subclasses_is_public_method
- assert ActiveRecord::Base.public_methods.map(&:to_sym).include?(:subclasses)
- end
-
def test_find_on_abstract_base_class_doesnt_use_type_condition
old_class = LooseDescendant
Object.send :remove_const, :LooseDescendant
@@ -2239,7 +2224,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_inspect_instance
topic = topics(:first)
- assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil>), topic.inspect
+ assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect
end
def test_inspect_new_instance
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 329dd7d761..2c9d23c80f 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -272,7 +272,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_count_with_column_and_options_parameter
- assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50")
+ assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL")
end
def test_count_with_no_parameters_isnt_deprecated
diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb
index 377de168b9..377de168b9 100755..100644
--- a/activerecord/test/cases/counter_cache_test.rb
+++ b/activerecord/test/cases/counter_cache_test.rb
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index c1c8f01e46..8c09fc4d59 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -173,7 +173,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_complex_inheritance
very_special_client = VerySpecialClient.create("name" => "veryspecial")
- assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 1f544b4211..4aeae1fe45 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -1,20 +1,19 @@
require "cases/helper"
require "models/developer"
-require "rails/log_subscriber/test_helper"
-require "active_record/railties/log_subscriber"
+require "active_support/log_subscriber/test_helper"
class LogSubscriberTest < ActiveSupport::TestCase
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
@old_logger = ActiveRecord::Base.logger
super
-
- Rails::LogSubscriber.add(:active_record, ActiveRecord::Railties::LogSubscriber.new)
+ ActiveRecord::LogSubscriber.attach_to(:active_record)
end
def teardown
super
+ ActiveRecord::LogSubscriber.log_subscribers.pop
ActiveRecord::Base.logger = @old_logger
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index e93f22bede..4e8ce1dac1 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -1,3 +1,7 @@
+# This file can be removed once with_exclusive_scope and with_scope are removed.
+# All the tests were already ported to relation_scoping_test.rb when the new
+# relation scoping API was added.
+
require "cases/helper"
require 'models/post'
require 'models/author'
@@ -77,11 +81,13 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
- def test_options_select_replaces_scope_select
- Developer.send(:with_scope, :find => { :select => "id, name" }) do
+ def test_scope_select_concatenates
+ Developer.send(:with_scope, :find => { :select => "name" }) do
developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
assert_equal 80000, developer.salary
- assert !developer.has_attribute?(:name)
+ assert developer.has_attribute?(:id)
+ assert developer.has_attribute?(:name)
+ assert developer.has_attribute?(:salary)
end
end
@@ -281,6 +287,12 @@ class NestedScopingTest < ActiveRecord::TestCase
end
end
+ def test_with_exclusive_scope_with_relation
+ assert_raise(ArgumentError) do
+ Developer.all_johns
+ end
+ end
+
def test_append_conditions
Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do
Developer.send(:with_scope, :find => { :conditions => 'salary = 80000' }) do
@@ -531,205 +543,4 @@ class NestedScopingTest < ActiveRecord::TestCase
assert_equal 1, scoped_authors.size
assert_equal authors(:david).attributes, scoped_authors.first.attributes
end
-end
-
-class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
-
- def test_forwarding_to_scoped
- assert_equal 4, Comment.search_by_type('Comment').size
- assert_equal 2, @welcome.comments.search_by_type('Comment').size
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Comment.find_all_by_type('Comment').size
- assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
- end
-
- def test_nested_scope
- Comment.send(:with_scope, :find => { :conditions => '1=1' }) do
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-end
-
-class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
- fixtures :posts, :categories, :categories_posts
-
- def setup
- @welcome = Post.find(1)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a category...', Category.what_are_you
- assert_equal 'a category...', @welcome.categories.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Category.find_all_by_type('SpecialCategory').size
- assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
- assert_equal 2, @welcome.categories.find_all_by_type('Category').size
- end
-
- def test_nested_scope
- Category.send(:with_scope, :find => { :conditions => '1=1' }) do
- assert_equal 'a comment...', @welcome.comments.what_are_you
- end
- end
-end
-
-class DefaultScopingTest < ActiveRecord::TestCase
- fixtures :developers, :posts
-
- def test_default_scope
- expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_default_scope_with_conditions_string
- assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
- assert_equal nil, DeveloperCalledDavid.create!.name
- end
-
- def test_default_scope_with_conditions_hash
- assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
- assert_equal 'Jamis', DeveloperCalledJamis.create!.name
- end
-
- def test_default_scoping_with_threads
- 2.times do
- Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
- end
- end
-
- def test_default_scoping_with_inheritance
- # Inherit a class having a default scope and define a new default scope
- klass = Class.new(DeveloperOrderedBySalary)
- klass.send :default_scope, :limit => 1
-
- # Scopes added on children should append to parent scope
- assert_equal 1, klass.scoped.limit_value
- assert_equal ['salary DESC'], klass.scoped.order_values
-
- # Parent should still have the original scope
- assert_nil DeveloperOrderedBySalary.scoped.limit_value
- assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
- end
-
- def test_default_scope_called_twice_merges_conditions
- Developer.destroy_all
- Developer.create!(:name => "David", :salary => 80000)
- Developer.create!(:name => "David", :salary => 100000)
- Developer.create!(:name => "Brian", :salary => 100000)
-
- klass = Class.new(Developer)
- klass.__send__ :default_scope, :conditions => { :name => "David" }
- klass.__send__ :default_scope, :conditions => { :salary => 100000 }
- assert_equal 1, klass.count
- assert_equal "David", klass.first.name
- assert_equal 100000, klass.first.salary
- end
- def test_method_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_nested_scope
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
- def test_named_scope_overwrites_default
- expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
- assert_equal expected, received
- end
-
- def test_nested_exclusive_scope
- expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
- DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
- end
- assert_equal expected, received
- end
-
- def test_overwriting_default_scope
- expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
- received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
- assert_equal expected, received
- end
-
- def test_default_scope_using_relation
- posts = PostWithComment.scoped
- assert_equal 2, posts.count
- assert_equal posts(:thinking), posts.first
- end
-
- def test_create_attribute_overwrites_default_scoping
- assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
- assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
- end
-
- def test_create_attribute_overwrites_default_values
- assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
- assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
- end
-end
-
-=begin
-# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
-
-class BelongsToScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @greetings = Comment.find(1)
- end
-
- def test_forwarding_of_static_method
- assert_equal 'a post...', Post.what_are_you
- assert_equal 'a post...', @greetings.post.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 4, Post.find_all_by_type('Post').size
- assert_equal 1, @greetings.post.find_all_by_type('Post').size
- end
-
-end
-
-class HasOneScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
-
- def setup
- @sti_comments = Post.find(4)
- end
-
- def test_forwarding_of_static_methods
- assert_equal 'a comment...', Comment.what_are_you
- assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
- end
-
- def test_forwarding_to_dynamic_finders
- assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
- assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
- assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
- end
-
-end
-
-=end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 6fe3b01281..4ce9bdb46d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -128,9 +128,9 @@ if ActiveRecord::Base.connection.supports_migrations?
good_index_name = 'x' * Person.connection.index_name_length
too_long_index_name = good_index_name + 'x'
assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => too_long_index_name) }
- assert !Person.connection.index_exists?("people", too_long_index_name, false)
+ assert !Person.connection.index_name_exists?("people", too_long_index_name, false)
assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) }
- assert Person.connection.index_exists?("people", good_index_name, false)
+ assert Person.connection.index_name_exists?("people", good_index_name, false)
end
def test_remove_nonexistent_index
@@ -146,8 +146,8 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.add_index('people', [:first_name], :name => 'old_idx')
assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') }
# if the adapter doesn't support the indexes call, pick defaults that let the test pass
- assert !Person.connection.index_exists?('people', 'old_idx', false)
- assert Person.connection.index_exists?('people', 'new_idx', true)
+ assert !Person.connection.index_name_exists?('people', 'old_idx', false)
+ assert Person.connection.index_name_exists?('people', 'new_idx', true)
end
end
@@ -158,6 +158,53 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ def test_index_exists
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :string, :limit => 100
+ end
+ Person.connection.add_index :testings, :foo
+
+ assert Person.connection.index_exists?(:testings, :foo)
+ assert !Person.connection.index_exists?(:testings, :bar)
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_index_exists_on_multiple_columns
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :string, :limit => 100
+ end
+ Person.connection.add_index :testings, [:foo, :bar]
+
+ assert Person.connection.index_exists?(:testings, [:foo, :bar])
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_unique_index_exists
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ end
+ Person.connection.add_index :testings, :foo, :unique => true
+
+ assert Person.connection.index_exists?(:testings, :foo, :unique => true)
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_named_index_exists
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ end
+ Person.connection.add_index :testings, :foo, :name => "custom_index_name"
+
+ assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name")
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
def testing_table_with_only_foo_attribute
Person.connection.create_table :testings, :id => false do |t|
t.column :foo, :string
@@ -748,6 +795,10 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Base.connection.drop_table(:hats)
end
+ def test_remove_column_no_second_parameter_raises_exception
+ assert_raise(ArgumentError) { Person.connection.remove_column("funny") }
+ end
+
def test_change_type_of_not_null_column
assert_nothing_raised do
Topic.connection.change_column "topics", "written_on", :datetime, :null => false
@@ -970,6 +1021,45 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_nil Person.new.first_name
end
+ def test_column_exists
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert Person.connection.column_exists?(:testings, :foo)
+ assert !Person.connection.column_exists?(:testings, :bar)
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_column_exists_with_type
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert Person.connection.column_exists?(:testings, :foo, :string)
+ assert !Person.connection.column_exists?(:testings, :foo, :integer)
+ assert Person.connection.column_exists?(:testings, :bar, :decimal)
+ assert !Person.connection.column_exists?(:testings, :bar, :integer)
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_column_exists_with_definition
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 100
+ t.column :bar, :decimal, :precision => 8, :scale => 2
+ end
+
+ assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100)
+ assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50)
+ assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
+ assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
def test_add_table
assert !Reminder.table_exists?
@@ -1531,6 +1621,23 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ class ReservedWordsMigrationTest < ActiveRecord::TestCase
+ def test_drop_index_from_table_named_values
+ connection = Person.connection
+ connection.create_table :values, :force => true do |t|
+ t.integer :value
+ end
+
+ assert_nothing_raised do
+ connection.add_index :values, :value
+ connection.remove_index :values, :column => :value
+ end
+
+ connection.drop_table :values rescue nil
+ end
+ end
+
+
class ChangeTableMigrationsTest < ActiveRecord::TestCase
def setup
@connection = Person.connection
@@ -1680,6 +1787,20 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ def test_index_exists
+ with_change_table do |t|
+ @connection.expects(:index_exists?).with(:delete_me, :bar, {})
+ t.index_exists?(:bar)
+ end
+ end
+
+ def test_index_exists_with_options
+ with_change_table do |t|
+ @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
+ t.index_exists?(:bar, :unique => true)
+ end
+ end
+
def test_change_changes_column
with_change_table do |t|
@connection.expects(:change_column).with(:delete_me, :bar, :string, {})
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index 33ffb041c1..dc85b395d3 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -54,8 +54,8 @@ class NamedScopeTest < ActiveRecord::TestCase
end
def test_respond_to_respects_include_private_parameter
- assert !Topic.approved.respond_to?(:with_create_scope)
- assert Topic.approved.respond_to?(:with_create_scope, true)
+ assert !Topic.approved.respond_to?(:tables_in_string)
+ assert Topic.approved.respond_to?(:tables_in_string, true)
end
def test_subclasses_inherit_scopes
@@ -450,6 +450,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert before.object_id != post.comments.containing_the_letter_e.object_id, "AssociationCollection##{method} should reset the named scopes cache"
end
end
+
+ def test_named_scoped_are_lazy_loaded_if_table_still_does_not_exist
+ assert_nothing_raised do
+ require "models/without_table"
+ end
+ end
end
class DynamicScopeMatchTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 685b11cb03..3c797076e0 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -176,12 +176,6 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name
end
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
- @pirate.ship_attributes = { :id => 1234567890 }
- end
- end
-
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@pirate.reload.ship_attributes = { 'id' => @ship.id, 'name' => 'Davy Jones Gold Dagger' }
@@ -331,11 +325,14 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
assert_equal 'Arr', @ship.pirate.catchphrase
end
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do
- @ship.pirate_attributes = { :id => 1234567890 }
- end
- end
+ def test_should_associate_with_record_if_parent_record_is_not_saved
+ @ship.destroy
+ @pirate = Pirate.create(:catchphrase => 'Arr')
+ @ship = Ship.new(:name => 'Nights Dirty Lightning', :pirate_attributes => { :id => @pirate.id, :catchphrase => @pirate.catchphrase})
+
+ assert_equal @ship.name, 'Nights Dirty Lightning'
+ assert_equal @pirate, @ship.pirate
+ end
def test_should_take_a_hash_with_string_keys_and_update_the_associated_model
@ship.reload.pirate_attributes = { 'id' => @pirate.id, 'catchphrase' => 'Arr' }
@@ -444,6 +441,11 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.reload.name, @child_2.reload.name]
end
+ def test_should_assign_existing_children_if_parent_is_new
+ @pirate = Pirate.new({:catchphrase => "Don' botharr talkin' like one, savvy?"}.merge(@alternate_params))
+ assert_equal ['Grace OMalley', 'Privateers Greed'], [@pirate.send(@association_name)[0].name, @pirate.send(@association_name)[1].name]
+ end
+
def test_should_also_work_with_a_HashWithIndifferentAccess
@pirate.send(association_setter, HashWithIndifferentAccess.new('foo' => HashWithIndifferentAccess.new(:id => @child_1.id, :name => 'Grace OMalley')))
@pirate.save
@@ -466,6 +468,27 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal 'Grace OMalley', @child_1.reload.name
end
+ def test_should_not_overwrite_unsaved_updates_when_loading_association
+ @pirate.reload
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
+ assert_equal 'Grace OMalley', @pirate.send(@association_name).send(:load_target).find { |r| r.id == @child_1.id }.name
+ end
+
+ def test_should_preserve_order_when_not_overwriting_unsaved_updates
+ @pirate.reload
+ @pirate.send(association_setter, [{ :id => @child_1.id, :name => 'Grace OMalley' }])
+ assert_equal @child_1.id, @pirate.send(@association_name).send(:load_target).first.id
+ end
+
+ 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')
+ @pirate.send(@association_name) << record
+ record.save!
+ @pirate.send(@association_name).last.update_attributes!(:name => 'Polly')
+ assert_equal 'Polly', @pirate.send(@association_name).send(:load_target).last.name
+ end
+
def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models
@child_1.stubs(:id).returns('ABC1X')
@child_2.stubs(:id).returns('ABC2X')
@@ -480,8 +503,8 @@ module NestedAttributesOnACollectionAssociationTests
assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name]
end
- def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
- assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do
+ def test_should_not_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record
+ assert_nothing_raised ActiveRecord::RecordNotFound do
@pirate.attributes = { association_getter => [{ :id => 1234567890 }] }
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 67818622d7..03c4fc4e80 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -24,25 +24,25 @@ class ReflectionTest < ActiveRecord::TestCase
def test_read_attribute_names
assert_equal(
- %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id parent_title type ).sort,
+ %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type ).sort,
@first.attribute_names
)
end
def test_columns
- assert_equal 13, Topic.columns.length
+ assert_equal 14, Topic.columns.length
end
def test_columns_are_returned_in_the_order_they_were_declared
column_names = Topic.columns.map { |column| column.name }
- assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type), column_names
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group), column_names
end
def test_content_columns
content_columns = Topic.content_columns
content_column_names = content_columns.map {|column| column.name}
- assert_equal 9, content_columns.length
- assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved parent_title).sort, content_column_names.sort
+ assert_equal 10, content_columns.length
+ assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title).sort, content_column_names.sort
end
def test_column_string_type_and_limit
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
new file mode 100644
index 0000000000..41dcdbcd37
--- /dev/null
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -0,0 +1,396 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/category'
+
+class RelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
+
+ def test_scoped_find
+ Developer.where("name = 'David'").scoping do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ developer = Developer.find(10)
+ Developer.where("salary = 100000").scoping do
+ assert_equal developer, Developer.order("name").first
+ end
+ end
+
+ def test_scoped_find_last
+ highest_salary = Developer.order("salary DESC").first
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ end
+ end
+
+ def test_scoped_find_last_preserves_scope
+ lowest_salary = Developer.first :order => "salary ASC"
+ highest_salary = Developer.first :order => "salary DESC"
+
+ Developer.order("salary").scoping do
+ assert_equal highest_salary, Developer.last
+ assert_equal lowest_salary, Developer.first
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.where("salary = 9000").scoping do
+ assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.where("name = 'David'").scoping do
+ assert_equal [developers(:david)], Developer.all
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.select("id, name").scoping do
+ developer = Developer.where("name = 'David'").first
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scope_select_concatenates
+ Developer.select("id, name").scoping do
+ developer = Developer.select('id, salary').where("name = 'David'").first
+ assert_equal 80000, developer.salary
+ assert developer.has_attribute?(:id)
+ assert developer.has_attribute?(:name)
+ assert developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_scoped_count
+ Developer.where("name = 'David'").scoping do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.where('salary = 100000').scoping do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.includes(:projects).scoping do
+ Developer.where('projects.id = 2').all
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ end
+
+ def test_scoped_find_joins
+ scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
+ Developer.where('developers_projects.project_id = 2').all
+ end
+
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
+ def test_scoped_create_with_where
+ new_comment = VerySpecialComment.where(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with
+ new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_scoped_create_with_create_with_has_higher_priority
+ new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
+ VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert_equal 1, new_comment.post_id
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ scoped_methods = Developer.send(:current_scoped_methods)
+
+ begin
+ Developer.where("name = 'Jamis'").scoping do
+ raise "an exception"
+ end
+ rescue
+ end
+
+ assert_equal scoped_methods, Developer.send(:current_scoped_methods)
+ end
+end
+
+class NestedRelationScopingTest < ActiveRecord::TestCase
+ fixtures :authors, :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ Developer.where('salary = 80000').scoping do
+ Developer.limit(10).scoping do
+ devs = Developer.scoped
+ assert_equal '(salary = 80000)', devs.arel.send(:where_clauses).join(' AND ')
+ assert_equal 10, devs.taken
+ end
+ end
+ end
+
+ def test_merge_inner_scope_has_priority
+ Developer.limit(5).scoping do
+ Developer.limit(10).scoping do
+ assert_equal 10, Developer.count
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.where(:name => 'David').scoping do
+ Developer.unscoped do
+ assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
+ end
+
+ assert_equal 'David', Developer.first[:name]
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.where("name = 'Jamis'").scoping do
+ assert_equal 'Jamis', Developer.first.name
+
+ Developer.unscoped.where("name = 'David'") do
+ assert_equal 'David', Developer.first.name
+
+ Developer.unscoped.where("name = 'Maiha'") do
+ assert_equal nil, Developer.first
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'David', Developer.first.name
+ end
+
+ # ensure that scoping is restored
+ assert_equal 'Jamis', Developer.first.name
+ end
+ end
+
+ def test_nested_scoped_create
+ comment = Comment.create_with(:post_id => 1).scoping do
+ Comment.create_with(:post_id => 2).scoping do
+ Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
+ end
+ end
+
+ assert_equal 2, comment.post_id
+ end
+
+ 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 Comment.new.body.blank?
+ Comment.create :body => "Hey guys"
+ end
+ end
+
+ assert_equal 1, comment.post_id
+ assert_equal 'Hey guys', comment.body
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_nested_scope_finder
+ Comment.where('1=0').scoping do
+ assert_equal 0, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ Comment.where('1=1').scoping do
+ assert_equal 2, @welcome.comments.count
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+end
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, @welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_nested_scope_finder
+ Category.where('1=0').scoping do
+ assert_equal 0, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ Category.where('1=1').scoping do
+ assert_equal 2, @welcome.categories.count
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+ end
+end
+
+class DefaultScopingTest < ActiveRecord::TestCase
+ fixtures :developers, :posts
+
+ def test_default_scope
+ expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_default_scope_is_unscoped_on_find
+ assert_equal 1, DeveloperCalledDavid.count
+ assert_equal 11, DeveloperCalledDavid.unscoped.count
+ end
+
+ def test_default_scope_is_unscoped_on_create
+ assert_nil DeveloperCalledJamis.unscoped.create!.name
+ end
+
+ def test_default_scope_with_conditions_string
+ assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
+ assert_equal nil, DeveloperCalledDavid.create!.name
+ end
+
+ def test_default_scope_with_conditions_hash
+ assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
+ assert_equal 'Jamis', DeveloperCalledJamis.create!.name
+ end
+
+ def test_default_scoping_with_threads
+ 2.times do
+ Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
+ end
+ end
+
+ def test_default_scoping_with_inheritance
+ # Inherit a class having a default scope and define a new default scope
+ klass = Class.new(DeveloperOrderedBySalary)
+ klass.send :default_scope, :limit => 1
+
+ # Scopes added on children should append to parent scope
+ assert_equal 1, klass.scoped.limit_value
+ assert_equal ['salary DESC'], klass.scoped.order_values
+
+ # Parent should still have the original scope
+ assert_nil DeveloperOrderedBySalary.scoped.limit_value
+ assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
+ end
+
+ def test_default_scope_called_twice_merges_conditions
+ Developer.destroy_all
+ Developer.create!(:name => "David", :salary => 80000)
+ Developer.create!(:name => "David", :salary => 100000)
+ Developer.create!(:name => "Brian", :salary => 100000)
+
+ klass = Class.new(Developer)
+ klass.__send__ :default_scope, :conditions => { :name => "David" }
+ klass.__send__ :default_scope, :conditions => { :salary => 100000 }
+ assert_equal 1, klass.count
+ assert_equal "David", klass.first.name
+ assert_equal 100000, klass.first.salary
+ end
+ def test_method_scope
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_nested_scope
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
+ DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ end
+ assert_equal expected, received
+ end
+
+ def test_named_scope_overwrites_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
+ def test_nested_exclusive_scope
+ expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
+ DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
+ end
+ assert_equal expected, received
+ end
+
+ def test_overwriting_default_scope
+ expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
+ assert_equal expected, received
+ end
+
+ def test_default_scope_using_relation
+ posts = PostWithComment.scoped
+ assert_equal 2, posts.count
+ assert_equal posts(:thinking), posts.first
+ end
+
+ def test_create_attribute_overwrites_default_scoping
+ assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
+ assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
+ end
+
+ def test_create_attribute_overwrites_default_values
+ assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
+ assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 43519db976..ffde8daa07 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -16,12 +16,34 @@ class RelationTest < ActiveRecord::TestCase
fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments,
:taggings
+ def test_apply_relation_as_where_id
+ posts = Post.arel_table
+ post_authors = posts.where(posts[:author_id].eq(1)).project(posts[:id])
+ assert_equal 5, post_authors.to_a.size
+ assert_equal 5, Post.where(:id => post_authors).size
+ end
+
def test_scoped
topics = Topic.scoped
assert_kind_of ActiveRecord::Relation, topics
assert_equal 4, topics.size
end
+ def test_to_json
+ assert_nothing_raised { Bird.scoped.to_json }
+ assert_nothing_raised { Bird.scoped.all.to_json }
+ end
+
+ def test_to_yaml
+ assert_nothing_raised { Bird.scoped.to_yaml }
+ assert_nothing_raised { Bird.scoped.all.to_yaml }
+ end
+
+ def test_to_xml
+ assert_nothing_raised { Bird.scoped.to_xml }
+ assert_nothing_raised { Bird.scoped.all.to_xml }
+ end
+
def test_scoped_all
topics = Topic.scoped.all
assert_kind_of Array, topics
@@ -88,6 +110,18 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:first).title, topics.first.title
end
+ def test_finding_with_order_concatenated
+ topics = Topic.order('author_name').order('title')
+ assert_equal 4, topics.to_a.size
+ assert_equal topics(:fourth).title, topics.first.title
+ end
+
+ def test_finding_with_reorder
+ topics = Topic.order('author_name').order('title').reorder('id')
+ assert_equal 4, topics.to_a.size
+ assert_equal topics(:first).title, topics.first.title
+ end
+
def test_finding_with_order_and_take
entrants = Entrant.order("id ASC").limit(2).to_a
@@ -461,7 +495,7 @@ class RelationTest < ActiveRecord::TestCase
posts = Post.scoped
assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq
- assert_equal 0, posts.where('id is not null').select('comments_count').count
+ assert_equal 7, posts.where('id is not null').select('comments_count').count
assert_equal 7, posts.select('comments_count').count('id')
assert_equal 0, posts.select('comments_count').count
@@ -469,6 +503,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 0, posts.count('comments_count')
end
+ def test_multiple_selects
+ post = Post.scoped.select('comments_count').select('title').order("id ASC").first
+ assert_equal "Welcome to the weblog", post.title
+ assert_equal 2, post.comments_count
+ end
+
def test_size
posts = Post.scoped
@@ -503,13 +543,13 @@ class RelationTest < ActiveRecord::TestCase
def test_many
posts = Post.scoped
-
+
assert_queries(2) do
assert posts.many? # Uses COUNT()
assert posts.many? {|p| p.id > 0 }
assert ! posts.many? {|p| p.id < 2 }
end
-
+
assert posts.loaded?
end
@@ -578,7 +618,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_anonymous_extension
- relation = Post.where(:author_id => 1).order('id ASC') do
+ relation = Post.where(:author_id => 1).order('id ASC').extending do
def author
'lifo'
end
@@ -593,4 +633,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal "lifo", relation.author
assert_equal "lifo", relation.limit(1).author
end
+
+ def test_order_by_relation_attribute
+ assert_equal Post.order(Post.arel_table[:title]).all, Post.order("title").all
+ end
+
+ def test_relations_limit_with_conditions_or_limit
+ assert_equal Post.limit(2).size, Post.limit(2).all.size
+ end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index ebc16653cb..df123c9de8 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -221,20 +221,28 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal 2, @first.rollbacks
end
- def test_after_transaction_callbacks_should_not_raise_errors
+ def test_after_transaction_callbacks_should_prevent_callbacks_from_being_called
def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
def @first.last_after_transaction_error; @last_transaction_error; end
@first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
@first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
+ @second.after_commit_block{|r| r.history << :after_commit}
+ @second.after_rollback_block{|r| r.history << :after_rollback}
- @first.save!
+ Topic.transaction do
+ @first.save!
+ @second.save!
+ end
assert_equal :commit, @first.last_after_transaction_error
+ assert_equal [:after_commit], @second.history
+ @second.history.clear
Topic.transaction do
@first.save!
+ @second.save!
raise ActiveRecord::Rollback
end
-
assert_equal :rollback, @first.last_after_transaction_error
+ assert_equal [:after_rollback], @second.history
end
end
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 8ee2a5868c..454e42ed37 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -9,22 +9,22 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
I18n.backend = I18n::Backend::Simple.new
end
- # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value)
+ # validates_associated: generate_message(attr_name, :invalid, :message => custom_message, :value => value)
def test_generate_message_invalid_with_default_message
- assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title')
+ assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :value => 'title')
end
def test_generate_message_invalid_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title')
end
- # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message])
+ # validates_uniqueness_of: generate_message(attr_name, :taken, :message => custom_message)
def test_generate_message_taken_with_default_message
- assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title')
+ assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :value => 'title')
end
def test_generate_message_taken_with_custom_message
- assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message %{value}', :value => 'title')
+ assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :message => 'custom message %{value}', :value => 'title')
end
# ActiveRecord#RecordInvalid exception
diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb
index 38fa2b821d..15b97c02c8 100644
--- a/activerecord/test/cases/validations/i18n_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_validation_test.rb
@@ -31,34 +31,40 @@ class I18nValidationTest < ActiveRecord::TestCase
end
end
- # validates_uniqueness_of w/ mocha
+ # A set of common cases for ActiveModel::Validations message generation that
+ # are used to generate tests to keep things DRY
+ #
+ COMMON_CASES = [
+ # [ case, validation_options, generate_message_options]
+ [ "given no options", {}, {}],
+ [ "given custom message", {:message => "custom"}, {:message => "custom"}],
+ [ "given if condition", {:if => lambda { true }}, {}],
+ [ "given unless condition", {:unless => lambda { false }}, {}],
+ [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }]
+ # TODO Add :on case, but below doesn't work, because then the validation isn't run for some reason
+ # even when using .save instead .valid?
+ # [ "given on condition", {:on => :save}, {}]
+ ]
- def test_validates_uniqueness_of_generates_message
- Topic.validates_uniqueness_of :title
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'})
- @topic.valid?
- end
+ # validates_uniqueness_of w/ mocha
- def test_validates_uniqueness_of_generates_message_with_custom_default_message
- Topic.validates_uniqueness_of :title, :message => 'custom'
- @topic.title = unique_topic.title
- @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'})
- @topic.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_uniqueness_of on generated message #{name}" do
+ Topic.validates_uniqueness_of :title, validation_options
+ @topic.title = unique_topic.title
+ @topic.errors.expects(:generate_message).with(:title, :taken, generate_message_options.merge(:value => 'unique!'))
+ @topic.valid?
+ end
end
# validates_associated w/ mocha
- def test_validates_associated_generates_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
- end
-
- def test_validates_associated_generates_message_with_custom_default_message
- Topic.validates_associated :replies
- replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil})
- replied_topic.valid?
+ COMMON_CASES.each do |name, validation_options, generate_message_options|
+ test "validates_associated on generated message #{name}" do
+ Topic.validates_associated :replies, validation_options
+ replied_topic.errors.expects(:generate_message).with(:replies, :invalid, generate_message_options.merge(:value => replied_topic.replies))
+ replied_topic.save
+ end
end
# validates_associated w/o mocha
diff --git a/activerecord/test/fixtures/topics.yml b/activerecord/test/fixtures/topics.yml
index 1769152445..93f48aedc4 100644
--- a/activerecord/test/fixtures/topics.yml
+++ b/activerecord/test/fixtures/topics.yml
@@ -24,7 +24,7 @@ second:
third:
id: 3
title: The Third Topic of the day
- author_name: Nick
+ author_name: Carl
written_on: 2005-07-15t15:28:00.0099+01:00
content: I'm a troll
approved: true
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index e35de3b9b0..de68fd7f24 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -55,6 +55,12 @@ class Developer < ActiveRecord::Base
def log=(message)
audit_logs.build :message => message
end
+
+ def self.all_johns
+ self.with_exclusive_scope :find => where(:name => 'John') do
+ self.all
+ end
+ end
end
class AuditLog < ActiveRecord::Base
@@ -103,4 +109,4 @@ end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index f1dbe32c6e..d89c8cf381 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -1,7 +1,7 @@
class Pirate < ActiveRecord::Base
belongs_to :parrot, :validate => true
belongs_to :non_validated_parrot, :class_name => 'Parrot'
- has_and_belongs_to_many :parrots, :validate => true
+ has_and_belongs_to_many :parrots, :validate => true, :order => 'parrots.id ASC'
has_and_belongs_to_many :non_validated_parrots, :class_name => 'Parrot'
has_and_belongs_to_many :parrots_with_method_callbacks, :class_name => "Parrot",
:before_add => :log_before_add,
@@ -21,7 +21,7 @@ class Pirate < ActiveRecord::Base
has_one :ship
has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship'
- has_many :birds
+ has_many :birds, :order => 'birds.id ASC'
has_many :birds_with_method_callbacks, :class_name => "Bird",
:before_add => :log_before_add,
:after_add => :log_after_add,
diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb
new file mode 100644
index 0000000000..87f80911e1
--- /dev/null
+++ b/activerecord/test/models/without_table.rb
@@ -0,0 +1,3 @@
+class WithoutTable < ActiveRecord::Base
+ default_scope where(:published => true)
+end \ No newline at end of file
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index f5fba2f87d..b212e7cff2 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -485,6 +485,7 @@ ActiveRecord::Schema.define do
t.integer :parent_id
t.string :parent_title
t.string :type
+ t.string :group
end
create_table :taggings, :force => true do |t|
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index c1ec8559f5..6c494a8bcc 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -17,6 +17,7 @@ require 'active_resource/exceptions'
require 'active_resource/connection'
require 'active_resource/formats'
require 'active_resource/schema'
+require 'active_resource/log_subscriber'
module ActiveResource
# ActiveResource::Base is the main class for mapping RESTful resources as models in a Rails application.
@@ -763,7 +764,7 @@ module ActiveResource
# With any other scope, find returns nil when no data is returned.
#
# Person.find(1)
- # # => raises ResourcenotFound
+ # # => raises ResourceNotFound
#
# Person.find(:all)
# Person.find(:first)
@@ -941,7 +942,7 @@ module ActiveResource
end
# This is a list of known attributes for this resource. Either
- # gathered fromthe provided <tt>schema</tt>, or from the attributes
+ # gathered from the provided <tt>schema</tt>, or from the attributes
# set on this instance after it has been fetched from the remote system.
def known_attributes
self.class.known_attributes + self.attributes.keys.map(&:to_s)
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index 1ed3804017..f192c53b4f 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -148,16 +148,28 @@ module ActiveResource
attr_accessor :path, :method, :body, :headers
def initialize(method, path, body = nil, headers = {})
- @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml')
+ @method, @path, @body, @headers = method, path, body, headers
end
def ==(req)
- path == req.path && method == req.method && headers == req.headers
+ path == req.path && method == req.method && headers_match?(req)
end
def to_s
"<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
end
+
+ private
+
+ def headers_match?(req)
+ # Ignore format header on equality if it's not defined
+ format_header = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method]
+ if headers[format_header].present? || req.headers[format_header].blank?
+ headers == req.headers
+ else
+ headers.dup.merge(format_header => req.headers[format_header]) == req.headers
+ end
+ end
end
class Response
diff --git a/activeresource/lib/active_resource/log_subscriber.rb b/activeresource/lib/active_resource/log_subscriber.rb
new file mode 100644
index 0000000000..9e52baf36d
--- /dev/null
+++ b/activeresource/lib/active_resource/log_subscriber.rb
@@ -0,0 +1,15 @@
+module ActiveResource
+ class LogSubscriber < ActiveSupport::LogSubscriber
+ def request(event)
+ result = event.payload[:result]
+ info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
+ info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
+ end
+
+ def logger
+ ActiveResource::Base.logger
+ end
+ end
+end
+
+ActiveResource::LogSubscriber.attach_to :active_resource \ No newline at end of file
diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb
index aa878c7212..60f6f88311 100644
--- a/activeresource/lib/active_resource/railtie.rb
+++ b/activeresource/lib/active_resource/railtie.rb
@@ -5,9 +5,6 @@ module ActiveResource
class Railtie < Rails::Railtie
config.active_resource = ActiveSupport::OrderedOptions.new
- require "active_resource/railties/log_subscriber"
- log_subscriber :active_resource, ActiveResource::Railties::LogSubscriber.new
-
initializer "active_resource.set_configs" do |app|
app.config.active_resource.each do |k,v|
ActiveResource::Base.send "#{k}=", v
diff --git a/activeresource/lib/active_resource/railties/log_subscriber.rb b/activeresource/lib/active_resource/railties/log_subscriber.rb
deleted file mode 100644
index 86806a93d0..0000000000
--- a/activeresource/lib/active_resource/railties/log_subscriber.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module ActiveResource
- module Railties
- class LogSubscriber < Rails::LogSubscriber
- def request(event)
- result = event.payload[:result]
- info "#{event.payload[:method].to_s.upcase} #{event.payload[:request_uri]}"
- info "--> %d %s %d (%.1fms)" % [result.code, result.message, result.body.to_s.length, event.duration]
- end
-
- def logger
- ActiveResource::Base.logger
- end
- end
- end
-end \ No newline at end of file
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index a19e0d0ac9..026d81e44a 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -27,7 +27,7 @@ module ActiveResource
# Grabs errors from a json response.
def from_json(json, save_cache = false)
- array = ActiveSupport::JSON.decode(json)['errors'] rescue []
+ array = Array.wrap(ActiveSupport::JSON.decode(json)['errors']) rescue []
from_array array, save_cache
end
diff --git a/activeresource/test/cases/base_errors_test.rb b/activeresource/test/cases/base_errors_test.rb
index b4fd75fba3..5063916d10 100644
--- a/activeresource/test/cases/base_errors_test.rb
+++ b/activeresource/test/cases/base_errors_test.rb
@@ -17,7 +17,7 @@ class BaseErrorsTest < Test::Unit::TestCase
end
end
- def test_should_parse_xml_errors
+ def test_should_parse_json_and_xml_errors
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
assert_kind_of ActiveResource::Errors, @person.errors
@@ -26,6 +26,17 @@ class BaseErrorsTest < Test::Unit::TestCase
end
end
+ def test_should_parse_json_errors_when_no_errors_key
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/people.json", {}, '{}', 422, {'Content-Type' => 'application/json; charset=utf-8'}
+ end
+
+ invalid_user_using_format(:json) do
+ assert_kind_of ActiveResource::Errors, @person.errors
+ assert_equal 0, @person.errors.size
+ end
+ end
+
def test_should_parse_errors_to_individual_attributes
[ :json, :xml ].each do |format|
invalid_user_using_format(format) do
diff --git a/activeresource/test/cases/http_mock_test.rb b/activeresource/test/cases/http_mock_test.rb
new file mode 100644
index 0000000000..5e032d03f1
--- /dev/null
+++ b/activeresource/test/cases/http_mock_test.rb
@@ -0,0 +1,71 @@
+require 'abstract_unit'
+
+class HttpMockTest < ActiveSupport::TestCase
+ setup do
+ @http = ActiveResource::HttpMock.new("http://example.com")
+ end
+
+ FORMAT_HEADER = ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES
+
+ [:post, :put, :get, :delete, :head].each do |method|
+ test "responds to simple #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/xml"}, "Response")
+ end
+
+ assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
+ end
+
+ test "adds format header by default to #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {}, "Response")
+ end
+
+ assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
+ end
+
+ test "respond only when headers match header by default to #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {"X-Header" => "X"}, "Response")
+ end
+
+ assert_equal "Response", request(method, "/people/1", "X-Header" => "X").body
+ assert_raise(ActiveResource::InvalidRequestError) { request(method, "/people/1") }
+ end
+
+ test "does not overwrite format header to #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/json"}, "Response")
+ end
+
+ assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
+ end
+
+ test "ignores format header when there is only one response to same url in a #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {}, "Response")
+ end
+
+ assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
+ assert_equal "Response", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
+ end
+
+ test "responds correctly when format header is given to #{method} request" do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/xml"}, "XML")
+ mock.send(method, "/people/1", {FORMAT_HEADER[method] => "application/json"}, "Json")
+ end
+
+ assert_equal "XML", request(method, "/people/1", FORMAT_HEADER[method] => "application/xml").body
+ assert_equal "Json", request(method, "/people/1", FORMAT_HEADER[method] => "application/json").body
+ end
+ end
+
+ def request(method, path, headers = {}, body = nil)
+ if [:put, :post].include? method
+ @http.send(method, path, body, headers)
+ else
+ @http.send(method, path, headers)
+ end
+ end
+end
diff --git a/activeresource/test/cases/log_subscriber_test.rb b/activeresource/test/cases/log_subscriber_test.rb
index f0330e8f51..3cd96007db 100644
--- a/activeresource/test/cases/log_subscriber_test.rb
+++ b/activeresource/test/cases/log_subscriber_test.rb
@@ -1,12 +1,11 @@
require "abstract_unit"
require "fixtures/person"
-require "rails/log_subscriber/test_helper"
-require "active_resource/railties/log_subscriber"
+require "active_support/log_subscriber/test_helper"
+require "active_resource/log_subscriber"
require "active_support/core_ext/hash/conversions"
-# TODO: This test should be part of Railties
class LogSubscriberTest < ActiveSupport::TestCase
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@@ -16,7 +15,7 @@ class LogSubscriberTest < ActiveSupport::TestCase
mock.get "/people/1.xml", {}, @matz
end
- Rails::LogSubscriber.add(:active_resource, ActiveResource::Railties::LogSubscriber.new)
+ ActiveResource::LogSubscriber.attach_to :active_resource
end
def set_logger(logger)
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 13ec3c3775..60586cf99a 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,17 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* Deprecation behavior is no longer hardcoded to the name of the environment.
+ Instead, it is set via config.active_support.deprecation and can be one
+ of :log, :stderr or :notify. :notify is a new style that sends the warning
+ via ActiveSupport::Notifications, and is the new default for production
+ [Yehuda Katz]
+
+* Renamed ActiveSupport::Dependecies.load_(once_)paths to autoload_(once_)paths. [fxn]
+
+* Added ActiveSupport::FileUpdateChecker to execute a block only if a set of files changed, used by Router and I18n locale files. [José Valim]
+
+* Added ActiveSupport::DescendantsTracker to track descendants with support to constants reloading. [José Valim]
+
* ActiveSupport::OrderedHash#merge and #merge! accept a block. #4838 [Paul Mucur, fxn]
* Date#since, #ago, #beginning_of_day, #end_of_day, and #xmlschema honor now the user time zone if set. [Geoff Buesing]
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 51edb59c77..51edb59c77 100755..100644
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index e34e46b4cf..ba91e8bba3 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -39,6 +39,11 @@ require "active_support/dependencies/autoload"
module ActiveSupport
extend ActiveSupport::Autoload
+ autoload :DescendantsTracker
+ autoload :FileUpdateChecker
+ autoload :LogSubscriber
+ autoload :Notifications
+
# TODO: Narrow this list down
eager_autoload do
autoload :BacktraceCleaner
@@ -61,7 +66,6 @@ module ActiveSupport
autoload :OptionMerger
autoload :OrderedHash
autoload :OrderedOptions
- autoload :Notifications
autoload :Rescuable
autoload :SecureRandom
autoload :StringInquirer
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb
index f537818300..def8eca89f 100644
--- a/activesupport/lib/active_support/all.rb
+++ b/activesupport/lib/active_support/all.rb
@@ -1,3 +1,4 @@
require 'active_support'
require 'active_support/time'
require 'active_support/core_ext'
+require 'active_support/json'
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 2605a3f2b8..bef9c98ecf 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -22,7 +22,7 @@ module ActiveSupport
EMPTY_OPTIONS = {}.freeze
# These options mean something to all cache implementations. Individual cache
- # implementations may support additional optons.
+ # implementations may support additional options.
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
module Strategy
@@ -267,27 +267,39 @@ module ActiveSupport
# :bar
# end
# cache.fetch("foo") # => "bar"
- def fetch(name, options = nil, &block)
- options = merged_options(options)
- key = namespaced_key(name, options)
- entry = instrument(:read, name, options) { read_entry(key, options) } unless options[:force]
- if entry && entry.expired?
- race_ttl = options[:race_condition_ttl].to_f
- if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
- entry.expires_at = Time.now + race_ttl
- write_entry(key, entry, :expires_in => race_ttl * 2)
- else
- delete_entry(key, options)
+ def fetch(name, options = nil)
+ if block_given?
+ options = merged_options(options)
+ key = namespaced_key(name, options)
+ unless options[:force]
+ entry = instrument(:read, name, options) do |payload|
+ payload[:super_operation] = :fetch if payload
+ read_entry(key, options)
+ end
+ end
+ if entry && entry.expired?
+ race_ttl = options[:race_condition_ttl].to_f
+ if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
+ entry.expires_at = Time.now + race_ttl
+ write_entry(key, entry, :expires_in => race_ttl * 2)
+ else
+ delete_entry(key, options)
+ end
+ entry = nil
end
- entry = nil
- end
- if entry
- entry.value
- elsif block_given?
- result = instrument(:generate, name, options, &block)
- write(name, result, options)
- result
+ if entry
+ instrument(:fetch_hit, name, options) { |payload| }
+ entry.value
+ else
+ result = instrument(:generate, name, options) do |payload|
+ yield
+ end
+ write(name, result, options)
+ result
+ end
+ else
+ read(name, options)
end
end
@@ -299,16 +311,19 @@ module ActiveSupport
def read(name, options = nil)
options = merged_options(options)
key = namespaced_key(name, options)
- instrument(:read, name, options) do
+ instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
if entry
if entry.expired?
delete_entry(key, options)
+ payload[:hit] = false if payload
nil
else
+ payload[:hit] = true if payload
entry.value
end
else
+ payload[:hit] = false if payload
nil
end
end
@@ -345,7 +360,7 @@ module ActiveSupport
# +options+.
def write(name, value, options = nil)
options = merged_options(options)
- instrument(:write, name, options) do
+ instrument(:write, name, options) do |payload|
entry = Entry.new(value, options)
write_entry(namespaced_key(name, options), entry, options)
end
@@ -356,7 +371,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
options = merged_options(options)
- instrument(:delete, name) do
+ instrument(:delete, name) do |payload|
delete_entry(namespaced_key(name, options), options)
end
end
@@ -366,7 +381,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
options = merged_options(options)
- instrument(:exist?, name) do
+ instrument(:exist?, name) do |payload|
entry = read_entry(namespaced_key(name, options), options)
if entry && !entry.expired?
true
@@ -502,9 +517,9 @@ module ActiveSupport
if self.class.instrument
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield }
+ ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
else
- yield
+ yield(nil)
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e3a2688e2f..852defeae8 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -35,7 +35,7 @@ module ActiveSupport
def self.build_mem_cache(*addresses)
addresses = addresses.flatten
options = addresses.extract_options!
- addresses = ["localhost"] if addresses.empty?
+ addresses = ["localhost:11211"] if addresses.empty?
MemCache.new(addresses, options)
end
@@ -159,7 +159,7 @@ module ActiveSupport
private
def escape_key(key)
- key = key.to_s.gsub(ESCAPE_KEY_CHARS){|match| "%#{match[0].to_s(16).upcase}"}
+ key = key.to_s.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"}
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index b1d14a0d8f..f5c2b8af8b 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -10,7 +10,7 @@ module ActiveSupport
# appropriate cache for you.
#
# This cache has a bounded size specified by the :size options to the
- # initializer (default is 32Mb). When the cache exceeds the alotted size,
+ # initializer (default is 32Mb). When the cache exceeds the allotted size,
# a cleanup will occur which tries to prune the cache down to three quarters
# of the maximum size by removing the least recently used entries.
#
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 3ff33eea72..c4e1eb2c04 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -1,6 +1,6 @@
+require 'active_support/descendants_tracker'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/inheritable_attributes'
-require 'active_support/core_ext/class/subclasses'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
@@ -85,6 +85,10 @@ module ActiveSupport
module Callbacks
extend Concern
+ included do
+ extend ActiveSupport::DescendantsTracker
+ end
+
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end
@@ -428,7 +432,7 @@ module ActiveSupport
options = filters.last.is_a?(Hash) ? filters.pop : {}
filters.unshift(block) if block
- ([self] + self.descendents).each do |target|
+ ([self] + self.descendants).each do |target|
chain = target.send("_#{name}_callbacks")
yield chain, type, filters, options
target.__define_runner(name)
@@ -502,7 +506,7 @@ module ActiveSupport
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
- self.descendents.each do |target|
+ self.descendants.each do |target|
chain = target.send("_#{symbol}_callbacks")
callbacks.each { |c| chain.delete(c) }
target.__define_runner(symbol)
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index ef416787a9..4cd9bfadac 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -55,7 +55,7 @@ class Array
# ["6", "7"]
def in_groups(number, fill_with = nil)
# size / number gives minor group size;
- # size % number gives how many objects need extra accomodation;
+ # size % number gives how many objects need extra accommodation;
# each group hold either division or division + 1 items.
division = size / number
modulo = size % number
diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb
index 7a4836cecd..edac7278bc 100644
--- a/activesupport/lib/active_support/core_ext/array/random_access.rb
+++ b/activesupport/lib/active_support/core_ext/array/random_access.rb
@@ -1,5 +1,12 @@
class Array
# Backport of Array#sample based on Marc-Andre Lafortune's http://github.com/marcandre/backports/
+ # Returns a random element or +n+ random elements from the array.
+ # If the array is empty and +n+ is nil, returns <tt>nil</tt>. if +n+ is passed, returns <tt>[]</tt>.
+ #
+ # [1,2,3,4,5,6].sample # => 4
+ # [1,2,3,4,5,6].sample(3) # => [2, 4, 5]
+ # [].sample # => nil
+ # [].sample(3) # => []
def sample(n=nil)
return self[Kernel.rand(size)] if n.nil?
n = n.to_int
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index f2ca9c7cc9..6f308a0d62 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/delegating_attributes'
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index 4be7eaf476..7aff05dcdf 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -128,7 +128,7 @@ class Class # :nodoc:
private
# Prevent this constant from being created multiple times
- EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
+ EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze
def inherited_with_inheritable_attributes(child)
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index bbd8f5aef6..7d58a8b56a 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -11,9 +11,9 @@ class Class #:nodoc:
# Rubinius
if defined?(Class.__subclasses__)
- def descendents
+ def descendants
subclasses = []
- __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents }
+ __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendants }
subclasses
end
else
@@ -21,7 +21,7 @@ class Class #:nodoc:
begin
ObjectSpace.each_object(Class.new) {}
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(class << self; self; end) do |k|
subclasses << k unless k == self
@@ -30,7 +30,7 @@ class Class #:nodoc:
end
# JRuby
rescue StandardError
- def descendents
+ def descendants
subclasses = []
ObjectSpace.each_object(Class) do |k|
subclasses << k if k < self
@@ -48,7 +48,7 @@ class Class #:nodoc:
def self.subclasses_of(*superclasses) #:nodoc:
subclasses = []
superclasses.each do |klass|
- subclasses.concat klass.descendents.select {|k| k.anonymous? || k.reachable?}
+ subclasses.concat klass.descendants.select {|k| k.anonymous? || k.reachable?}
end
subclasses
end
diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb
index c4994ca2ee..d023b4bcff 100644
--- a/activesupport/lib/active_support/core_ext/logger.rb
+++ b/activesupport/lib/active_support/core_ext/logger.rb
@@ -72,44 +72,6 @@ class Logger
@formatter ||= SimpleFormatter.new
end
- unless const_defined? :Formatter
- class Formatter
- Format = "%s, [%s#%d] %5s -- %s: %s\n"
-
- attr_accessor :datetime_format
-
- def initialize
- @datetime_format = nil
- end
-
- def call(severity, time, progname, msg)
- Format % [severity[0..0], format_datetime(time), $$, severity, progname,
- msg2str(msg)]
- end
-
- private
- def format_datetime(time)
- if @datetime_format.nil?
- time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
- else
- time.strftime(@datetime_format)
- end
- end
-
- def msg2str(msg)
- case msg
- when ::String
- msg
- when ::Exception
- "#{ msg.message } (#{ msg.class })\n" <<
- (msg.backtrace || []).join("\n")
- else
- msg.inspect
- end
- end
- end
- end
-
# Simple formatter which only displays the message.
class SimpleFormatter < Logger::Formatter
# This method is invoked when a log event occurs
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 06f077e920..06f077e920 100755..100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
diff --git a/activesupport/lib/active_support/core_ext/range/overlaps.rb b/activesupport/lib/active_support/core_ext/range/overlaps.rb
index 0dec6e0ac4..7df653b53f 100644
--- a/activesupport/lib/active_support/core_ext/range/overlaps.rb
+++ b/activesupport/lib/active_support/core_ext/range/overlaps.rb
@@ -1,5 +1,5 @@
class Range
- # Compare two ranges and see if they overlap eachother
+ # Compare two ranges and see if they overlap each other
# (1..5).overlaps?(4..6) # => true
# (1..5).overlaps?(7..9) # => false
def overlaps?(other)
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index cd7a42ed2b..5b2cb6e331 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -28,6 +28,9 @@ class String
self[0]
end unless method_defined?(:ord)
+ # +getbyte+ backport from Ruby 1.9
+ alias_method :getbyte, :[] unless method_defined?(:getbyte)
+
# Form can be either :utc (default) or :local.
def to_time(form = :utc)
return nil if self.blank?
@@ -47,15 +50,4 @@ class String
d[5] += d.pop
::DateTime.civil(*d)
end
-
- # +constantize+ tries to find a declared constant with the name specified
- # in the string. It raises a NameError when the name is not in CamelCase
- # or is not initialized.
- #
- # Examples
- # "Module".constantize # => Module
- # "Class".constantize # => Class
- def constantize
- ActiveSupport::Inflector.constantize(self)
- end
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index ef479d559e..66c4034781 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -28,6 +28,17 @@ class String
ActiveSupport::Inflector.singularize(self)
end
+ # +constantize+ tries to find a declared constant with the name specified
+ # in the string. It raises a NameError when the name is not in CamelCase
+ # or is not initialized.
+ #
+ # Examples
+ # "Module".constantize # => Module
+ # "Class".constantize # => Class
+ def constantize
+ ActiveSupport::Inflector.constantize(self)
+ end
+
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
#
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 2cc812643f..7d5143ba37 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -33,14 +33,14 @@ module ActiveSupport #:nodoc:
# The set of directories from which we may automatically load files. Files
# under these directories will be reloaded on each request in development mode,
- # unless the directory also appears in load_once_paths.
- mattr_accessor :load_paths
- self.load_paths = []
+ # unless the directory also appears in autoload_once_paths.
+ mattr_accessor :autoload_paths
+ self.autoload_paths = []
# The set of directories from which automatically loaded constants are loaded
- # only once. All directories in this set must also be present in +load_paths+.
- mattr_accessor :load_once_paths
- self.load_once_paths = []
+ # only once. All directories in this set must also be present in +autoload_paths+.
+ mattr_accessor :autoload_once_paths
+ 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 cleared.
@@ -340,7 +340,7 @@ module ActiveSupport #:nodoc:
if Module.method(:const_defined?).arity == 1
# Does this module define this constant?
- # Wrapper to accomodate changing Module#const_defined? in Ruby 1.9
+ # Wrapper to accommodate changing Module#const_defined? in Ruby 1.9
def local_const_defined?(mod, const)
mod.const_defined?(const)
end
@@ -352,22 +352,31 @@ 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.
- def loadable_constants_for_path(path, bases = load_paths)
- expanded_path = Pathname.new(path[/\A(.*?)(\.rb)?\Z/, 1]).expand_path
+ def loadable_constants_for_path(path, bases = autoload_paths)
+ path = $1 if path =~ /\A(.*)\.rb\Z/
+ expanded_path = File.expand_path(path)
+ paths = []
+
+ bases.each do |root|
+ expanded_root = File.expand_path(root)
+ next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
+
+ nesting = expanded_path[(expanded_root.size)..-1]
+ nesting = nesting[1..-1] if nesting && nesting[0] == ?/
+ next if nesting.blank?
- bases.inject([]) do |paths, root|
- expanded_root = Pathname.new(root).expand_path
- nesting = expanded_path.relative_path_from(expanded_root).to_s
- next paths if nesting =~ /\.\./
paths << nesting.camelize
- end.uniq
+ end
+
+ paths.uniq!
+ paths
end
- # Search for a file in load_paths matching the provided suffix.
+ # Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
- load_paths.each do |root|
+ autoload_paths.each do |root|
path = File.join(root, path_suffix)
return path if File.file? path
end
@@ -377,14 +386,14 @@ module ActiveSupport #:nodoc:
# Does the provided path_suffix correspond to an autoloadable module?
# Instead of returning a boolean, the autoload base for this module is returned.
def autoloadable_module?(path_suffix)
- load_paths.each do |load_path|
+ autoload_paths.each do |load_path|
return load_path if File.directory? File.join(load_path, path_suffix)
end
nil
end
def load_once_path?(path)
- load_once_paths.any? { |base| path.starts_with? base }
+ autoload_once_paths.any? { |base| path.starts_with? base }
end
# Attempt to autoload the provided module name by searching for a directory
@@ -396,7 +405,7 @@ module ActiveSupport #:nodoc:
return nil unless base_path = autoloadable_module?(path_suffix)
mod = Module.new
into.const_set const_name, mod
- autoloaded_constants << qualified_name unless load_once_paths.include?(base_path)
+ autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
return mod
end
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 578c025fcf..feb1508586 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,28 +1,27 @@
+require "active_support/notifications"
+
module ActiveSupport
module Deprecation
class << self
- # Behavior is a block that takes a message argument.
- attr_writer :behavior
-
# Whether to print a backtrace along with the warning.
attr_accessor :debug
def behavior
- @behavior ||= default_behavior
+ @behavior ||= DEFAULT_BEHAVIORS[:stderr]
end
- def default_behavior
- Deprecation::DEFAULT_BEHAVIORS[defined?(Rails.env) ? Rails.env.to_s : 'test']
+ def behavior=(behavior)
+ @behavior = DEFAULT_BEHAVIORS[behavior] || behavior
end
end
- # Default warning behaviors per Rails.env. Ignored in production.
+ # Default warning behaviors per Rails.env.
DEFAULT_BEHAVIORS = {
- 'test' => Proc.new { |message, callstack|
+ :stderr => Proc.new { |message, callstack|
$stderr.puts(message)
$stderr.puts callstack.join("\n ") if debug
},
- 'development' => Proc.new { |message, callstack|
+ :log => Proc.new { |message, callstack|
logger =
if defined?(Rails) && Rails.logger
Rails.logger
@@ -32,6 +31,10 @@ module ActiveSupport
end
logger.warn message
logger.debug callstack.join("\n ") if debug
+ },
+ :notify => Proc.new { |message, callstack|
+ ActiveSupport::Notifications.instrument("deprecation.rails",
+ :message => message, :callstack => callstack)
}
}
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
new file mode 100644
index 0000000000..a587d7770c
--- /dev/null
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -0,0 +1,39 @@
+require 'active_support/dependencies'
+
+module ActiveSupport
+ # This module provides an internal implementation to track descendants
+ # which is faster than iterating through ObjectSpace.
+ module DescendantsTracker
+ @@descendants = Hash.new { |h, k| h[k] = [] }
+
+ def self.descendants
+ @@descendants
+ end
+
+ def self.clear
+ @@descendants.each do |klass, descendants|
+ if ActiveSupport::Dependencies.autoloaded?(klass)
+ @@descendants.delete(klass)
+ else
+ descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) }
+ end
+ end
+ end
+
+ def inherited(base)
+ self.direct_descendants << base
+ super
+ end
+
+ def direct_descendants
+ @@descendants[self]
+ end
+
+ def descendants
+ @@descendants[self].inject([]) do |descendants, klass|
+ descendants << klass
+ descendants.concat klass.descendants
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
new file mode 100644
index 0000000000..5f5b264eb9
--- /dev/null
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -0,0 +1,37 @@
+module ActiveSupport
+ # This class is responsible to track files and invoke the given block
+ # whenever one of these files are changed. For example, this class
+ # is used by Rails to reload routes whenever they are changed upon
+ # a new request.
+ #
+ # routes_reloader = ActiveSupport::FileUpdateChecker.new(paths) do
+ # paths.each { |p| load(p) }
+ # Rails::Application.routes.reload!
+ # end
+ #
+ # ActionDispatch::Callbacks.to_prepare do
+ # routes_reloader.execute_if_updated
+ # end
+ #
+ class FileUpdateChecker
+ attr_reader :paths, :last_update_at
+
+ def initialize(paths, calculate=false, &block)
+ @paths = paths
+ @block = block
+ @last_update_at = calculate ? updated_at : nil
+ end
+
+ def updated_at
+ paths.map { |path| File.stat(path).mtime }.max
+ end
+
+ def execute_if_updated
+ current_update_at = self.updated_at
+ if @last_update_at != current_update_at
+ @last_update_at = current_update_at
+ @block.call
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb
index 0ffdd904fd..45b9d20c01 100644
--- a/activesupport/lib/active_support/i18n.rb
+++ b/activesupport/lib/active_support/i18n.rb
@@ -4,5 +4,6 @@ rescue LoadError => e
$stderr.puts "You don't have i18n installed in your application. Please add it to your Gemfile and run bundle install"
raise e
end
+
I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
ActiveSupport.run_load_hooks(:i18n)
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
new file mode 100644
index 0000000000..d82e54f1d4
--- /dev/null
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -0,0 +1,80 @@
+require "active_support"
+require "rails"
+require "active_support/file_update_checker"
+
+module I18n
+ class Railtie < Rails::Railtie
+ config.i18n = ActiveSupport::OrderedOptions.new
+ config.i18n.railties_load_path = []
+ config.i18n.load_path = []
+ config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
+
+ def self.reloader
+ @reloader ||= ActiveSupport::FileUpdateChecker.new([]){ I18n.reload! }
+ end
+
+ # Add I18n::Railtie.reloader to ActionDispatch callbacks. Since, at this
+ # point, no path was added to the reloader, I18n.reload! is not triggered
+ # on to_prepare callbacks. This will only happen on the config.after_initialize
+ # callback below.
+ initializer "i18n.callbacks" do
+ ActionDispatch::Callbacks.to_prepare do
+ I18n::Railtie.reloader.execute_if_updated
+ end
+ end
+
+ # Set the i18n configuration only after initialization since a lot of
+ # configuration is still usually done in application initializers.
+ config.after_initialize do |app|
+ fallbacks = app.config.i18n.delete(:fallbacks)
+
+ app.config.i18n.each do |setting, value|
+ case setting
+ when :railties_load_path
+ app.config.i18n.load_path.unshift(*value)
+ when :load_path
+ I18n.load_path += value
+ else
+ I18n.send("#{setting}=", value)
+ end
+ end
+
+ init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
+
+ reloader.paths.concat I18n.load_path
+ reloader.execute_if_updated
+ end
+
+ protected
+
+ def self.include_fallbacks_module
+ I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
+ end
+
+ def self.init_fallbacks(fallbacks)
+ include_fallbacks_module
+
+ args = case fallbacks
+ when ActiveSupport::OrderedOptions
+ [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
+ when Hash, Array
+ Array.wrap(fallbacks)
+ else # TrueClass
+ []
+ end
+
+ I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
+ end
+
+ def self.validate_fallbacks(fallbacks)
+ case fallbacks
+ when ActiveSupport::OrderedOptions
+ !fallbacks.empty?
+ when TrueClass, Array, Hash
+ true
+ else
+ raise "Unexpected fallback type #{fallbacks.inspect}"
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/json/backends/yajl.rb b/activesupport/lib/active_support/json/backends/yajl.rb
index d76f8b03e4..64e50e0d87 100644
--- a/activesupport/lib/active_support/json/backends/yajl.rb
+++ b/activesupport/lib/active_support/json/backends/yajl.rb
@@ -1,4 +1,4 @@
-require 'yajl-ruby' unless defined?(Yajl)
+require 'yajl' unless defined?(Yajl)
module ActiveSupport
module JSON
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 02c233595d..dd94315111 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -128,12 +128,21 @@ module ActiveSupport
end
end
-class Object
- # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
- def to_json(options = nil)
- ActiveSupport::JSON.encode(self, options)
- end
+# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
+# their default behavior. That said, we need to define the basic to_json method in all of them,
+# otherwise they will always use to_json gem implementation, which is backwards incompatible in
+# several cases (for instance, the JSON implementation for Hash does not work) with inheritance
+# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json.
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
+ klass.class_eval <<-RUBY, __FILE__, __LINE__
+ # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info.
+ def to_json(options = nil)
+ ActiveSupport::JSON.encode(self, options)
+ end
+ RUBY
+end
+class Object
def as_json(options = nil) #:nodoc:
if respond_to?(:to_hash)
to_hash
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index 49ad192bf1..8f08f37a70 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -14,7 +14,7 @@ en:
# Don't forget the nil at the beginning; there's no such thing as a 0th month
month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
- # Used in date_select and datime_select.
+ # Used in date_select and datetime_select.
order:
- :year
- :month
diff --git a/railties/lib/rails/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index a30701d4d5..891d718af3 100644
--- a/railties/lib/rails/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -1,58 +1,66 @@
-require 'active_support/core_ext/class/inheritable_attributes'
-require 'active_support/notifications'
+require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/class/attribute'
-module Rails
- # Rails::LogSubscriber is an object set to consume ActiveSupport::Notifications
- # on initialization with solely purpose of logging. The log subscriber dispatches
- # notifications to a regirested object based on its given namespace.
+module ActiveSupport
+ # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications
+ # with solely purpose of logging. The log subscriber dispatches notifications to a
+ # regirested object based on its given namespace.
#
# An example would be Active Record log subscriber responsible for logging queries:
#
# module ActiveRecord
- # class Railtie
- # class LogSubscriber < Rails::LogSubscriber
- # def sql(event)
- # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
- # end
+ # class LogSubscriber < ActiveSupport::LogSubscriber
+ # def sql(event)
+ # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
# end
# end
# end
#
- # It's finally registed as:
+ # And it's finally registed as:
#
- # Rails::LogSubscriber.add :active_record, ActiveRecord::Railtie::LogSubscriber.new
+ # ActiveRecord::LogSubscriber.attach_to :active_record
#
- # So whenever a "sql.active_record" notification arrive to Rails::LogSubscriber,
+ # Since we need to know all instance methods before attaching the log subscriber,
+ # the line above shuold be called after your ActiveRecord::LogSubscriber definition.
+ #
+ # After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
# the sql method.
#
- # This is useful because it avoids spanning several log subscribers just for logging
- # purposes(which slows down the main thread). Besides of providing a centralized
- # facility on top of Rails.logger.
- #
# Log subscriber also has some helpers to deal with logging and automatically flushes
- # all logs when the request finishes (via action_dispatch.callback notification).
+ # all logs when the request finishes (via action_dispatch.callback notification) in
+ # a Rails environment.
class LogSubscriber
mattr_accessor :colorize_logging
self.colorize_logging = true
- # Embed in a String to clear all previous ANSI sequences.
- CLEAR = "\e[0m"
- BOLD = "\e[1m"
+ class_attribute :logger
+
+ class << self
+ remove_method :logger
+ end
+ def self.logger
+ @logger ||= Rails.logger if defined?(Rails)
+ end
+
+ # Embed in a String to clear all previous ANSI sequences.
+ CLEAR = "\e[0m"
+ BOLD = "\e[1m"
+
# Colors
- BLACK = "\e[30m"
- RED = "\e[31m"
- GREEN = "\e[32m"
- YELLOW = "\e[33m"
- BLUE = "\e[34m"
- MAGENTA = "\e[35m"
- CYAN = "\e[36m"
- WHITE = "\e[37m"
+ BLACK = "\e[30m"
+ RED = "\e[31m"
+ GREEN = "\e[32m"
+ YELLOW = "\e[33m"
+ BLUE = "\e[34m"
+ MAGENTA = "\e[35m"
+ CYAN = "\e[36m"
+ WHITE = "\e[37m"
- def self.add(namespace, log_subscriber, notifier = ActiveSupport::Notifications)
+ def self.attach_to(namespace, log_subscriber=new, notifier=ActiveSupport::Notifications)
log_subscribers << log_subscriber
- @flushable_loggers = nil
+ @@flushable_loggers = nil
log_subscriber.public_methods(false).each do |event|
notifier.subscribe("#{event}.#{namespace}") do |*args|
@@ -61,18 +69,18 @@ module Rails
begin
log_subscriber.send(event, ActiveSupport::Notifications::Event.new(*args))
rescue Exception => e
- Rails.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}"
+ log_subscriber.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}"
end
end
end
end
def self.log_subscribers
- @log_subscribers ||= []
+ @@log_subscribers ||= []
end
def self.flushable_loggers
- @flushable_loggers ||= begin
+ @@flushable_loggers ||= begin
loggers = log_subscribers.map(&:logger)
loggers.uniq!
loggers.select { |l| l.respond_to?(:flush) }
@@ -84,11 +92,6 @@ module Rails
flushable_loggers.each(&:flush)
end
- # By default, we use the Rails.logger for logging.
- def logger
- Rails.logger
- end
-
protected
%w(info debug warn error fatal unknown).each do |level|
diff --git a/railties/lib/rails/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 9b7b0738cd..96506a4b2b 100644
--- a/railties/lib/rails/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,13 +1,16 @@
-require 'rails/log_subscriber'
+require 'active_support/log_subscriber'
-module Rails
+module ActiveSupport
class LogSubscriber
# Provides some helpers to deal with testing log subscribers by setting up
# notifications. Take for instance Active Record subscriber tests:
#
# class SyncLogSubscriberTest < ActiveSupport::TestCase
- # include Rails::LogSubscriber::TestHelper
- # Rails::LogSubscriber.add(:active_record, ActiveRecord::Railties::LogSubscriber.new)
+ # include ActiveSupport::LogSubscriber::TestHelper
+ #
+ # def setup
+ # ActiveRecord::LogSubscriber.attach_to(:active_record)
+ # end
#
# def test_basic_query_logging
# Developer.all
@@ -16,16 +19,6 @@ module Rails
# assert_match /Developer Load/, @logger.logged(:debug).last
# assert_match /SELECT \* FROM "developers"/, @logger.logged(:debug).last
# end
- #
- # class SyncLogSubscriberTest < ActiveSupport::TestCase
- # include Rails::LogSubscriber::SyncTestHelper
- # include LogSubscriberTest
- # end
- #
- # class AsyncLogSubscriberTest < ActiveSupport::TestCase
- # include Rails::LogSubscriber::AsyncTestHelper
- # include LogSubscriberTest
- # end
# end
#
# All you need to do is to ensure that your log subscriber is added to Rails::Subscriber,
@@ -42,7 +35,7 @@ module Rails
@logger = MockLogger.new
@notifier = ActiveSupport::Notifications::Notifier.new(queue)
- Rails::LogSubscriber.colorize_logging = false
+ ActiveSupport::LogSubscriber.colorize_logging = false
set_logger(@logger)
ActiveSupport::Notifications.notifier = @notifier
@@ -86,7 +79,7 @@ module Rails
# end
#
def set_logger(logger)
- Rails.logger = logger
+ ActiveSupport::LogSubscriber.logger = logger
end
def queue
diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb
index e7a271a660..8ffdf5a1bf 100644
--- a/activesupport/lib/active_support/multibyte.rb
+++ b/activesupport/lib/active_support/multibyte.rb
@@ -17,7 +17,7 @@ module ActiveSupport #:nodoc:
@proxy_class = klass
end
- # Returns the currect proxy class
+ # Returns the current proxy class
def self.proxy_class
@proxy_class ||= ActiveSupport::Multibyte::Chars
end
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index d6ccb4bac1..8823e4a5ed 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -50,10 +50,6 @@ module ActiveSupport #:nodoc:
end
end
- def <=>(other)
- @wrapped_string <=> other
- end
-
# Forward all undefined methods to the wrapped string.
def method_missing(method, *args, &block)
if method.to_s =~ /!$/
@@ -87,6 +83,16 @@ module ActiveSupport #:nodoc:
include Comparable
+ # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
+ # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
+ # See <tt>String#<=></tt> for more details.
+ #
+ # Example:
+ # 'é'.mb_chars <=> 'ü'.mb_chars #=> -1
+ def <=>(other)
+ @wrapped_string <=> other.to_s
+ end
+
if RUBY_VERSION < "1.9"
# Returns +true+ if the Chars class can and should act as a proxy for the string _string_. Returns
# +false+ otherwise.
@@ -94,22 +100,12 @@ module ActiveSupport #:nodoc:
$KCODE == 'UTF8' && consumes?(string)
end
- # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
- # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
- # See <tt>String#<=></tt> for more details.
- #
- # Example:
- # 'é'.mb_chars <=> 'ü'.mb_chars #=> -1
- def <=>(other)
- @wrapped_string <=> other.to_s
- end
-
# Returns a new Chars object containing the _other_ object concatenated to the string.
#
# Example:
# ('Café'.mb_chars + ' périferôl').to_s #=> "Café périferôl"
def +(other)
- self << other
+ chars(@wrapped_string + other)
end
# Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
@@ -199,6 +195,45 @@ module ActiveSupport #:nodoc:
Unicode.u_unpack(@wrapped_string)[0]
end
+ # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
+ #
+ # Example:
+ #
+ # "¾ cup".mb_chars.rjust(8).to_s
+ # #=> " ¾ cup"
+ #
+ # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
+ # #=> "   ¾ cup"
+ def rjust(integer, padstr=' ')
+ justify(integer, :right, padstr)
+ end
+
+ # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
+ #
+ # Example:
+ #
+ # "¾ cup".mb_chars.rjust(8).to_s
+ # #=> "¾ cup "
+ #
+ # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
+ # #=> "¾ cup   "
+ def ljust(integer, padstr=' ')
+ justify(integer, :left, padstr)
+ end
+
+ # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
+ #
+ # Example:
+ #
+ # "¾ cup".mb_chars.center(8).to_s
+ # #=> " ¾ cup "
+ #
+ # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
+ # #=> " ¾ cup  "
+ def center(integer, padstr=' ')
+ justify(integer, :center, padstr)
+ end
+
else
def =~(other)
@wrapped_string =~ other
@@ -254,46 +289,6 @@ module ActiveSupport #:nodoc:
end
end
- # Works just like <tt>String#rjust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # #=> " ¾ cup"
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # #=> "   ¾ cup"
- def rjust(integer, padstr=' ')
- justify(integer, :right, padstr)
- end
-
- # Works just like <tt>String#ljust</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.rjust(8).to_s
- # #=> "¾ cup "
- #
- # "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # #=> "¾ cup   "
- def ljust(integer, padstr=' ')
- justify(integer, :left, padstr)
- end
-
- # Works just like <tt>String#center</tt>, only integer specifies characters instead of bytes.
- #
- # Example:
- #
- # "¾ cup".mb_chars.center(8).to_s
- # #=> " ¾ cup "
- #
- # "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
- # #=> " ¾ cup  "
- def center(integer, padstr=' ')
- justify(integer, :center, padstr)
- end
-
-
# Reverses all characters in the string.
#
# Example:
@@ -321,11 +316,12 @@ module ActiveSupport #:nodoc:
result = @wrapped_string.slice(*args)
elsif args.size == 1 && args[0].kind_of?(Numeric)
character = Unicode.u_unpack(@wrapped_string)[args[0]]
- result = character.nil? ? nil : [character].pack('U')
+ result = character && [character].pack('U')
else
- result = Unicode.u_unpack(@wrapped_string).slice(*args).pack('U*')
+ cps = Unicode.u_unpack(@wrapped_string).slice(*args)
+ result = cps && cps.pack('U*')
end
- result.nil? ? nil : chars(result)
+ result && chars(result)
end
alias_method :[], :slice
@@ -375,6 +371,16 @@ module ActiveSupport #:nodoc:
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
end
+ # Capitalizes the first letter of every word, when possible.
+ #
+ # Example:
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
+ # "日本語".mb_chars.titleize # => "日本語"
+ def titleize
+ chars(downcase.to_s.gsub(/\b('?[\S])/u) { Unicode.apply_mapping $1, :uppercase_mapping })
+ end
+ alias_method :titlecase, :titleize
+
# Returns the KC normalization of the string by default. NFKC is considered the best normalization form for
# passing strings to databases and validations.
#
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index f91e50c755..11c72d873b 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -9,7 +9,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '5.1.0'
+ UNICODE_VERSION = '5.2.0'
# The default normalization used for operations that require normalization. It can be set to any of the
# normalizations in NORMALIZATION_FORMS.
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 3f1fe64e9b..1444fc1609 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -23,8 +23,7 @@ module ActiveSupport
#
# event = @events.first
# event.name #=> :render
- # event.duration #=> 10 (in miliseconds)
- # event.result #=> "Foo"
+ # event.duration #=> 10 (in milliseconds)
# event.payload #=> { :extra => :information }
#
# When subscribing to Notifications, you can pass a pattern, to only consume
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 91de722748..6b563b9063 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -149,6 +149,8 @@ module ActiveSupport
self
end
+ alias_method :update, :merge!
+
def merge(other_hash, &block)
dup.merge!(other_hash, &block)
end
@@ -160,6 +162,10 @@ module ActiveSupport
self
end
+ def invert
+ OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
+ end
+
def inspect
"#<OrderedHash #{super}>"
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index 59f9ab18b1..c2deba3b1b 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -1,5 +1,6 @@
require "active_support"
require "rails"
+require "active_support/i18n_railtie"
module ActiveSupport
class Railtie < Rails::Railtie
@@ -11,6 +12,36 @@ module ActiveSupport
require 'active_support/whiny_nil' if app.config.whiny_nils
end
+ initializer "active_support.deprecation_behavior" do |app|
+ if deprecation = app.config.active_support.deprecation
+ ActiveSupport::Deprecation.behavior = deprecation
+ else
+ defaults = {"development" => :log,
+ "production" => :notify,
+ "test" => :stderr}
+
+ env = Rails.env
+
+ if defaults.key?(env)
+ msg = "You did not specify how you would like Rails to report " \
+ "deprecation notices for your #{env} environment, please " \
+ "set config.active_support.deprecation to :#{defaults[env]} " \
+ "at config/environments/#{env}.rb"
+
+ warn msg
+ ActiveSupport::Deprecation.behavior = defaults[env]
+ else
+ msg = "You did not specify how you would like Rails to report " \
+ "deprecation notices for your #{env} environment, please " \
+ "set config.active_support.deprecation to :log, :notify or " \
+ ":stderr at config/environments/#{env}.rb"
+
+ warn msg
+ ActiveSupport::Deprecation.behavior = :stderr
+ end
+ 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|
@@ -27,74 +58,3 @@ module ActiveSupport
end
end
end
-
-module I18n
- class Railtie < Rails::Railtie
- config.i18n = ActiveSupport::OrderedOptions.new
- config.i18n.railties_load_path = []
- config.i18n.load_path = []
- config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
-
- initializer "i18n.initialize" do
- ActiveSupport.on_load(:i18n) do
- I18n.reload!
-
- ActionDispatch::Callbacks.to_prepare do
- I18n.reload!
- end
- end
- end
-
- # Set the i18n configuration from config.i18n but special-case for
- # the load_path which should be appended to what's already set instead of overwritten.
- config.after_initialize do |app|
- fallbacks = app.config.i18n.delete(:fallbacks)
-
- app.config.i18n.each do |setting, value|
- case setting
- when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
- when :load_path
- I18n.load_path += value
- else
- I18n.send("#{setting}=", value)
- end
- end
-
- init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks)
- I18n.reload!
- end
-
- class << self
- protected
-
- def init_fallbacks(fallbacks)
- include_fallbacks_module
- args = case fallbacks
- when ActiveSupport::OrderedOptions
- [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
- when Hash, Array
- Array.wrap(fallbacks)
- else # TrueClass
- []
- end
- I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
- end
-
- def include_fallbacks_module
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
- end
-
- def validate_fallbacks(fallbacks)
- case fallbacks
- when ActiveSupport::OrderedOptions
- !fallbacks.empty?
- when TrueClass, Array, Hash
- true
- else
- raise "Unexpected fallback type #{fallbacks.inspect}"
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 69df399cde..d629f6f2b7 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -45,12 +45,16 @@ module ActiveSupport
end
end
+ def _run_class_setup # class setup method should only happen in parent
+ unless defined?(@@ran_class_setup) || ENV['ISOLATION_TEST']
+ self.class.setup if self.class.respond_to?(:setup)
+ @@ran_class_setup = true
+ end
+ end
+
module TestUnit
def run(result)
- unless defined?(@@ran_class_setup)
- self.class.setup if self.class.respond_to?(:setup)
- @@ran_class_setup = true
- end
+ _run_class_setup
yield(Test::Unit::TestCase::STARTED, name)
@@ -74,10 +78,7 @@ module ActiveSupport
module MiniTest
def run(runner)
- unless defined?(@@ran_class_setup)
- self.class.setup if self.class.respond_to?(:setup)
- @@ran_class_setup = true
- end
+ _run_class_setup
serialized = run_in_isolation do |isolated_runner|
super(isolated_runner)
@@ -109,6 +110,8 @@ module ActiveSupport
end
module Subprocess
+ ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV)
+
# Crazy H4X to get this working in windows / jruby with
# no forking.
def run_in_isolation(&blk)
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 710dce78de..62d02bdeb6 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -18,7 +18,7 @@ module ActiveSupport
#
# See Time and TimeZone for further documentation of these methods.
#
- # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangable. Examples:
+ # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable. Examples:
#
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
# t.hour # => 13
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 67b37785f5..05b40298d3 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -30,154 +30,152 @@ end
# (if a recent version of the gem is installed locally, this will be used instead of the bundled version.)
module ActiveSupport
class TimeZone
- unless const_defined?(:MAPPING)
- # Keys are Rails TimeZone names, values are TZInfo identifiers
- MAPPING = {
- "International Date Line West" => "Pacific/Midway",
- "Midway Island" => "Pacific/Midway",
- "Samoa" => "Pacific/Pago_Pago",
- "Hawaii" => "Pacific/Honolulu",
- "Alaska" => "America/Juneau",
- "Pacific Time (US & Canada)" => "America/Los_Angeles",
- "Tijuana" => "America/Tijuana",
- "Mountain Time (US & Canada)" => "America/Denver",
- "Arizona" => "America/Phoenix",
- "Chihuahua" => "America/Chihuahua",
- "Mazatlan" => "America/Mazatlan",
- "Central Time (US & Canada)" => "America/Chicago",
- "Saskatchewan" => "America/Regina",
- "Guadalajara" => "America/Mexico_City",
- "Mexico City" => "America/Mexico_City",
- "Monterrey" => "America/Monterrey",
- "Central America" => "America/Guatemala",
- "Eastern Time (US & Canada)" => "America/New_York",
- "Indiana (East)" => "America/Indiana/Indianapolis",
- "Bogota" => "America/Bogota",
- "Lima" => "America/Lima",
- "Quito" => "America/Lima",
- "Atlantic Time (Canada)" => "America/Halifax",
- "Caracas" => "America/Caracas",
- "La Paz" => "America/La_Paz",
- "Santiago" => "America/Santiago",
- "Newfoundland" => "America/St_Johns",
- "Brasilia" => "America/Sao_Paulo",
- "Buenos Aires" => "America/Argentina/Buenos_Aires",
- "Georgetown" => "America/Guyana",
- "Greenland" => "America/Godthab",
- "Mid-Atlantic" => "Atlantic/South_Georgia",
- "Azores" => "Atlantic/Azores",
- "Cape Verde Is." => "Atlantic/Cape_Verde",
- "Dublin" => "Europe/Dublin",
- "Edinburgh" => "Europe/London",
- "Lisbon" => "Europe/Lisbon",
- "London" => "Europe/London",
- "Casablanca" => "Africa/Casablanca",
- "Monrovia" => "Africa/Monrovia",
- "UTC" => "Etc/UTC",
- "Belgrade" => "Europe/Belgrade",
- "Bratislava" => "Europe/Bratislava",
- "Budapest" => "Europe/Budapest",
- "Ljubljana" => "Europe/Ljubljana",
- "Prague" => "Europe/Prague",
- "Sarajevo" => "Europe/Sarajevo",
- "Skopje" => "Europe/Skopje",
- "Warsaw" => "Europe/Warsaw",
- "Zagreb" => "Europe/Zagreb",
- "Brussels" => "Europe/Brussels",
- "Copenhagen" => "Europe/Copenhagen",
- "Madrid" => "Europe/Madrid",
- "Paris" => "Europe/Paris",
- "Amsterdam" => "Europe/Amsterdam",
- "Berlin" => "Europe/Berlin",
- "Bern" => "Europe/Berlin",
- "Rome" => "Europe/Rome",
- "Stockholm" => "Europe/Stockholm",
- "Vienna" => "Europe/Vienna",
- "West Central Africa" => "Africa/Algiers",
- "Bucharest" => "Europe/Bucharest",
- "Cairo" => "Africa/Cairo",
- "Helsinki" => "Europe/Helsinki",
- "Kyiv" => "Europe/Kiev",
- "Riga" => "Europe/Riga",
- "Sofia" => "Europe/Sofia",
- "Tallinn" => "Europe/Tallinn",
- "Vilnius" => "Europe/Vilnius",
- "Athens" => "Europe/Athens",
- "Istanbul" => "Europe/Istanbul",
- "Minsk" => "Europe/Minsk",
- "Jerusalem" => "Asia/Jerusalem",
- "Harare" => "Africa/Harare",
- "Pretoria" => "Africa/Johannesburg",
- "Moscow" => "Europe/Moscow",
- "St. Petersburg" => "Europe/Moscow",
- "Volgograd" => "Europe/Moscow",
- "Kuwait" => "Asia/Kuwait",
- "Riyadh" => "Asia/Riyadh",
- "Nairobi" => "Africa/Nairobi",
- "Baghdad" => "Asia/Baghdad",
- "Tehran" => "Asia/Tehran",
- "Abu Dhabi" => "Asia/Muscat",
- "Muscat" => "Asia/Muscat",
- "Baku" => "Asia/Baku",
- "Tbilisi" => "Asia/Tbilisi",
- "Yerevan" => "Asia/Yerevan",
- "Kabul" => "Asia/Kabul",
- "Ekaterinburg" => "Asia/Yekaterinburg",
- "Islamabad" => "Asia/Karachi",
- "Karachi" => "Asia/Karachi",
- "Tashkent" => "Asia/Tashkent",
- "Chennai" => "Asia/Kolkata",
- "Kolkata" => "Asia/Kolkata",
- "Mumbai" => "Asia/Kolkata",
- "New Delhi" => "Asia/Kolkata",
- "Kathmandu" => "Asia/Kathmandu",
- "Astana" => "Asia/Dhaka",
- "Dhaka" => "Asia/Dhaka",
- "Sri Jayawardenepura" => "Asia/Colombo",
- "Almaty" => "Asia/Almaty",
- "Novosibirsk" => "Asia/Novosibirsk",
- "Rangoon" => "Asia/Rangoon",
- "Bangkok" => "Asia/Bangkok",
- "Hanoi" => "Asia/Bangkok",
- "Jakarta" => "Asia/Jakarta",
- "Krasnoyarsk" => "Asia/Krasnoyarsk",
- "Beijing" => "Asia/Shanghai",
- "Chongqing" => "Asia/Chongqing",
- "Hong Kong" => "Asia/Hong_Kong",
- "Urumqi" => "Asia/Urumqi",
- "Kuala Lumpur" => "Asia/Kuala_Lumpur",
- "Singapore" => "Asia/Singapore",
- "Taipei" => "Asia/Taipei",
- "Perth" => "Australia/Perth",
- "Irkutsk" => "Asia/Irkutsk",
- "Ulaan Bataar" => "Asia/Ulaanbaatar",
- "Seoul" => "Asia/Seoul",
- "Osaka" => "Asia/Tokyo",
- "Sapporo" => "Asia/Tokyo",
- "Tokyo" => "Asia/Tokyo",
- "Yakutsk" => "Asia/Yakutsk",
- "Darwin" => "Australia/Darwin",
- "Adelaide" => "Australia/Adelaide",
- "Canberra" => "Australia/Melbourne",
- "Melbourne" => "Australia/Melbourne",
- "Sydney" => "Australia/Sydney",
- "Brisbane" => "Australia/Brisbane",
- "Hobart" => "Australia/Hobart",
- "Vladivostok" => "Asia/Vladivostok",
- "Guam" => "Pacific/Guam",
- "Port Moresby" => "Pacific/Port_Moresby",
- "Magadan" => "Asia/Magadan",
- "Solomon Is." => "Asia/Magadan",
- "New Caledonia" => "Pacific/Noumea",
- "Fiji" => "Pacific/Fiji",
- "Kamchatka" => "Asia/Kamchatka",
- "Marshall Is." => "Pacific/Majuro",
- "Auckland" => "Pacific/Auckland",
- "Wellington" => "Pacific/Auckland",
- "Nuku'alofa" => "Pacific/Tongatapu"
- }.each { |name, zone| name.freeze; zone.freeze }
- MAPPING.freeze
- end
+ # Keys are Rails TimeZone names, values are TZInfo identifiers
+ MAPPING = {
+ "International Date Line West" => "Pacific/Midway",
+ "Midway Island" => "Pacific/Midway",
+ "Samoa" => "Pacific/Pago_Pago",
+ "Hawaii" => "Pacific/Honolulu",
+ "Alaska" => "America/Juneau",
+ "Pacific Time (US & Canada)" => "America/Los_Angeles",
+ "Tijuana" => "America/Tijuana",
+ "Mountain Time (US & Canada)" => "America/Denver",
+ "Arizona" => "America/Phoenix",
+ "Chihuahua" => "America/Chihuahua",
+ "Mazatlan" => "America/Mazatlan",
+ "Central Time (US & Canada)" => "America/Chicago",
+ "Saskatchewan" => "America/Regina",
+ "Guadalajara" => "America/Mexico_City",
+ "Mexico City" => "America/Mexico_City",
+ "Monterrey" => "America/Monterrey",
+ "Central America" => "America/Guatemala",
+ "Eastern Time (US & Canada)" => "America/New_York",
+ "Indiana (East)" => "America/Indiana/Indianapolis",
+ "Bogota" => "America/Bogota",
+ "Lima" => "America/Lima",
+ "Quito" => "America/Lima",
+ "Atlantic Time (Canada)" => "America/Halifax",
+ "Caracas" => "America/Caracas",
+ "La Paz" => "America/La_Paz",
+ "Santiago" => "America/Santiago",
+ "Newfoundland" => "America/St_Johns",
+ "Brasilia" => "America/Sao_Paulo",
+ "Buenos Aires" => "America/Argentina/Buenos_Aires",
+ "Georgetown" => "America/Guyana",
+ "Greenland" => "America/Godthab",
+ "Mid-Atlantic" => "Atlantic/South_Georgia",
+ "Azores" => "Atlantic/Azores",
+ "Cape Verde Is." => "Atlantic/Cape_Verde",
+ "Dublin" => "Europe/Dublin",
+ "Edinburgh" => "Europe/London",
+ "Lisbon" => "Europe/Lisbon",
+ "London" => "Europe/London",
+ "Casablanca" => "Africa/Casablanca",
+ "Monrovia" => "Africa/Monrovia",
+ "UTC" => "Etc/UTC",
+ "Belgrade" => "Europe/Belgrade",
+ "Bratislava" => "Europe/Bratislava",
+ "Budapest" => "Europe/Budapest",
+ "Ljubljana" => "Europe/Ljubljana",
+ "Prague" => "Europe/Prague",
+ "Sarajevo" => "Europe/Sarajevo",
+ "Skopje" => "Europe/Skopje",
+ "Warsaw" => "Europe/Warsaw",
+ "Zagreb" => "Europe/Zagreb",
+ "Brussels" => "Europe/Brussels",
+ "Copenhagen" => "Europe/Copenhagen",
+ "Madrid" => "Europe/Madrid",
+ "Paris" => "Europe/Paris",
+ "Amsterdam" => "Europe/Amsterdam",
+ "Berlin" => "Europe/Berlin",
+ "Bern" => "Europe/Berlin",
+ "Rome" => "Europe/Rome",
+ "Stockholm" => "Europe/Stockholm",
+ "Vienna" => "Europe/Vienna",
+ "West Central Africa" => "Africa/Algiers",
+ "Bucharest" => "Europe/Bucharest",
+ "Cairo" => "Africa/Cairo",
+ "Helsinki" => "Europe/Helsinki",
+ "Kyiv" => "Europe/Kiev",
+ "Riga" => "Europe/Riga",
+ "Sofia" => "Europe/Sofia",
+ "Tallinn" => "Europe/Tallinn",
+ "Vilnius" => "Europe/Vilnius",
+ "Athens" => "Europe/Athens",
+ "Istanbul" => "Europe/Istanbul",
+ "Minsk" => "Europe/Minsk",
+ "Jerusalem" => "Asia/Jerusalem",
+ "Harare" => "Africa/Harare",
+ "Pretoria" => "Africa/Johannesburg",
+ "Moscow" => "Europe/Moscow",
+ "St. Petersburg" => "Europe/Moscow",
+ "Volgograd" => "Europe/Moscow",
+ "Kuwait" => "Asia/Kuwait",
+ "Riyadh" => "Asia/Riyadh",
+ "Nairobi" => "Africa/Nairobi",
+ "Baghdad" => "Asia/Baghdad",
+ "Tehran" => "Asia/Tehran",
+ "Abu Dhabi" => "Asia/Muscat",
+ "Muscat" => "Asia/Muscat",
+ "Baku" => "Asia/Baku",
+ "Tbilisi" => "Asia/Tbilisi",
+ "Yerevan" => "Asia/Yerevan",
+ "Kabul" => "Asia/Kabul",
+ "Ekaterinburg" => "Asia/Yekaterinburg",
+ "Islamabad" => "Asia/Karachi",
+ "Karachi" => "Asia/Karachi",
+ "Tashkent" => "Asia/Tashkent",
+ "Chennai" => "Asia/Kolkata",
+ "Kolkata" => "Asia/Kolkata",
+ "Mumbai" => "Asia/Kolkata",
+ "New Delhi" => "Asia/Kolkata",
+ "Kathmandu" => "Asia/Kathmandu",
+ "Astana" => "Asia/Dhaka",
+ "Dhaka" => "Asia/Dhaka",
+ "Sri Jayawardenepura" => "Asia/Colombo",
+ "Almaty" => "Asia/Almaty",
+ "Novosibirsk" => "Asia/Novosibirsk",
+ "Rangoon" => "Asia/Rangoon",
+ "Bangkok" => "Asia/Bangkok",
+ "Hanoi" => "Asia/Bangkok",
+ "Jakarta" => "Asia/Jakarta",
+ "Krasnoyarsk" => "Asia/Krasnoyarsk",
+ "Beijing" => "Asia/Shanghai",
+ "Chongqing" => "Asia/Chongqing",
+ "Hong Kong" => "Asia/Hong_Kong",
+ "Urumqi" => "Asia/Urumqi",
+ "Kuala Lumpur" => "Asia/Kuala_Lumpur",
+ "Singapore" => "Asia/Singapore",
+ "Taipei" => "Asia/Taipei",
+ "Perth" => "Australia/Perth",
+ "Irkutsk" => "Asia/Irkutsk",
+ "Ulaan Bataar" => "Asia/Ulaanbaatar",
+ "Seoul" => "Asia/Seoul",
+ "Osaka" => "Asia/Tokyo",
+ "Sapporo" => "Asia/Tokyo",
+ "Tokyo" => "Asia/Tokyo",
+ "Yakutsk" => "Asia/Yakutsk",
+ "Darwin" => "Australia/Darwin",
+ "Adelaide" => "Australia/Adelaide",
+ "Canberra" => "Australia/Melbourne",
+ "Melbourne" => "Australia/Melbourne",
+ "Sydney" => "Australia/Sydney",
+ "Brisbane" => "Australia/Brisbane",
+ "Hobart" => "Australia/Hobart",
+ "Vladivostok" => "Asia/Vladivostok",
+ "Guam" => "Pacific/Guam",
+ "Port Moresby" => "Pacific/Port_Moresby",
+ "Magadan" => "Asia/Magadan",
+ "Solomon Is." => "Asia/Magadan",
+ "New Caledonia" => "Pacific/Noumea",
+ "Fiji" => "Pacific/Fiji",
+ "Kamchatka" => "Asia/Kamchatka",
+ "Marshall Is." => "Pacific/Majuro",
+ "Auckland" => "Pacific/Auckland",
+ "Wellington" => "Pacific/Auckland",
+ "Nuku'alofa" => "Pacific/Tongatapu"
+ }.each { |name, zone| name.freeze; zone.freeze }
+ MAPPING.freeze
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.sub(':', '')
@@ -224,7 +222,7 @@ module ActiveSupport
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
end
- # Compare this time zone to the parameter. The two are comapred first on
+ # Compare this time zone to the parameter. The two are compared first on
# their offsets, and then by name.
def <=>(zone)
result = (utc_offset <=> zone.utc_offset)
@@ -352,7 +350,11 @@ module ActiveSupport
def [](arg)
case arg
when String
- zones_map[arg] ||= lookup(arg)
+ begin
+ zones_map[arg] ||= lookup(arg).tap { |tz| tz.utc_offset }
+ rescue TZInfo::InvalidTimezoneIdentifier
+ nil
+ end
when Numeric, ActiveSupport::Duration
arg *= 3600 if arg.abs <= 13
all.find { |z| z.utc_offset == arg.to_i }
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 10f2cae465..4fe0268cca 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index dd84860a91..6db21f9e12 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -1,3 +1,5 @@
+ORIG_ARGV = ARGV.dup
+
begin
old, $VERBOSE = $VERBOSE, nil
require File.expand_path('../../../load_paths', __FILE__)
@@ -23,7 +25,7 @@ require 'active_support/ruby/shim' if RUBY_VERSION < '1.8.7'
def uses_memcached(test_name)
require 'memcache'
begin
- MemCache.new('localhost').stats
+ MemCache.new('localhost:11211').stats
yield
rescue MemCache::MemCacheError
$stderr.puts "Skipping #{test_name} tests. Start memcached and try again."
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index d9702dd9ff..affa1b5e18 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -110,6 +110,14 @@ class StringInflectionsTest < Test::Unit::TestCase
assert_equal 97, 'abc'.ord
end
+ if RUBY_VERSION < '1.9'
+ def test_getbyte
+ assert_equal 97, 'a'.getbyte(0)
+ assert_equal 99, 'abc'.getbyte(2)
+ assert_nil 'abc'.getbyte(3)
+ end
+ 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)
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index cf11f4d28f..5ce4277672 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -832,9 +832,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
def test_time_zone_setter_with_invalid_zone
Time.zone = 'foo'
- assert_not_nil Time.zone
- assert_equal 'foo', Time.zone.name
- assert_raise(TZInfo::InvalidTimezoneIdentifier) { Time.zone.utc_offset }
+ assert_nil Time.zone
Time.zone = -15.hours
assert_nil Time.zone
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 5422c75f5f..c7088638c7 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -25,11 +25,11 @@ class DependenciesTest < Test::Unit::TestCase
this_dir = File.dirname(__FILE__)
parent_dir = File.dirname(this_dir)
$LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
- prior_load_paths = ActiveSupport::Dependencies.load_paths
- ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{this_dir}/#{f}" }
+ prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
+ ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
yield
ensure
- ActiveSupport::Dependencies.load_paths = prior_load_paths
+ ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
ActiveSupport::Dependencies.mechanism = old_mechanism
ActiveSupport::Dependencies.explicitly_unloadable_constants = []
end
@@ -264,7 +264,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_loadable_constants_for_path_should_provide_all_results
fake_root = '/usr/apps/backpack'
with_loading fake_root, fake_root + '/lib' do
- root = ActiveSupport::Dependencies.load_paths.first
+ root = ActiveSupport::Dependencies.autoload_paths.first
assert_equal ["Lib::A::B", "A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/lib/a/b')
end
end
@@ -272,7 +272,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_loadable_constants_for_path_should_uniq_results
fake_root = '/usr/apps/backpack/lib'
with_loading fake_root, fake_root + '/' do
- root = ActiveSupport::Dependencies.load_paths.first
+ root = ActiveSupport::Dependencies.autoload_paths.first
assert_equal ["A::B"], ActiveSupport::Dependencies.loadable_constants_for_path(root + '/a/b')
end
end
@@ -344,7 +344,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_file_search
with_loading 'dependencies' do
- root = ActiveSupport::Dependencies.load_paths.first
+ root = ActiveSupport::Dependencies.autoload_paths.first
assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three')
assert_equal nil, ActiveSupport::Dependencies.search_for_file('service_three.rb')
assert_equal root + '/service_one.rb', ActiveSupport::Dependencies.search_for_file('service_one')
@@ -354,14 +354,14 @@ class DependenciesTest < Test::Unit::TestCase
def test_file_search_uses_first_in_load_path
with_loading 'dependencies', 'autoloading_fixtures' do
- deps, autoload = ActiveSupport::Dependencies.load_paths
+ deps, autoload = ActiveSupport::Dependencies.autoload_paths
assert_match %r/dependencies/, deps
assert_match %r/autoloading_fixtures/, autoload
assert_equal deps + '/conflict.rb', ActiveSupport::Dependencies.search_for_file('conflict')
end
with_loading 'autoloading_fixtures', 'dependencies' do
- autoload, deps = ActiveSupport::Dependencies.load_paths
+ autoload, deps = ActiveSupport::Dependencies.autoload_paths
assert_match %r/dependencies/, deps
assert_match %r/autoloading_fixtures/, autoload
@@ -418,7 +418,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_removal_from_tree_should_be_detected
with_loading 'dependencies' do
- root = ActiveSupport::Dependencies.load_paths.first
+ root = ActiveSupport::Dependencies.autoload_paths.first
c = ServiceOne
ActiveSupport::Dependencies.clear
assert ! defined?(ServiceOne)
@@ -433,7 +433,7 @@ class DependenciesTest < Test::Unit::TestCase
def test_references_should_work
with_loading 'dependencies' do
- root = ActiveSupport::Dependencies.load_paths.first
+ root = ActiveSupport::Dependencies.autoload_paths.first
c = ActiveSupport::Dependencies.ref("ServiceOne")
service_one_first = ServiceOne
assert_equal service_one_first, c.get
@@ -460,9 +460,9 @@ class DependenciesTest < Test::Unit::TestCase
end
end
- def test_load_once_paths_do_not_add_to_autoloaded_constants
+ def test_autoload_once_paths_do_not_add_to_autoloaded_constants
with_autoloading_fixtures do
- ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths.dup
+ ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup
assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder")
assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass")
@@ -473,7 +473,7 @@ class DependenciesTest < Test::Unit::TestCase
end
ensure
Object.class_eval { remove_const :ModuleFolder }
- ActiveSupport::Dependencies.load_once_paths = []
+ ActiveSupport::Dependencies.autoload_once_paths = []
end
def test_application_should_special_case_application_controller
@@ -760,20 +760,20 @@ class DependenciesTest < Test::Unit::TestCase
def test_load_once_constants_should_not_be_unloaded
with_autoloading_fixtures do
- ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths
+ ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths
::A.to_s
assert defined?(A)
ActiveSupport::Dependencies.clear
assert defined?(A)
end
ensure
- ActiveSupport::Dependencies.load_once_paths = []
+ ActiveSupport::Dependencies.autoload_once_paths = []
Object.class_eval { remove_const :A if const_defined?(:A) }
end
- def test_load_once_paths_should_behave_when_recursively_loading
+ def test_autoload_once_paths_should_behave_when_recursively_loading
with_loading 'dependencies', 'autoloading_fixtures' do
- ActiveSupport::Dependencies.load_once_paths = [ActiveSupport::Dependencies.load_paths.last]
+ ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last]
assert !defined?(CrossSiteDependency)
assert_nothing_raised { CrossSiteDepender.nil? }
assert defined?(CrossSiteDependency)
@@ -784,7 +784,7 @@ class DependenciesTest < Test::Unit::TestCase
"CrossSiteDependency shouldn't have been unloaded!"
end
ensure
- ActiveSupport::Dependencies.load_once_paths = []
+ ActiveSupport::Dependencies.autoload_once_paths = []
end
def test_hook_called_multiple_times
diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb
new file mode 100644
index 0000000000..ff24e310de
--- /dev/null
+++ b/activesupport/test/descendants_tracker_test.rb
@@ -0,0 +1,75 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'active_support/core_ext/hash/slice'
+
+class DescendantsTrackerTest < Test::Unit::TestCase
+ class Parent
+ extend ActiveSupport::DescendantsTracker
+ end
+
+ class Child1 < Parent
+ end
+
+ class Child2 < Parent
+ end
+
+ class Grandchild1 < Child1
+ end
+
+ class Grandchild2 < Child1
+ end
+
+ ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2]
+
+ def test_descendants
+ assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+
+ def test_direct_descendants
+ assert_equal [Child1, Child2], Parent.direct_descendants
+ assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants
+ assert_equal [], Child2.direct_descendants
+ end
+
+ def test_clear_with_autoloaded_parent_children_and_granchildren
+ mark_as_autoloaded(*ALL) do
+ ActiveSupport::DescendantsTracker.clear
+ assert ActiveSupport::DescendantsTracker.descendants.slice(*ALL).empty?
+ end
+ end
+
+ def test_clear_with_autoloaded_children_and_granchildren
+ mark_as_autoloaded Child1, Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child2], Parent.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ def test_clear_with_autoloaded_granchildren
+ mark_as_autoloaded Grandchild1, Grandchild2 do
+ ActiveSupport::DescendantsTracker.clear
+ assert_equal [Child1, Child2], Parent.descendants
+ assert_equal [], Child1.descendants
+ assert_equal [], Child2.descendants
+ end
+ end
+
+ protected
+
+ def mark_as_autoloaded(*klasses)
+ old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup
+ ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name)
+
+ old_descendants = ActiveSupport::DescendantsTracker.descendants.dup
+ old_descendants.each { |k, v| old_descendants[k] = v.dup }
+
+ yield
+ ensure
+ ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded
+ ActiveSupport::DescendantsTracker.descendants.replace(old_descendants)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
new file mode 100644
index 0000000000..baf29cc337
--- /dev/null
+++ b/activesupport/test/file_update_checker_test.rb
@@ -0,0 +1,56 @@
+require 'abstract_unit'
+require 'test/unit'
+require 'active_support'
+require 'fileutils'
+
+MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+
+class FileUpdateCheckerTest < Test::Unit::TestCase
+ FILES = %w(1.txt 2.txt 3.txt)
+
+ def setup
+ FileUtils.touch(FILES)
+ end
+
+ def teardown
+ FileUtils.rm(FILES)
+ end
+
+ def test_should_not_execute_the_block_if_no_paths_are_given
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_invoke_the_block_on_first_call_if_it_does_not_calculate_last_updated_at_on_load
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ def test_should_not_invoke_the_block_on_first_call_if_it_calculates_last_updated_at_on_load
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES, true){ i += 1 }
+ checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ def test_should_not_invoke_the_block_if_no_file_has_changed
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ 5.times { checker.execute_if_updated }
+ assert_equal 1, i
+ end
+
+ def test_should_invoke_the_block_if_a_file_has_changed
+ i = 0
+ checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
+ checker.execute_if_updated
+ sleep(1)
+ FileUtils.touch(FILES)
+ checker.execute_if_updated
+ assert_equal 2, i
+ end
+end
diff --git a/railties/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index a3a755ae62..0c1f3c51ed 100644
--- a/railties/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -1,7 +1,7 @@
require 'abstract_unit'
-require 'rails/log_subscriber/test_helper'
+require 'active_support/log_subscriber/test_helper'
-class MyLogSubscriber < Rails::LogSubscriber
+class MyLogSubscriber < ActiveSupport::LogSubscriber
attr_reader :event
def some_event(event)
@@ -25,7 +25,7 @@ class MyLogSubscriber < Rails::LogSubscriber
end
class SyncLogSubscriberTest < ActiveSupport::TestCase
- include Rails::LogSubscriber::TestHelper
+ include ActiveSupport::LogSubscriber::TestHelper
def setup
super
@@ -34,7 +34,7 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
def teardown
super
- Rails::LogSubscriber.log_subscribers.clear
+ ActiveSupport::LogSubscriber.log_subscribers.clear
end
def instrument(*args, &block)
@@ -49,7 +49,7 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
end
def test_set_color_for_messages
- Rails::LogSubscriber.colorize_logging = true
+ ActiveSupport::LogSubscriber.colorize_logging = true
@log_subscriber.bar(nil)
assert_equal "\e[31mcool\e[0m, \e[1m\e[34misn't it?\e[0m", @logger.logged(:info).last
end
@@ -60,56 +60,56 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
end
def test_event_is_sent_to_the_registered_class
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "some_event.my_log_subscriber"
wait
assert_equal %w(some_event.my_log_subscriber), @logger.logged(:info)
end
def test_event_is_an_active_support_notifications_event
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "some_event.my_log_subscriber"
wait
assert_kind_of ActiveSupport::Notifications::Event, @log_subscriber.event
end
def test_does_not_send_the_event_if_it_doesnt_match_the_class
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "unknown_event.my_log_subscriber"
wait
# If we get here, it means that NoMethodError was not raised.
end
def test_does_not_send_the_event_if_logger_is_nil
- Rails.logger = nil
+ ActiveSupport::LogSubscriber.logger = nil
@log_subscriber.expects(:some_event).never
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "some_event.my_log_subscriber"
wait
end
def test_does_not_fail_with_non_namespaced_events
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "whatever"
wait
end
def test_flushes_loggers
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
- Rails::LogSubscriber.flush_all!
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.flush_all!
assert_equal 1, @logger.flush_count
end
def test_flushes_the_same_logger_just_once
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
- Rails::LogSubscriber.add :another, @log_subscriber
- Rails::LogSubscriber.flush_all!
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :another, @log_subscriber
+ ActiveSupport::LogSubscriber.flush_all!
wait
assert_equal 1, @logger.flush_count
end
def test_logging_does_not_die_on_failures
- Rails::LogSubscriber.add :my_log_subscriber, @log_subscriber
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
instrument "puke.my_log_subscriber"
instrument "some_event.my_log_subscriber"
wait
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index f7a5834527..610295fa89 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -49,13 +49,15 @@ class MultibyteCharsTest < Test::Unit::TestCase
end
def test_should_concatenate
- assert_equal 'ab', 'a'.mb_chars + 'b'
- assert_equal 'ab', 'a' + 'b'.mb_chars
- assert_equal 'ab', 'a'.mb_chars + 'b'.mb_chars
+ mb_a = 'a'.mb_chars
+ mb_b = 'b'.mb_chars
+ assert_equal 'ab', mb_a + 'b'
+ assert_equal 'ab', 'a' + mb_b
+ assert_equal 'ab', mb_a + mb_b
- assert_equal 'ab', 'a'.mb_chars << 'b'
- assert_equal 'ab', 'a' << 'b'.mb_chars
- assert_equal 'ab', 'a'.mb_chars << 'b'.mb_chars
+ assert_equal 'ab', mb_a << 'b'
+ assert_equal 'ab', 'a' << mb_b
+ assert_equal 'abb', mb_a << mb_b
end
def test_consumes_utf8_strings
@@ -395,6 +397,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_equal 'こ', @chars.slice(0)
assert_equal 'わ', @chars.slice(3)
assert_equal nil, ''.mb_chars.slice(-1..1)
+ assert_equal nil, ''.mb_chars.slice(-1, 1)
assert_equal '', ''.mb_chars.slice(0..10)
assert_equal 'にちわ', @chars.slice(1..3)
assert_equal 'にちわ', @chars.slice(1, 3)
@@ -443,6 +446,11 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
assert_equal 'Abc', 'abc'.mb_chars.capitalize
end
+ def test_titleize_should_work_on_ascii_characters
+ assert_equal '', ''.mb_chars.titleize
+ assert_equal 'Abc Abc', 'abc abc'.mb_chars.titleize
+ end
+
def test_respond_to_knows_which_methods_the_proxy_responds_to
assert ''.mb_chars.respond_to?(:slice) # Defined on Chars
assert ''.mb_chars.respond_to?(:capitalize!) # Defined on Chars
@@ -480,6 +488,15 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase
end
end
+ def test_titleize_should_be_unicode_aware
+ assert_equal "Él Que Se Enteró", chars("ÉL QUE SE ENTERÓ").titleize
+ assert_equal "Абвг Абвг", chars("аБвг аБвг").titleize
+ end
+
+ def test_titleize_should_not_affect_characters_that_do_not_case_fold
+ assert_equal "日本語", chars("日本語").titleize
+ end
+
def test_limit_should_not_break_on_blank_strings
example = chars('')
assert_equal example, example.limit(0)
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 0f36f5204d..3d1bae163f 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/json'
class OrderedHashTest < Test::Unit::TestCase
def setup
@@ -185,6 +186,12 @@ class OrderedHashTest < Test::Unit::TestCase
assert @ordered_hash.inspect.include?(@hash.inspect)
end
+ def test_json
+ ordered_hash = ActiveSupport::OrderedHash[:foo, :bar]
+ hash = Hash[:foo, :bar]
+ assert_equal ordered_hash.to_json, hash.to_json
+ end
+
def test_alternate_initialization_with_splat
alternate = ActiveSupport::OrderedHash[1,2,3,4]
assert_kind_of ActiveSupport::OrderedHash, alternate
@@ -244,4 +251,16 @@ class OrderedHashTest < Test::Unit::TestCase
assert_equal @ordered_hash.keys, @deserialized_ordered_hash.keys
assert_equal @ordered_hash.values, @deserialized_ordered_hash.values
end
+
+ def test_update_sets_keys
+ @updated_ordered_hash = ActiveSupport::OrderedHash.new
+ @updated_ordered_hash.update(:name => "Bob")
+ assert_equal [:name], @updated_ordered_hash.keys
+ end
+
+ def test_invert
+ expected = ActiveSupport::OrderedHash[@values.zip(@keys)]
+ assert_equal expected, @ordered_hash.invert
+ assert_equal @values.zip(@keys), @ordered_hash.invert.to_a
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 620623b389..af6eee69e5 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -268,7 +268,7 @@ class TimeZoneTest < Test::Unit::TestCase
end
def test_index
- assert_not_nil ActiveSupport::TimeZone["bogus"]
+ assert_nil ActiveSupport::TimeZone["bogus"]
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone["Central Time (US & Canada)"]
assert_instance_of ActiveSupport::TimeZone, ActiveSupport::TimeZone[8]
assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] }
@@ -285,6 +285,11 @@ class TimeZoneTest < Test::Unit::TestCase
assert_equal(-21_600, zone.utc_offset)
end
+ def test_unknown_zones_dont_store_mapping_keys
+ ActiveSupport::TimeZone["bogus"]
+ assert !ActiveSupport::TimeZone.zones_map.key?("bogus")
+ end
+
def test_new
assert_equal ActiveSupport::TimeZone["Central Time (US & Canada)"], ActiveSupport::TimeZone.new("Central Time (US & Canada)")
end
diff --git a/bin/rails b/bin/rails
index 4a74726e9b..a628cc35ca 100755
--- a/bin/rails
+++ b/bin/rails
@@ -1,3 +1,5 @@
+#!/usr/bin/env ruby
+
begin
require "rails/cli"
rescue LoadError
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index c33a4d11dd..8e2bac7f00 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,6 +1,13 @@
*Rails 3.0.0 [Release Candidate] (unreleased)*
+* Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim]
+
+* Rails::LogSubscriber was renamed to ActiveSupport::LogSubscriber [José Valim]
+
+* config.load_(once_)paths in config/application.rb got renamed to config.autoload_(once_)paths. [fxn]
+
* Abort generation/booting on Ruby 1.9.1. [fxn]
+
* Made the rails command work even when you're in a subdirectory [Chad Fowler]
diff --git a/railties/Rakefile b/railties/Rakefile
index bf19961b59..ddc872e18b 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -18,11 +18,11 @@ task :test => 'test:isolated'
namespace :test do
task :isolated do
dir = ENV["TEST_DIR"] || "**"
- Dir["test/#{dir}/*_test.rb"].all? do |file|
+ Dir["test/#{dir}/*_test.rb"].each do |file|
next true if file.include?("fixtures")
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file)
- end or raise "Failures"
+ end
end
end
diff --git a/railties/guides/assets/javascripts/code_highlighter.js b/railties/guides/assets/javascripts/code_highlighter.js
index ce983dad52..ce983dad52 100755..100644
--- a/railties/guides/assets/javascripts/code_highlighter.js
+++ b/railties/guides/assets/javascripts/code_highlighter.js
diff --git a/railties/guides/assets/javascripts/guides.js b/railties/guides/assets/javascripts/guides.js
index c4e4d459ea..c4e4d459ea 100755..100644
--- a/railties/guides/assets/javascripts/guides.js
+++ b/railties/guides/assets/javascripts/guides.js
diff --git a/railties/guides/assets/stylesheets/print.css b/railties/guides/assets/stylesheets/print.css
index 628da105d4..628da105d4 100755..100644
--- a/railties/guides/assets/stylesheets/print.css
+++ b/railties/guides/assets/stylesheets/print.css
diff --git a/railties/guides/assets/stylesheets/reset.css b/railties/guides/assets/stylesheets/reset.css
index cb14fbcc55..cb14fbcc55 100755..100644
--- a/railties/guides/assets/stylesheets/reset.css
+++ b/railties/guides/assets/stylesheets/reset.css
diff --git a/railties/guides/assets/stylesheets/style.css b/railties/guides/assets/stylesheets/style.css
index 89b2ab885a..89b2ab885a 100755..100644
--- a/railties/guides/assets/stylesheets/style.css
+++ b/railties/guides/assets/stylesheets/style.css
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 9d8426b5de..038ca903c1 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -338,6 +338,25 @@ end
Note that while for session values you set the key to +nil+, to delete a cookie value you should use +cookies.delete(:key)+.
+h3. Rendering xml and json data
+
+ActionController makes it extremely easy to render +xml+ or +json+ data. If you generate a controller using scaffold then your controller would look something like this.
+
+<ruby>
+class UsersController < ApplicationController
+ def index
+ @users = User.all
+ respond_to do |format|
+ format.html # index.html.erb
+ format.xml { render :xml => @users}
+ end
+ end
+end
+</ruby>
+
+Notice that in the above case code is <tt>render :xml => @users</tt> and not <tt>render :xml => @users.to_xml</tt>. That is because if the input is not string then rails automatically invokes +to_xml+ .
+
+
h3. Filters
Filters are methods that are run before, after or "around" a controller action.
diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile
index d81e461e63..e6ef2cdd20 100644
--- a/railties/guides/source/active_record_basics.textile
+++ b/railties/guides/source/active_record_basics.textile
@@ -32,16 +32,16 @@ Active Record gives us several mechanisms, the most important being the ability
* Validate models before they get persisted to the database
* Perform database operations in an object-oriented fashion.
-h3. Convention over Configuration in ActiveRecord
+h3. 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 ActiveRecord 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.
h4. Naming Conventions
-By default, ActiveRecord 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 i.e. (book_clubs)
-* Model Class - Singular with the first letter of each word capitalized i.e. (BookClub)
+* 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 |_.Table / Schema |
|Post |posts|
@@ -53,30 +53,32 @@ By default, ActiveRecord uses some naming conventions to find out how the mappin
h4. Schema Conventions
-ActiveRecord uses naming conventions for the columns in database tables, depending on the purpose of these columns.
+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 table_id i.e. (item_id, order_id). These are the fields that ActiveRecord will look for when you create associations between your models.
-* *Primary keys* - By default, ActiveRecord 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.
+* *Foreign keys* - These fields should be named following the pattern table_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 ActiveRecord instances:
+There are also some optional column names that will create additional features to Active Record instances:
-* *created_at / created_on* - ActiveRecord will store the current date and time to this field when creating the record.
-* *updated_at / updated_on* - ActiveRecord will store the current date and times to this field when updating the record.
+* *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.com/classes/ActiveRecord/Locking.html to a model.
* *type* - Specifies that the model uses "Single Table Inheritance":http://api.rubyonrails.com/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 ActiveRecord. 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. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
+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. If you are not using STI, try an analogous keyword like "context", that may still accurately describe the data you are modeling.
-h3. Creating ActiveRecord Models
+h3. Creating Active Record Models
-It's very easy to create ActiveRecord models. All you have to do is to subclass the ActiveRecord::Base class and you're good to go:
+It's 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
</ruby>
-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. So, suppose that the *products* table was created using a 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. So, suppose that the *products* table was created using an SQL sentence like:
<sql>
CREATE TABLE products (
@@ -99,12 +101,15 @@ h3. 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.
You can use the +ActiveRecord::Base.set_table_name+ method to specify the table name that should be used:
+
<ruby>
class Product < ActiveRecord::Base
set_table_name "PRODUCT"
end
</ruby>
+
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
set_fixture_class :funny_jokes => 'Joke'
@@ -113,7 +118,8 @@ class FunnyJoke < ActiveSupport::TestCase
end
</ruby>
-It's also possible to override the column that should be used as the table's primary key. Use the +ActiveRecord::Base.set_primary_key+ method for that:
+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
set_primary_key "product_id"
@@ -122,7 +128,7 @@ end
h3. Reading and Writing Data
-CRUD is an acronym for the four verbs we use to operate on data: Create, Read, Update, Delete. 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.
h4. Create
@@ -155,7 +161,7 @@ Finally, passing a block to either create or new will return a new User object:
h4. Read
-ActiveRecord provides a rich API for accessing data within a database. Below are a few examples of different data access methods provided by ActiveRecord.
+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 all records
@@ -163,7 +169,7 @@ ActiveRecord provides a rich API for accessing data within a database. Below are
</ruby>
<ruby>
- # return first record
+ # return the first record
user = User.first
</ruby>
@@ -201,11 +207,11 @@ Likewise, once retrieved an Active Record object can be destroyed which removes
h3. 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":activerecord_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. You can learn more about validations in the "Active Record Validations and Callbacks guide":active_record_validations_callbacks.html#validations-overview.
h3. 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":activerecord_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 Validations and Callbacks guide":active_record_validations_callbacks.html#callbacks-overview.
h3. Migrations
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index 73f3ebafbe..f5e70aef41 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -51,7 +51,7 @@ Active Record will perform queries on the database for you and is compatible wit
h3. Retrieving Objects from the Database
-To retrieve objects from the database, Active Record provides several finder methods. These methods allows you to pass arguments into it to perform certain queries on your database without the need of writing raw SQL.
+To retrieve objects from the database, Active Record provides several finder methods. Each finder method allows you to pass arguments into it to perform certain queries on your database without writing raw SQL.
The methods are:
* +where+
@@ -66,7 +66,7 @@ The methods are:
* +readonly+
* +from+
-All of these methods return a Relation
+All of the above methods return an instance of <tt>ActiveRecord::Relation</tt>.
Primary operation of <tt>Model.find(options)</tt> can be summarized as:
diff --git a/railties/guides/source/activerecord_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 1f9bc1279a..cfd4ae55cc 100644
--- a/railties/guides/source/activerecord_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -16,7 +16,7 @@ endprologue.
h3. The Object Lifecycle
-During the normal operation of a Rails application objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</em> so that you can control your application and its data.
+During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</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.
@@ -71,7 +71,7 @@ The following methods trigger validations, and will save the object to the datab
* +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 object/s.
+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.
h4. Skipping Validations
@@ -240,9 +240,9 @@ class Account < ActiveRecord::Base
end
</ruby>
-The +validates_exclusion_of+ 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 +validates_exclusion_of+ 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 for +validates_exclusion_of+ is "_is reserved_".
+The default error message for +validates_exclusion_of+ is "_is reserved_".
h4. +validates_format_of+
@@ -270,7 +270,7 @@ end
The +validates_inclusion_of+ 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 +validates_inclusion_of+ is "_is not included in the list_".
+The default error message for +validates_inclusion_of+ is "_is not included in the list_".
h4. +validates_length_of+
@@ -343,7 +343,7 @@ Besides +:only_integer+, the +validates_numericality_of+ helper also accepts the
* +: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 or equal to %{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_".
@@ -374,7 +374,7 @@ The default error message for +validates_presence_of+ is "_can't be empty_".
h4. +validates_uniqueness_of+
-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 an unique index in your database.
+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
@@ -423,14 +423,14 @@ class GoodnessValidator < ActiveRecord::Validator
end
</ruby>
-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.
+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.
The validator class has two attributes by default:
* +record+ - the record to be validated
* +options+ - the extra options that were passed to +validates_with+
-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+:
+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
@@ -494,7 +494,7 @@ As you've already seen, the +:message+ option lets you specify the message that
h4. +: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 ran 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.
+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
@@ -573,7 +573,7 @@ class Invoice < ActiveRecord::Base
end
</ruby>
-You can even create your own validation helpers and reuse them in several different models. For example, an application that manages surveys may find useful to express that a certain field corresponds to a set of choices:
+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
@@ -599,7 +599,7 @@ The following is a list of the most commonly used methods. Please refer to the +
h4(#working_with_validation_errors-errors). +errors+
-Returns an OrderedHash with all errors. Each key is the attribute name and value is an array of strings with all errors.
+Returns an OrderedHash with all errors. Each key is the attribute name and the value is an array of strings with all errors.
<ruby>
class Person < ActiveRecord::Base
@@ -619,7 +619,7 @@ person.errors # => []
h4(#working_with_validation_errors-errors-2). +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 returns an empty array.
++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
@@ -681,7 +681,7 @@ Another way to do this is using +[]=+ setter
h4. +errors[:base]+
-You can add errors 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 the array and uses it as the error message.
+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 the array and uses it as the error message.
<ruby>
class Person < ActiveRecord::Base
@@ -789,7 +789,7 @@ Both the +form.error_messages+ and the +error_messages_for+ helpers accept optio
:header_tag => :h3 %>
</erb>
-Which results in the following content
+Which results in the following content:
!images/customized_error_messages.png(Customized error messages)!
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 1c17609b0a..844b9428bd 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -24,7 +24,7 @@ h5. Cherry-picking a Definition
The most lightweight way to get +blank?+ is to cherry-pick the file that defines it.
-For every single method defined as a core extension this guide has a note that says where is such a method defined. In the case of +blank?+ the note reads:
+For every single method defined as a core extension this guide has a note that says where such a method is defined. In the case of +blank?+ the note reads:
NOTE: Defined in +active_support/core_ext/object/blank.rb+.
@@ -124,7 +124,7 @@ NOTE: Defined in +active_support/core_ext/object/blank.rb+.
h4. +duplicable?+
-A few fundamental objects in Ruby are singletons. For example, in the whole live of a program the integer 1 refers always to the same instance:
+A few fundamental objects in Ruby are singletons. For example, in the whole life of a program the integer 1 refers always to the same instance:
<ruby>
1.object_id # => 3
@@ -159,7 +159,7 @@ NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
h4. +returning+
-The method +returning+ yields its argument to a block and returns it. You tipically use it with a mutable object that gets modified in the block:
+The method +returning+ yields its argument to a block and returns it. You typically use it with a mutable object that gets modified in the block:
<ruby>
def html_options_for_form(url_for_options, options, *parameters_for_url)
@@ -187,7 +187,7 @@ def log_info(sql, name, ms)
end
</ruby>
-You can shorten that using +Object#try+. This method is a synonim for +Object#send+ except that it returns +nil+ if sent to +nil+. The previous example could then be rewritten as:
+You can shorten that using +Object#try+. This method is a synonym for +Object#send+ except that it returns +nil+ if sent to +nil+. The previous example could then be rewritten as:
<ruby>
def log_info(sql, name, ms)
@@ -209,8 +209,7 @@ String.singleton_class # => #<Class:String>
String.new.singleton_class # => #<Class:#<String:0x17a1d1c>>
</ruby>
-WARNING: Fixnums and symbols have no singleton classes, +singleton_class+
-raises +TypeError+ on them. Moreover, the singleton classes of +nil+, +true+, and +false+, are +NilClass+, +TrueClass+, and +FalseClass+, respectively.
+WARNING: Fixnums and symbols have no singleton classes, +singleton_class+ raises +TypeError+ on them. Moreover, the singleton classes of +nil+, +true+, and +false+, are +NilClass+, +TrueClass+, and +FalseClass+, respectively.
NOTE: Defined in +active_support/core_ext/kernel/singleton_class.rb+.
@@ -256,7 +255,7 @@ NOTE: Defined in +active_support/core_ext/object/acts_like.rb+.
h4. +to_param+
-All objects in Rails respond to the method +to_param+, which is meant to return something that represents them as values in a query string, or as a URL fragments.
+All objects in Rails respond to the method +to_param+, which is meant to return something that represents them as values in a query string, or as URL fragments.
By default +to_param+ just calls +to_s+:
@@ -294,7 +293,7 @@ we get:
user_path(@user) # => "/users/357-john-smith"
</ruby>
-WARNING. Controllers need to be aware of any redifinition of +to_param+ because when a request like that comes in "357-john-smith" is the value of +params[:id]+.
+WARNING. Controllers need to be aware of any redefinition of +to_param+ because when a request like that comes in "357-john-smith" is the value of +params[:id]+.
NOTE: Defined in +active_support/core_ext/object/to_param.rb+.
@@ -332,7 +331,7 @@ Arrays return the result of applying +to_query+ to each element with <tt>_key_[]
# => "sample%5B%5D=3.4&sample%5B%5D=-45.6"
</ruby>
-Hashes also respond to +to_query+ but with a different signature. If no argument is passed a call generates a sorted series of key/value assigments calling +to_query(key)+ on its values. Then it joins the result with "&":
+Hashes also respond to +to_query+ but with a different signature. If no argument is passed a call generates a sorted series of key/value assignments calling +to_query(key)+ on its values. Then it joins the result with "&":
<ruby>
{:c => 3, :b => 2, :a => 1}.to_query # => "a=1&b=2&c=3"
@@ -656,9 +655,9 @@ h5. Internal Attributes
When you are defining an attribute in a class that is meant to be subclassed name collisions are a risk. That's remarkably important for libraries.
-Active Support defines the macros +attr_internal_reader+, +attr_internal_writer+, and +attr_internal_accessor+. They behave like their Ruby builtin +attr_*+ counterparts, except they name the unerlying instace variable in a way that makes collisions less likely.
+Active Support defines the macros +attr_internal_reader+, +attr_internal_writer+, and +attr_internal_accessor+. They behave like their Ruby builtin +attr_*+ counterparts, except they name the underlying instance variable in a way that makes collisions less likely.
-The macro +attr_internal+ is a synonim for +attr_internal_accessor+:
+The macro +attr_internal+ is a synonym for +attr_internal_accessor+:
<ruby>
# library
@@ -721,7 +720,7 @@ h4. Method Delegation
The class method +delegate+ offers an easy way to forward methods.
-For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to acces such attribute directly, <tt>user.age</tt>, instead of having to explicit the chain <tt>user.profile.age</tt>.
+For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to access such attributes directly, <tt>user.age</tt>, instead of having to explicit the chain <tt>user.profile.age</tt>.
That can be accomplished by hand:
@@ -935,7 +934,7 @@ NOTE: Defined in +active_support/core_ext/module/synchronization.rb+.
h4. Reachable
-A named module is reachable if it is stored in its correspoding constant. It means you can reach the module object via the constant.
+A named module is reachable if it is stored in its corresponding constant. It means you can reach the module object via the constant.
That is what ordinarily happens, if a module is called "M", the +M+ constant exists and holds it:
@@ -1016,7 +1015,7 @@ h3. Extensions to +Class+
h4. Class Attributes
-The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overriden at any level down the hierarchy:
+The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy:
<ruby>
class A
@@ -1088,7 +1087,7 @@ NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+.
h4. Class Inheritable Attributes
-Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accesors for class-level data which is inherited but not shared with children:
+Class variables are shared down the inheritance tree. Class instance variables are not shared, but they are not inherited either. The macros +class_inheritable_reader+, +class_inheritable_writer+, and +class_inheritable_accessor+ provide accessors for class-level data which is inherited but not shared with children:
<ruby>
module ActionController
@@ -1204,7 +1203,7 @@ s.html_safe? # => true
s # => "<script>...</script>"
</ruby>
-It is your responsability to ensure calling +html_safe+ on a particular string is fine.
+It is your responsibility to ensure calling +html_safe+ on a particular string is fine.
NOTE: For performance reasons safe strings are implemented in a way that cannot offer an in-place +html_safe!+ variant.
@@ -1377,7 +1376,7 @@ The method +pluralize+ returns the plural of its receiver:
"equipment".pluralize # => "equipment"
</ruby>
-As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Builtin rules can be extended in +config/initializers/inflections.rb+. That file is generated by the +rails+ command and has instructions in comments.
+As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in +config/initializers/inflections.rb+. That file is generated by the +rails+ command and has instructions in comments.
Active Record uses this method to compute the default table name that corresponds to a model:
@@ -1697,6 +1696,90 @@ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
+h4. Conversions
+
+h5. +constantize+
+
+The method +constantize+ expects the receiver to contain the name of a constant, and tries to get you the object stored in there, assuming it is defined:
+
+<ruby>
+"ActiveRecord::Base".constantize # => ActiveRecord::Base
+</ruby>
+
+The name is assumed to be top-level, no matter whether it starts with "::" or not. No lexical context is taken into account:
+
+<ruby>
+C = 1
+module M
+ C = 2
+ "C".constantize # => 1, same as "::C".constantize
+end
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
+
+h5. +ord+
+
+Ruby 1.9 defines +ord+ to be the codepoint of the first character of the receiver. Active Support backports +ord+ for single-byte encondings like ASCII or ISO-8859-1 in Ruby 1.8:
+
+<ruby>
+"a".ord # => 97
+"à".ord # => 224, in ISO-8859-1
+</ruby>
+
+In Ruby 1.8 +ord+ doesn't work in general in UTF8 strings, use the multibyte support in Active Support for that:
+
+<ruby>
+"a".mb_chars.ord # => 97
+"à".mb_chars.ord # => 224, in UTF8
+</ruby>
+
+Note that the 224 is different in both examples. In ISO-8859-1 "à" is represented as a single byte, 224. Its single-character representattion in UTF8 has two bytes, namely 195 and 160, but its Unicode codepoint is 224. If we call +ord+ on the UTF8 string "à" the return value will be 195 in Ruby 1.8. That is not an error, because UTF8 is unsupported, the call itself would be bogus.
+
+INFO: +ord+ is equivalent to +getbyte(0)+.
+
+NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
+
+h5. +getbyte+
+
+Active Support backports +getbyte+ from Ruby 1.9:
+
+<ruby>
+"foo".getbyte(0) # => 102, same as "foo".ord
+"foo".getbyte(1) # => 111
+"foo".getbyte(9) # => nil
+"foo".getbyte(-1) # => 111
+</ruby>
+
+INFO: +getbyte+ is equivalent to +[]+.
+
+NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
+
+h5. +to_date+, +to_time+, +to_datetime+
+
+The methods +to_date+, +to_time+, and +to_datetime+ are basically convenience wrappers around +Date._parse+:
+
+<ruby>
+"2010-07-27".to_date # => Tue, 27 Jul 2010
+"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010
+"2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000
+</ruby>
+
++to_time+ receives an optional argument +:utc+ or +:local+, to indicate which time zone you want the time in:
+
+<ruby>
+"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010
+"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010
+</ruby>
+
+Default is +:utc+.
+
+Please refer to the documentation of +Date._parse+ for further details.
+
+INFO: The three of them return +nil+ for blank receivers.
+
+NOTE: Defined in +active_support/core_ext/string/conversions.rb+.
+
h3. Extensions to +Numeric+
h4. Bytes
@@ -1760,7 +1843,7 @@ h3. Extensions to +Float+
h4. +round+
-The builtin method +Float#round+ rounds a float to the nearest integer. Active Support adds an optional parameter to let you specify a precision:
+The built-in method +Float#round+ rounds a float to the nearest integer. Active Support adds an optional parameter to let you specify a precision:
<ruby>
Math::E.round(4) # => 2.7183
@@ -1925,7 +2008,7 @@ Similarly, +from+ returns the tail from the element at the passed index on:
[].from(0) # => []
</ruby>
-The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is builtin). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available.
+The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is built-in). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available.
NOTE: Defined in +active_support/core_ext/array/access.rb+.
@@ -2120,7 +2203,7 @@ NOTE: Defined in +active_support/core_ext/array/conversions.rb+.
h4. Wrapping
-The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behaviour for enumerables:
+The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behavior for enumerables:
<ruby>
Array.wrap(:foo => :bar) # => [{:foo => :bar}]
@@ -2282,7 +2365,7 @@ NOTE: Defined in +active_support/core_ext/hash/conversions.rb+.
h4. Merging
-Ruby has a builtin method +Hash#merge+ that merges two hashes:
+Ruby has a built-in method +Hash#merge+ that merges two hashes:
<ruby>
{:a => 1, :b => 1}.merge(:a => 0, :c => 2)
@@ -2511,7 +2594,7 @@ NOTE: Defined in +active_support/core_ext/hash/keys.rb+.
h4. Slicing
-Ruby has builtin support for taking slices out of strings and arrays. Active Support extends slicing to hashes:
+Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes:
<ruby>
{:a => 1, :b => 2, :c => 3}.slice(:a, :c)
@@ -2625,7 +2708,7 @@ Active Support extends this method so that the argument may be another range in
(1...9).include?(3..9) # => false
</ruby>
-WARNING: The orginal +Range#include?+ is still the one aliased to +Range#===+.
+WARNING: The original +Range#include?+ is still the one aliased to +Range#===+.
NOTE: Defined in +active_support/core_ext/range/include_range.rb+.
@@ -3080,7 +3163,7 @@ Active Support adds +missing_name?+ to +NameError+, which tests whether the exce
The name may be given as a symbol or string. A symbol is tested against the bare constant name, a string is against the fully-qualified constant name.
-TIP: A symbol can represent a fully-qualified constant name as in +:"ActiveRecord::Base"+, so the behaviour for symbols is defined for convenience, not because it has to be that way technically.
+TIP: A symbol can represent a fully-qualified constant name as in +:"ActiveRecord::Base"+, so the behavior for symbols is defined for convenience, not because it has to be that way technically.
For example, when an action of +PostsController+ is called Rails tries optimistically to use +PostsHelper+. It is OK that the helper module does not exist, so if an exception for that constant name is raised it should be silenced. But it could be the case that +posts_helper.rb+ raises a +NameError+ due to an actual unknown constant. That should be reraised. The method +missing_name?+ provides a way to distinguish both cases:
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 515b3aad39..1f1b7d076e 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -205,7 +205,7 @@ Upon form submission the value entered by the user will be stored in +params[:pe
WARNING: You must pass the name of an instance variable, i.e. +:person+ or +"person"+, not an actual instance of your model object.
-Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the "Active Record Validations and Callbacks":./activerecord_validations_callbacks.html#displaying-validation-errors-in-the-view guide.
+Rails provides helpers for displaying the validation errors associated with a model object. These are covered in detail by the "Active Record Validations and Callbacks":./active_record_validations_callbacks.html#displaying-validation-errors-in-the-view guide.
h4. Binding a Form to an Object
diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile
index 704a8793b2..c5b41673e1 100644
--- a/railties/guides/source/generators.textile
+++ b/railties/guides/source/generators.textile
@@ -1,21 +1,21 @@
-h2. Creating and customizing Rails Generators
+h2. Creating and Customizing Rails Generators
Rails generators are an essential tool if you plan to improve your workflow and in this guide you will learn how to create and customize already existing generators.
In this guide you will:
-* 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 generators templates;
-* Learn how to use fallbacks to avoid overwriting a huge set of generators;
+* 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
endprologue.
NOTE: This guide is about Rails generators for versions >= 3.0. Rails generators from previous versions are not supported.
-h3. First contact
+h3. First Contact
When you create an application using the +rails+ command, you are in fact using a Rails generator. After that, you can get a list of all available generators by just invoking +rails generate+:
@@ -25,15 +25,15 @@ $ cd myapp
$ rails generate
</shell>
-You will get a list of all generators that comes with Rails. If you need a detailed description, for instance about the helper generator, you can simply do:
+You will get a list of all generators that comes with Rails. If you need a detailed description of the helper generator, for example, you can simply do:
<shell>
$ rails generate helper --help
</shell>
-h3. Creating your first generator
+h3. Creating Your First Generator
-Since Rails 3.0, generators are built on top of "Thor":http://github.com/wycats/thor. Thor has a powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named +initializer.rb+ inside +config/initializers+.
+Since Rails 3.0, generators are built on top of "Thor":http://github.com/wycats/thor. Thor provides powerful options parsing and a great API for manipulating files. For instance, let's build a generator that creates an initializer file named +initializer.rb+ inside +config/initializers+.
The first step is to create a file at +RAILS_APP/lib/generators/initializer_generator.rb+ with the following content:
@@ -45,7 +45,7 @@ class InitializerGenerator < Rails::Generators::Base
end
</ruby>
-Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and have one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with Rails Application Templates API, you are at home with new generators API.
+Our new generator is quite simple: it inherits from +Rails::Generators::Base+ and has one method definition. Each public method in the generator is executed when a generator is invoked. Finally, we invoke the +create_file+ method that will create a file at the given destination with the given content. If you are familiar with the Rails Application Templates API, you'll feel right at home with the new generators API.
To invoke our new generator, we just need to do:
@@ -59,7 +59,7 @@ Before we go on, let's see our brand new generator description:
$ rails generate initializer --help
</shell>
-Rails usually is able to generate good descriptions if a generator is namespaced, as +ActiveRecord::Generators::ModelGenerator+, but not in this particular case. We can solve this problem in two ways. The first one is calling +desc+ inside our generator:
+Rails is usually able to generate good descriptions if a generator is namespaced, as +ActiveRecord::Generators::ModelGenerator+, but not in this particular case. We can solve this problem in two ways. The first one is calling +desc+ inside our generator:
<ruby>
class InitializerGenerator < Rails::Generators::Base
@@ -70,9 +70,9 @@ class InitializerGenerator < Rails::Generators::Base
end
</ruby>
-Now we can see the new description by invoking +--help+ in the new generator. The second way to add a description is by creating a file named +USAGE+ in the same directory as our generator. We are going to do that in the next step.
+Now we can see the new description by invoking +--help+ on the new generator. The second way to add a description is by creating a file named +USAGE+ in the same directory as our generator. We are going to do that in the next step.
-h3. Creating generators with generators
+h3. Creating Generators with Generators
A faster way to create a generator is using the generator's generator:
@@ -84,7 +84,7 @@ $ rails generate generator initializer
create lib/generators/initializer/templates
</shell>
-And it will create a new generator as follow:
+And it will create a new generator as follows:
<ruby>
class InitializerGenerator < Rails::Generators::NamedBase
@@ -92,7 +92,7 @@ class InitializerGenerator < Rails::Generators::NamedBase
end
</ruby>
-At first, we can notice that we are inheriting from +Rails::Generators::NamedBase+ instead of +Rails::Generators::Base+. This means that our generator expects as least one argument, which will be the name of the initializer.
+First, notice that we are inheriting from +Rails::Generators::NamedBase+ instead of +Rails::Generators::Base+. This means that our generator expects as least one argument, which will be the name of the initializer.
We can see that by invoking the description of this new generator (don't forget to delete the old generator file):
@@ -127,13 +127,13 @@ And let's execute our generator:
$ rails generate initializer foo
</shell>
-We can see that now a initializer named foo was created at +config/initializers/foo.rb+ with the contents of our template. That means that copy_file copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+.
+We can see that now a initializer named foo was created at +config/initializers/foo.rb+ with the contents of our template. That means that +copy_file+ copied a file in our source root to the destination path we gave. The method +file_name+ is automatically created when we inherit from +Rails::Generators::NamedBase+.
-h3. Generators lookup
+h3. Generators Lookup
-With our first generator created, we must discuss briefly generators lookup. The way Rails finds generators is exactly the same way Ruby find files, i.e. using +$LOAD_PATHS+.
+Now that we've created our first generator, we need to briefly discuss generator lookup. The way Rails finds generators is exactly the same way Ruby find files, i.e. using +$LOAD_PATHS+.
-For instance, when you say +rails g initializer foo+, rails knows you want to invoke the initializer generator and then search for the following generators in the $LOAD_PATHS:
+For instance, when you say +rails generate initializer foo+, Rails knows you want to invoke the initializer generator and then search for the following generators in the $LOAD_PATHS:
<shell>
rails/generators/initializer/initializer_generator.rb
@@ -144,7 +144,7 @@ generators/initializer_generator.rb
If none of them is found, it raises an error message.
-h3. Customizing your workflow
+h3. Customizing Your Workflow
Rails generators are flexible enough to let you customize your scaffold the way you want. In your +config/application.rb+ there is a section just for generators:
@@ -156,7 +156,7 @@ config.generators do |g|
end
</ruby>
-Before we customize our workflow, let's first see how our scaffold looks like:
+Before we customize our workflow, let's first see what our scaffold looks like:
<shell>
$ rails generate scaffold User name:string
@@ -186,7 +186,7 @@ $ rails generate scaffold User name:string
create public/stylesheets/scaffold.css
</shell>
-Looking at this output, is easy to understand how generators work on Rails 3.0 and above. The scaffold generator actually doesn't generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
+Looking at this output, it's easy to understand how generators work on Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication.
Our first customization on the workflow will be to stop generating stylesheets and test fixtures on scaffold. We can achieve that by changing our application to the following:
@@ -199,15 +199,15 @@ config.generators do |g|
end
</ruby>
-If we generate another resource on scaffold, we can notice that neither stylesheets nor fixtures are created anymore. If you want to customize it further, for example to use +Datamapper+ and +Rspec+ instead of +ActiveRecord+ and +TestUnit+, is just a matter of adding their gems to your application and configuring your generators.
+If we generate another resource on scaffold, we can notice that neither stylesheets nor fixtures are created anymore. If you want to customize it further, for example to use +Datamapper+ and +RSpec+ instead of +ActiveRecord+ and +TestUnit+, it's just a matter of adding their gems to your application and configuring your generators.
-To show that, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator:
+To demonstrate this, we are going to create a new helper generator that simply adds some instance variable readers. First, we create a generator:
<shell>
$ rails generate generator my_helper
</shell>
-After that, we can delete both templates directory and the +source_root+ class method from our new generators, because we are not going to need them. So our new generator looks like the following:
+After that, we can delete both the +templates+ directory and the +source_root+ class method from our new generators, because we are not going to need them. So our new generator looks like the following:
<ruby>
class MyHelperGenerator < Rails::Generators::NamedBase
@@ -227,7 +227,7 @@ We can try out our new generator by creating a helper for users:
$ rails generate my_helper users
</shell>
-And it will generate the following helper file in app/helpers:
+And it will generate the following helper file in +app/helpers+:
<ruby>
module UsersHelper
@@ -258,7 +258,7 @@ $ rails generate scaffold Post body:text
We can notice on the output that our new helper was invoked instead of the Rails default. However one thing is missing, which is tests for our new generator and to do that, we are going to reuse old helpers test generators.
-Since Rails 3.0, this is easy to do due to the hooks concept. Our new helper does not need to be focused in one specific test framework, it can simply provide a hook and a test framework just need to implement this hook in order to be compatible.
+Since Rails 3.0, this is easy to do due to the hooks concept. Our new helper does not need to be focused in one specific test framework, it can simply provide a hook and a test framework just needs to implement this hook in order to be compatible.
To do that, we can change your generator to the following:
@@ -276,7 +276,7 @@ end
end
</ruby>
-Now, when the helper generator is invoked and let's say test unit is configured as test framework, it will try to invoke both +MyHelper::Generators::TestUnitGenerator+ and +TestUnit::Generators::MyHelperGenerator+. Since none of those are defined, we can tell our generator to invoke +TestUnit::Generators::HelperGenerator+ instead, which is defined since it's a Rails generator. To do that, we just need to add:
+Now, when the helper generator is invoked and TestUnit is configured as the test framework, it will try to invoke both +MyHelper::Generators::TestUnitGenerator+ and +TestUnit::Generators::MyHelperGenerator+. Since none of those are defined, we can tell our generator to invoke +TestUnit::Generators::HelperGenerator+ instead, which is defined since it's a Rails generator. To do that, we just need to add:
<ruby>
# Search for :helper instead of :my_helper
@@ -285,11 +285,11 @@ Now, when the helper generator is invoked and let's say test unit is configured
And now you can re-run scaffold for another resource and see it generating tests as well!
-h3. Customizing your workflow by changing generators templates
+h3. Customizing Your Workflow by Changing Generators Templates
In the step above, we simply wanted to add a line to the generated helper, without adding any extra functionality. There is a simpler way to do that, and it's by replacing the templates of already existing generators.
-In Rails 3.0 and above, generators does not look only in the source root for templates, they also search for templates in other paths. And one of them is inside +RAILS_APP/lib/templates+. Since we want to customize +Rails::Generators::HelperGenerator+, we can do that by simple making a template copy inside +RAILS_APP/lib/templates/rails/helper+ with the name +helper.rb+. So let's create such file with the following content:
+In Rails 3.0 and above, generators don't just look in the source root for templates, they also search for templates in other paths. And one of them is inside +RAILS_APP/lib/templates+. Since we want to customize +Rails::Generators::HelperGenerator+, we can do that by simply making a template copy inside +RAILS_APP/lib/templates/rails/helper+ with the name +helper.rb+. So let's create that file with the following content:
<erb>
module <%= class_name %>Helper
@@ -310,9 +310,9 @@ end
If you generate another resource, you can see that we got exactly the same result! This is useful if you want to customize your scaffold templates and/or layout by just creating +edit.html.erb+, +index.html.erb+ and so on inside +RAILS_APP/lib/templates/erb/scaffold+.
-h3. Adding generators fallbacks
+h3. Adding Generators Fallbacks
-One last feature about generators which is quite useful for plugin generators is fallbacks. For example, imagine that you want to add a feature on top of TestUnit test framework, like "shoulda":http://github.com/thoughtbot/shoulda does. Since TestUnit already implements all generators required by Rails and shoulda just want to overwrite part of it, there is no need for shoulda to reimplement some generators again, they can simply tell Rails to use a +TestUnit+ generator if none was found under +Shoulda+ namespace.
+One last feature about generators which is quite useful for plugin generators is fallbacks. For example, imagine that you want to add a feature on top of TestUnit test framework, like "shoulda":http://github.com/thoughtbot/shoulda does. Since TestUnit already implements all generators required by Rails and shoulda just wants to overwrite part of it, there is no need for shoulda to reimplement some generators again, it can simply tell Rails to use a +TestUnit+ generator if none was found under the +Shoulda+ namespace.
We can easily simulate this behavior by changing our +config/application.rb+ once again:
@@ -324,11 +324,11 @@ config.generators do |g|
g.stylesheets false
# Add a fallback!
- g.fallbacks[:should] = :test_unit
+ g.fallbacks[:shoulda] = :test_unit
end
</ruby>
-Now, if create a Comment scaffold, you will see that shoulda generators are being invoked, and at the end, they are just falling back to test unit generators:
+Now, if you create a Comment scaffold, you will see that the shoulda generators are being invoked, and at the end, they are just falling back to test unit generators:
<shell>
$ rails generate scaffold Comment body:text
@@ -357,7 +357,7 @@ $ rails generate scaffold Comment body:text
create test/unit/helpers/comments_helper_test.rb
</shell>
-Such tool allows your generators to have single responsibility, increasing the code reuse and reducing the amount of duplication.
+Fallbacks allow your generators to have a single responsibility, increasing code reuse and reducing the amount of duplication.
h3. Changelog
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index 7b4f1ca471..6abb3ed9f4 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -227,7 +227,7 @@ development:
timeout: 5000
</yaml>
-NOTE: In this guide we are using an SQLite3 database for data storage, this is because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems, if you are using a database in a production environment, Rails most likely has an adapter for it.
+NOTE: In this guide we are using an SQLite3 database for data storage, because it is a zero configuration database that just works. Rails also supports MySQL and PostgreSQL "out of the box", and has plugins for many database systems. If you are using a database in a production environment Rails most likely has an adapter for it.
h5. Configuring a MySQL Database
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index 5a715cf9f7..be077fcd2f 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -47,7 +47,7 @@ Ruby on Rails Guides
<p>This guide covers how you can use Active Record migrations to alter your database in a structured and organized manner.</p>
<% end %>
-<%= guide("Active Record Validations and Callbacks", 'activerecord_validations_callbacks.html') do %>
+<%= guide("Active Record Validations and Callbacks", 'active_record_validations_callbacks.html') do %>
<p>This guide covers how you can use Active Record validations and callbacks.</p>
<% end %>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index bc3a2d0f0b..e458413b35 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -148,7 +148,7 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This
* mime-types-1.16.gem
* polyglot-0.3.1.gem
* rack-1.1.0.gem
-* rack-mount-0.6.4.gem
+* rack-mount-0.6.5.gem
* rack-test-0.5.4.gem
* rails-3.0.0.beta4.gem
* railties-3.0.0.beta4.gem
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index f1727166ba..501d8fef6d 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -50,7 +50,7 @@
<dd><a href="getting_started.html">Getting Started with Rails</a></dd>
<dt>Models</dt>
<dd><a href="migrations.html">Rails Database Migrations</a></dd>
- <dd><a href="activerecord_validations_callbacks.html">Active Record Validations and Callbacks</a></dd>
+ <dd><a href="active_record_validations_callbacks.html">Active Record Validations and Callbacks</a></dd>
<dd><a href="association_basics.html">Active Record Associations</a></dd>
<dd><a href="active_record_querying.html">Active Record Query Interface</a></dd>
<dt>Views</dt>
diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile
index 1af6f56957..1bf9cfec33 100644
--- a/railties/guides/source/rails_application_templates.textile
+++ b/railties/guides/source/rails_application_templates.textile
@@ -51,11 +51,11 @@ h4. gem(name, options = {})
Adds a +config.gem+ entry for the supplied gem to the generated application’s +config/environment.rb+.
-For example, if your application depends on the gems +bj+ and +hpricot+ :
+For example, if your application depends on the gems +bj+ and +nokogiri+ :
<ruby>
gem "bj"
-gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
+gem "nokogiri"
</ruby>
Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too :
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 54af42a03f..9ff06856c3 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -32,7 +32,7 @@ the request is dispatched to the +patients+ controller's +show+ action with <tt>
h4. Generating URLs from Code
-You can also generate routes. If your application contains this code:
+You can also generate URLs. If your application contains this code:
<ruby>
@patient = Patient.find(17)
@@ -308,7 +308,7 @@ You are not limited to the seven routes that RESTful routing creates by default.
h5. Adding Member Routes
-To add a member route, just add +member+ block into resource block:
+To add a member route, just add a +member+ block into the resource block:
<ruby>
resources :photos do
@@ -318,9 +318,9 @@ resources :photos do
end
</ruby>
-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+. 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+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also passing +:on+ to a route.
+Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block:
<ruby>
resources :photos do
@@ -340,9 +340,9 @@ resources :photos do
end
</ruby>
-This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
-Just as with member routes, you can pass +:on+ to a route.
+Just as with member routes, you can pass +:on+ to a route:
<ruby>
resources :photos do
@@ -384,7 +384,7 @@ An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of
h4. Static Segments
-You can specify static segments when creating a route.
+You can specify static segments when creating a route:
<ruby>
match ':controller/:action/:id/with_user/:user_id'
@@ -444,6 +444,21 @@ This route would match URLs such as +/photo/A12345+. You can more succinctly exp
match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/
</ruby>
++:constraints+ takes regular expression. However note that regexp anchors can't be used within constraints. For example following route will not work:
+
+<ruby>
+match '/:id' => 'posts#show', :constraints => {:id => /^\d/}
+</ruby>
+
+However, note that you don't need to use anchors because all routes are anchored at the start.
+
+For example, the following routes would allow for +posts+ with +to_param+ values like +1-hello-world+ that always begin with a number and +users+ with +to_param+ values like +david+ that never begin with a number to share the root namespace:
+
+<ruby>
+match '/:id' => 'posts#show', :constraints => { :id => /\d.+/ }
+match '/:username' => 'users#show'
+</ruby>
+
h4. Request-Based Constraints
You can also constrain a route based on any method on the <a href="action_controller_overview.html#the-request-object">Request</a> object that returns a +String+.
@@ -575,7 +590,7 @@ resources :photos, :constraints => {:id => /[A-Z][A-Z][0-9]+/}
This declaration constrains the +:id+ parameter to match the supplied regular expression. So, in this case, the router would no longer match +/photos/1+ to this route. Instead, +/photos/RR27+ would match.
-You can specify a single constraint to a apply to a number of routes by using the block form:
+You can specify a single constraint to apply to a number of routes by using the block form:
<ruby>
constraints(:id => /[A-Z][A-Z][0-9]+/) do
diff --git a/railties/guides/w3c_validator.rb b/railties/guides/w3c_validator.rb
index b55645a7a9..4da48bf3fb 100644
--- a/railties/guides/w3c_validator.rb
+++ b/railties/guides/w3c_validator.rb
@@ -35,23 +35,27 @@ module RailsGuides
def validate
validator = MarkupValidator.new
+ STDOUT.sync = true
+ errors_on_guides = {}
guides_to_validate.each do |f|
- puts "Validating #{f}"
results = validator.validate_file(f)
- if !results.validity
- puts "#{f} FAILED W3C validation with #{results.errors.size} error(s):"
- results.errors.each do |error|
- puts error.to_s
- end
+ if results.validity
+ print "."
+ else
+ print "E"
+ errors_on_guides[f] = results.errors
end
end
+
+ show_results(errors_on_guides)
end
private
def guides_to_validate
guides = Dir["./guides/output/*.html"]
+ guides.delete("./guides/output/layout.html")
ENV.key?('ONLY') ? select_only(guides) : guides
end
@@ -61,6 +65,26 @@ module RailsGuides
prefixes.any? {|p| guide.start_with?("./guides/output/#{p}")}
end
end
+
+ def show_results(error_list)
+ if error_list.size == 0
+ puts "\n\nAll checked guides validate OK!"
+ else
+ error_summary = error_detail = ""
+
+ error_list.each_pair do |name, errors|
+ error_summary += "\n #{name}"
+ error_detail += "\n\n #{name} has #{errors.size} validation error(s):\n"
+ errors.each do |error|
+ error_detail += "\n "+error.to_s.gsub("\n", "")
+ end
+ end
+
+ puts "\n\nThere are #{error_list.size} guides with validation errors:\n" + error_summary
+ puts "\nHere are the detailed errors for each guide:" + error_detail
+ end
+ end
+
end
end
diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb
index ed66f493e6..bbf28a8c08 100644
--- a/railties/lib/rails.rb
+++ b/railties/lib/rails.rb
@@ -9,7 +9,6 @@ require 'active_support/core_ext/logger'
require 'rails/application'
require 'rails/version'
require 'rails/deprecation'
-require 'rails/log_subscriber'
require 'active_support/railtie'
require 'action_dispatch/railtie'
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 85ae8cbbb1..4a7ed2d028 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/hash/reverse_merge'
+require 'active_support/file_update_checker'
require 'fileutils'
require 'rails/plugin'
require 'rails/engine'
@@ -27,7 +28,7 @@ module Rails
# Besides providing the same configuration as Rails::Engine and Rails::Railtie,
# the application object has several specific configurations, for example
# "allow_concurrency", "cache_classes", "consider_all_requests_local", "filter_parameters",
- # "logger", "reload_engines", "reload_plugins" and so forth.
+ # "logger", "reload_plugins" and so forth.
#
# Check Rails::Application::Configuration to see them all.
#
@@ -46,7 +47,6 @@ module Rails
autoload :Configuration, 'rails/application/configuration'
autoload :Finisher, 'rails/application/finisher'
autoload :Railties, 'rails/application/railties'
- autoload :RoutesReloader, 'rails/application/routes_reloader'
class << self
private :new
@@ -67,7 +67,7 @@ module Rails
raise "You cannot have more than one Rails::Application" if Rails.application
super
Rails.application = base.instance
- Rails.application.add_lib_to_load_paths!
+ Rails.application.add_lib_to_load_path!
ActiveSupport.run_load_hooks(:before_configuration, base.instance)
end
@@ -84,17 +84,30 @@ module Rails
delegate :middleware, :to => :config
- def add_lib_to_load_paths!
+ # This method is called just after an application inherits from Rails::Application,
+ # allowing the developer to load classes in lib and use them during application
+ # configuration.
+ #
+ # class MyApplication < Rails::Application
+ # require "my_backend" # in lib/my_backend
+ # config.i18n.backend = MyBackend
+ # end
+ #
+ # Notice this method takes into consideration the default root path. So if you
+ # are changing config.root inside your application definition or having a custom
+ # Rails application, you will need to add lib to $LOAD_PATH on your own in case
+ # you need to load files in lib/ during the application configuration as well.
+ def add_lib_to_load_path! #:nodoc:
path = config.root.join('lib').to_s
$LOAD_PATH.unshift(path) if File.exists?(path)
end
- def require_environment!
+ def require_environment! #:nodoc:
environment = paths.config.environment.to_a.first
require environment if environment
end
- def eager_load!
+ def eager_load! #:nodoc:
railties.all(&:eager_load!)
super
end
@@ -108,11 +121,18 @@ module Rails
end
def routes_reloader
- @routes_reloader ||= RoutesReloader.new
+ @routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! }
end
def reload_routes!
- routes_reloader.reload!
+ routes = Rails::Application.routes
+ routes.disable_clear_and_finalize = true
+
+ routes.clear!
+ routes_reloader.paths.each { |path| load(path) }
+ ActiveSupport.on_load(:action_controller) { routes.finalize! }
+ ensure
+ routes.disable_clear_and_finalize = false
end
def initialize!
@@ -140,6 +160,7 @@ module Rails
config.middleware.build(routes)
end
end
+ alias :build_middleware_stack :app
def call(env)
app.call(env.reverse_merge!(env_defaults))
@@ -196,10 +217,5 @@ module Rails
def initialize_generators
require "rails/generators"
end
-
- # Application is always reloadable when config.cache_classes is false.
- def reloadable?(app)
- true
- end
end
end
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 0a435f0f36..44e26b5713 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -1,4 +1,5 @@
require "active_support/notifications"
+require "active_support/descendants_tracker"
module Rails
class Application
@@ -55,6 +56,7 @@ module Rails
initializer :set_clear_dependencies_hook do
unless config.cache_classes
ActionDispatch::Callbacks.after do
+ ActiveSupport::DescendantsTracker.clear
ActiveSupport::Dependencies.clear
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 25e54e9dce..465851c0e6 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -10,7 +10,7 @@ module Rails
attr_accessor :allow_concurrency, :cache_classes, :cache_store,
:encoding, :consider_all_requests_local, :dependency_loading,
:filter_parameters, :log_level, :logger, :middleware,
- :plugins, :preload_frameworks, :reload_engines, :reload_plugins,
+ :plugins, :preload_frameworks, :reload_plugins,
:secret_token, :serve_static_assets, :session_options,
:time_zone, :whiny_nils
@@ -59,7 +59,7 @@ module Rails
if File.exists?("#{root}/test/mocks/#{Rails.env}")
ActiveSupport::Deprecation.warn "\"Rails.root/test/mocks/#{Rails.env}\" won't be added " <<
"automatically to load paths anymore in future releases"
- paths.mocks_path "test/mocks", :load_path => true, :glob => Rails.env
+ paths.mocks_path "test/mocks", :autoload => true, :glob => Rails.env
end
paths
@@ -110,7 +110,7 @@ module Rails
def colorize_logging=(val)
@colorize_logging = val
- Rails::LogSubscriber.colorize_logging = val
+ ActiveSupport::LogSubscriber.colorize_logging = val
self.generators.colorize_logging = val
end
diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb
index d7ff489336..855467227b 100644
--- a/railties/lib/rails/application/finisher.rb
+++ b/railties/lib/rails/application/finisher.rb
@@ -7,14 +7,14 @@ module Rails
config.generators.templates.unshift(*paths.lib.templates.to_a)
end
- initializer :ensure_load_once_paths_as_subset do
- extra = ActiveSupport::Dependencies.load_once_paths -
- ActiveSupport::Dependencies.load_paths
+ initializer :ensure_autoload_once_paths_as_subset do
+ extra = ActiveSupport::Dependencies.autoload_once_paths -
+ ActiveSupport::Dependencies.autoload_paths
unless extra.empty?
abort <<-end_error
- load_once_paths must be a subset of the load_paths.
- Extra items in load_once_paths: #{extra * ','}
+ autoload_once_paths must be a subset of the autoload_paths.
+ Extra items in autoload_once_paths: #{extra * ','}
end_error
end
end
@@ -32,7 +32,7 @@ module Rails
end
initializer :build_middleware_stack do
- app
+ build_middleware_stack
end
initializer :eager_load! do
diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb
deleted file mode 100644
index a2b3622df8..0000000000
--- a/railties/lib/rails/application/routes_reloader.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-module Rails
- class Application
- class RoutesReloader
- attr_reader :paths
-
- def initialize
- @paths, @last_change_at = [], nil
- end
-
- def changed_at
- routes_changed_at = nil
-
- paths.each do |path|
- config_changed_at = File.stat(path).mtime
-
- if routes_changed_at.nil? || config_changed_at > routes_changed_at
- routes_changed_at = config_changed_at
- end
- end
-
- routes_changed_at
- end
-
- def reload!
- routes = Rails::Application.routes
- routes.disable_clear_and_finalize = true
-
- routes.clear!
- paths.each { |path| load(path) }
- ActiveSupport.on_load(:action_controller) { routes.finalize! }
-
- nil
- ensure
- routes.disable_clear_and_finalize = false
- end
-
- def reload_if_changed
- current_change_at = changed_at
- if @last_change_at != current_change_at
- @last_change_at = current_change_at
- reload!
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb
index 1b3eef504a..1b3eef504a 100755..100644
--- a/railties/lib/rails/commands/generate.rb
+++ b/railties/lib/rails/commands/generate.rb
diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb
index 278548558e..b97ff086b6 100644
--- a/railties/lib/rails/commands/runner.rb
+++ b/railties/lib/rails/commands/runner.rb
@@ -44,6 +44,7 @@ begin
$stderr.puts "Run '#{$0} -h' for help."
exit 1
elsif File.exist?(code_or_file)
+ $0 = code_or_file
eval(File.read(code_or_file), nil, code_or_file)
else
eval(code_or_file)
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 3f74cd49fc..9d9dd48ea9 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -9,7 +9,7 @@ module Rails
args, options = args.dup, {}
opt_parser = OptionParser.new do |opts|
- opts.banner = "Usage: rails server [options]"
+ opts.banner = "Usage: rails server [mongrel, thin, etc] [options]"
opts.on("-p", "--port=port", Integer,
"Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
opts.on("-b", "--binding=ip", String,
@@ -83,7 +83,8 @@ module Rails
:environment => (ENV['RAILS_ENV'] || "development").dup,
:daemonize => false,
:debugger => false,
- :pid => "tmp/pids/server.pid"
+ :pid => File.expand_path("tmp/pids/server.pid"),
+ :config => File.expand_path("config.ru")
})
end
end
diff --git a/railties/lib/rails/console/app.rb b/railties/lib/rails/console/app.rb
index 4a7701081b..9d9763699d 100644
--- a/railties/lib/rails/console/app.rb
+++ b/railties/lib/rails/console/app.rb
@@ -30,5 +30,3 @@ def reload!(print=true)
ActionDispatch::Callbacks.new(Proc.new {}, false).call({})
true
end
-
-reload!(false)
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index cdb00a4eff..ee3e3ba040 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -32,14 +32,14 @@ module Rails
# == Configuration
#
# Besides the Railtie configuration which is shared across the application, in a
- # Rails::Engine you can access load_paths, eager_load_paths and load_once_paths,
+ # Rails::Engine you can access autoload_paths, eager_load_paths and autoload_once_paths,
# which differently from a Railtie, are scoped to the current Engine.
#
# Example:
#
# class MyEngine < Rails::Engine
# # Add a load path for this specific Engine
- # config.load_paths << File.expand_path("../lib/some/path", __FILE__)
+ # config.autoload_paths << File.expand_path("../lib/some/path", __FILE__)
#
# initializer "my_engine.add_middleware" do |app|
# app.middleware.use MyEngine::Middleware
@@ -142,7 +142,7 @@ module Rails
# Add configured load paths to ruby load paths and remove duplicates.
initializer :set_load_path, :before => :bootstrap_hook do
- config.load_paths.reverse_each do |path|
+ _all_load_paths.reverse_each do |path|
$LOAD_PATH.unshift(path) if File.directory?(path)
end
$LOAD_PATH.uniq!
@@ -154,17 +154,13 @@ module Rails
# This needs to be an initializer, since it needs to run once
# per engine and get the engine as a block parameter
initializer :set_autoload_paths, :before => :bootstrap_hook do |app|
- ActiveSupport::Dependencies.load_paths.unshift(*config.load_paths)
-
- if reloadable?(app)
- ActiveSupport::Dependencies.load_once_paths.unshift(*config.load_once_paths)
- else
- ActiveSupport::Dependencies.load_once_paths.unshift(*config.load_paths)
- end
+ ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
+ ActiveSupport::Dependencies.autoload_once_paths.unshift(*config.autoload_once_paths)
# Freeze so future modifications will fail rather than do nothing mysteriously
- config.load_paths.freeze
- config.load_once_paths.freeze
+ config.autoload_paths.freeze
+ config.eager_load_paths.freeze
+ config.autoload_once_paths.freeze
end
initializer :add_routing_paths do |app|
@@ -195,7 +191,6 @@ module Rails
ActiveSupport.on_load(:action_controller) do
prepend_view_path(views)
end
-
ActiveSupport.on_load(:action_mailer) do
prepend_view_path(views)
end
@@ -214,8 +209,12 @@ module Rails
protected
- def reloadable?(app)
- app.config.reload_engines
+ def _all_autoload_paths
+ @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
+ end
+
+ def _all_load_paths
+ @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq
end
end
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 446fe0bda9..2f465670cf 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -4,7 +4,7 @@ module Rails
class Engine
class Configuration < ::Rails::Railtie::Configuration
attr_reader :root
- attr_writer :eager_load_paths, :load_once_paths, :load_paths
+ attr_writer :eager_load_paths, :autoload_once_paths, :autoload_paths
def initialize(root=nil)
super()
@@ -41,12 +41,12 @@ module Rails
@eager_load_paths ||= paths.eager_load
end
- def load_once_paths
- @load_once_paths ||= paths.load_once
+ def autoload_once_paths
+ @autoload_once_paths ||= paths.autoload_once
end
- def load_paths
- @load_paths ||= paths.load_paths
+ def autoload_paths
+ @autoload_paths ||= paths.autoload_paths
end
end
end
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 41aecea355..8794392a7d 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -50,7 +50,6 @@ module Rails
:performance_tool => nil,
:resource_controller => :controller,
:scaffold_controller => :scaffold_controller,
- :singleton => false,
:stylesheets => true,
:test_framework => nil,
:template_engine => :erb
@@ -334,4 +333,4 @@ module Rails
paths
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index 7af329bbf0..199afbdc30 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -275,7 +275,7 @@ module Rails
#
def route(routing_code)
log :route, routing_code
- sentinel = "routes.draw do |map|"
+ sentinel = /\.routes\.draw do(\s*\|map\|)?\s*$/
in_root do
inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false }
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index bd2260fc29..67a9a6030d 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -3,7 +3,7 @@ begin
rescue LoadError
puts "Thor is not available.\nIf you ran this command from a git checkout " \
"of Rails, please make sure thor is installed,\nand run this command " \
- "as `ruby /path/to/rails myapp --dev`"
+ "as `ruby /path/to/rails new myapp --dev`"
exit
end
diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
index 2db7f7bbf3..b2c8d7051b 100644
--- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb
@@ -8,17 +8,12 @@ module Erb
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
- class_option :singleton, :type => :boolean, :desc => "Supply to skip index view"
-
def create_root_folder
empty_directory File.join("app/views", controller_file_path)
end
def copy_view_files
- views = available_views
- views.delete("index") if options[:singleton]
-
- views.each do |view|
+ available_views.each do |view|
filename = filename_with_extensions(view)
template filename, File.join("app/views", controller_file_path, filename)
end
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
index 9b83207b3f..d12b2ff0e5 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb
@@ -1,10 +1,10 @@
-<%%= form_for(@<%= singular_name %>) do |f| %>
- <%% if @<%= singular_name %>.errors.any? %>
+<%%= form_for(@<%= singular_table_name %>) do |f| %>
+ <%% if @<%= singular_table_name %>.errors.any? %>
<div id="error_explanation">
- <h2><%%= pluralize(@<%= singular_name %>.errors.count, "error") %> prohibited this <%= singular_name %> from being saved:</h2>
+ <h2><%%= pluralize(@<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
<ul>
- <%% @<%= singular_name %>.errors.full_messages.each do |msg| %>
+ <%% @<%= singular_table_name %>.errors.full_messages.each do |msg| %>
<li><%%= msg %></li>
<%% end %>
</ul>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
index 5bc507ffc8..e58b9fbd08 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb
@@ -1,6 +1,6 @@
-<h1>Editing <%= singular_name %></h1>
+<h1>Editing <%= singular_table_name %></h1>
<%%= render 'form' %>
-<%%= link_to 'Show', @<%= singular_name %> %> |
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Show', @<%= singular_table_name %> %> |
+<%%= link_to 'Back', <%= index_helper %>_path %>
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 d30d306d42..4c46db4d67 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb
@@ -1,4 +1,4 @@
-<h1>Listing <%= plural_name %></h1>
+<h1>Listing <%= plural_table_name %></h1>
<table>
<tr>
@@ -10,18 +10,18 @@
<th></th>
</tr>
-<%% @<%= plural_name %>.each do |<%= singular_name %>| %>
+<%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
<tr>
<% for attribute in attributes -%>
- <td><%%= <%= singular_name %>.<%= attribute.name %> %></td>
+ <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
<% end -%>
- <td><%%= link_to 'Show', <%= singular_name %> %></td>
- <td><%%= link_to 'Edit', edit_<%= singular_name %>_path(<%= singular_name %>) %></td>
- <td><%%= link_to 'Destroy', <%= singular_name %>, :confirm => 'Are you sure?', :method => :delete %></td>
+ <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 %>, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<%% end %>
</table>
<br />
-<%%= link_to 'New <%= human_name %>', new_<%= singular_name %>_path %>
+<%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
index 9a1c489331..02ae4d015e 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb
@@ -1,5 +1,5 @@
-<h1>New <%= singular_name %></h1>
+<h1>New <%= singular_table_name %></h1>
<%%= render 'form' %>
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
index 6b3518717a..c0e5ccff1e 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb
@@ -3,10 +3,10 @@
<% for attribute in attributes -%>
<p>
<b><%= attribute.human_name %>:</b>
- <%%= @<%= singular_name %>.<%= attribute.name %> %>
+ <%%= @<%= singular_table_name %>.<%= attribute.name %> %>
</p>
<% end -%>
-<%%= link_to 'Edit', edit_<%= singular_name %>_path(@<%= singular_name %>) %> |
-<%%= link_to 'Back', <%= plural_name %>_path %>
+<%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> |
+<%%= link_to 'Back', <%= index_helper %>_path %>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 8d1dfbd947..44f831e6f3 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -46,6 +46,30 @@ module Rails
end
end
+ def uncountable?
+ singular_name == plural_name
+ end
+
+ def index_helper
+ uncountable? ? "#{plural_table_name}_index" : plural_table_name
+ end
+
+ def singular_table_name
+ @singular_table_name ||= table_name.singularize
+ end
+
+ def plural_table_name
+ @plural_table_name ||= table_name.pluralize
+ end
+
+ def plural_file_name
+ @plural_file_name ||= file_name.pluralize
+ end
+
+ def route_url
+ @route_url ||= class_path.collect{|dname| "/" + dname }.join('') + "/" + plural_file_name
+ end
+
# Tries to retrieve the application name or simple return application.
def application_name
if defined?(Rails) && Rails.application
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 0b922a89c0..a108968b97 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -32,8 +32,9 @@ gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= req
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
-# Bundle gems for certain environments:
-# gem 'rspec', :group => :test
-# group :test do
+# Bundle gems for the local environment. Make sure to
+# put test-only gems in this group so their generators
+# and rake tasks are available in development mode:
+# group :development, :test do
# gem 'webrat'
# end
diff --git a/railties/lib/rails/generators/rails/app/templates/README b/railties/lib/rails/generators/rails/app/templates/README
index 9ec6db6d71..e2764dee03 100644
--- a/railties/lib/rails/generators/rails/app/templates/README
+++ b/railties/lib/rails/generators/rails/app/templates/README
@@ -37,7 +37,7 @@ link:files/vendor/rails/actionpack/README.html.
3. Go to http://localhost:3000/ and you'll see:
"Welcome aboard: You're riding the Rails!"
-4. Follow the guidelines to start developing your application. You can find
+4. Follow the guidelines to start developing your application. You can find
the following resources handy:
* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
@@ -71,13 +71,13 @@ The result will be a message in your log file along the lines of:
More information on how to use the logger is at http://www.ruby-doc.org/core/
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
+Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
several books available online as well:
* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-These two books will bring you up to speed on the Ruby language and also on
+These two books will bring you up to speed on the Ruby language and also on
programming in general.
@@ -199,7 +199,7 @@ app/controllers
ApplicationController which itself descends from ActionController::Base.
app/models
- Holds models that should be named like post.rb. Models descend from
+ Holds models that should be named like post.rb. Models descend from
ActiveRecord::Base by default.
app/views
diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile
index 13f1f9fa41..13f1f9fa41 100755..100644
--- a/railties/lib/rails/generators/rails/app/templates/Rakefile
+++ b/railties/lib/rails/generators/rails/app/templates/Rakefile
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 0066e2b0c2..031466cb86 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -22,7 +22,7 @@ module <%= app_const_base %>
# -- all .rb files in that directory are automatically loaded.
# Add additional load paths for your own custom dirs
- # config.load_paths += %W( #{config.root}/extras )
+ # config.autoload_paths += %W( #{config.root}/extras )
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
index 2784a949fb..df5ef33064 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml
@@ -32,40 +32,52 @@
# please refer to the latest documents at http://rubyforge.org/docman/?group_id=2361
development:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,4] %>_dev
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,4] %>_dev
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false
test:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,4] %>_tst
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,4] %>_tst
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false
production:
- adapter: ibm_db
- username: db2inst1
+ adapter: ibm_db
+ username: db2inst1
password:
- database: <%= app_name[0,8] %>
- #schema: db2inst1
- #host: localhost
- #port: 50000
- #account: my_account
- #app_user: my_app_user
+ database: <%= app_name[0,8] %>
+ #schema: db2inst1
+ #host: localhost
+ #port: 50000
+ #account: my_account
+ #app_user: my_app_user
#application: my_application
#workstation: my_workstation
+ #security: SSL
+ #timeout: 10
+ #authentication: SERVER
+ #parameterized: false \ No newline at end of file
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
index 6bf2f7b1fd..ffc8a0a8cb 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml
@@ -52,7 +52,7 @@ production:
database: <%= app_name %>_production
pool: 5
username: root
- password:
+ password:
<% if mysql_socket -%>
socket: <%= mysql_socket %>
<% else -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index a1883f6256..f99ee937f3 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -36,4 +36,4 @@ production:
adapter: oracle
database: <%= app_name %>_production
username: <%= app_name %>
- password:
+ password:
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index f0e917dd96..99758dfcf7 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -16,4 +16,7 @@
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
end
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 b9fb13b640..500fc4d860 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
@@ -43,4 +43,7 @@
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
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 beb28e2229..26cdef071a 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
@@ -29,4 +29,7 @@
# 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
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
index d531b8bb82..9e8b0131f8 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
@@ -1,6 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Add new inflection rules using the following format
+# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
index c2fa31aadb..22aa576f5d 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/secret_token.rb.tt
@@ -2,6 +2,6 @@
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
-# Make sure the secret is at least 30 characters and all random,
+# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
Rails.application.config.secret_token = '<%= app_secret %>'
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 d6c0365c04..d42cf3bfdf 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb
@@ -1,4 +1,4 @@
-<%= app_const %>.routes.draw do |map|
+<%= app_const %>.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
index c5fa02ae35..4283ed8982 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
+++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/rails.js
@@ -1,87 +1,148 @@
-document.observe("dom:loaded", function() {
+(function() {
+ // Technique from Juriy Zaytsev
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+ function isEventSupported(eventName) {
+ var el = document.createElement('div');
+ eventName = 'on' + eventName;
+ var isSupported = (eventName in el);
+ if (!isSupported) {
+ el.setAttribute(eventName, 'return;');
+ isSupported = typeof el[eventName] == 'function';
+ }
+ el = null;
+ return isSupported;
+ }
+
+ function isForm(element) {
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
+ }
+
+ function isInput(element) {
+ if (Object.isElement(element)) {
+ var name = element.nodeName.toUpperCase()
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
+ }
+ else return false
+ }
+
+ var submitBubbles = isEventSupported('submit'),
+ changeBubbles = isEventSupported('change')
+
+ if (!submitBubbles || !changeBubbles) {
+ // augment the Event.Handler class to observe custom events when needed
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
+ function(init, element, eventName, selector, callback) {
+ init(element, eventName, selector, callback)
+ // is the handler being attached to an element that doesn't support this event?
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
+ // "submit" => "emulated:submit"
+ this.eventName = 'emulated:' + this.eventName
+ }
+ }
+ )
+ }
+
+ if (!submitBubbles) {
+ // discover forms on the page by observing focus events which always bubble
+ document.on('focusin', 'form', function(focusEvent, form) {
+ // special handler for the real "submit" event (one-time operation)
+ if (!form.retrieve('emulated:submit')) {
+ form.on('submit', function(submitEvent) {
+ var emulated = form.fire('emulated:submit', submitEvent, true)
+ // if custom event received preventDefault, cancel the real one too
+ if (emulated.returnValue === false) submitEvent.preventDefault()
+ })
+ form.store('emulated:submit', true)
+ }
+ })
+ }
+
+ if (!changeBubbles) {
+ // discover form inputs on the page
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
+ // special handler for real "change" events
+ if (!input.retrieve('emulated:change')) {
+ input.on('change', function(changeEvent) {
+ input.fire('emulated:change', changeEvent, true)
+ })
+ input.store('emulated:change', true)
+ }
+ })
+ }
+
function handleRemote(element) {
var method, url, params;
+ var event = element.fire("ajax:before");
+ if (event.stopped) return false;
+
if (element.tagName.toLowerCase() === 'form') {
method = element.readAttribute('method') || 'post';
url = element.readAttribute('action');
- params = element.serialize(true);
+ params = element.serialize();
} else {
method = element.readAttribute('data-method') || 'get';
url = element.readAttribute('href');
params = {};
}
- var event = element.fire("ajax:before");
- if (event.stopped) return false;
-
new Ajax.Request(url, {
method: method,
parameters: params,
- asynchronous: true,
evalScripts: true,
- onLoading: function(request) { element.fire("ajax:loading", {request: request}); },
- onLoaded: function(request) { element.fire("ajax:loaded", {request: request}); },
- onInteractive: function(request) { element.fire("ajax:interactive", {request: request}); },
- onComplete: function(request) { element.fire("ajax:complete", {request: request}); },
- onSuccess: function(request) { element.fire("ajax:success", {request: request}); },
- onFailure: function(request) { element.fire("ajax:failure", {request: request}); }
+ onComplete: function(request) { element.fire("ajax:complete", request); },
+ onSuccess: function(request) { element.fire("ajax:success", request); },
+ onFailure: function(request) { element.fire("ajax:failure", request); }
});
element.fire("ajax:after");
}
function handleMethod(element) {
- var method, url, token_name, token;
-
- method = element.readAttribute('data-method');
- url = element.readAttribute('href');
- csrf_param = $$('meta[name=csrf-param]').first();
- csrf_token = $$('meta[name=csrf-token]').first();
+ var method = element.readAttribute('data-method'),
+ url = element.readAttribute('href'),
+ csrf_param = $$('meta[name=csrf-param]')[0],
+ csrf_token = $$('meta[name=csrf-token]')[0];
var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
- element.parentNode.appendChild(form);
+ element.parentNode.insert(form);
- if (method != 'post') {
+ if (method !== 'post') {
var field = new Element('input', { type: 'hidden', name: '_method', value: method });
- form.appendChild(field);
+ form.insert(field);
}
if (csrf_param) {
- var param = csrf_param.readAttribute('content');
- var token = csrf_token.readAttribute('content');
- var field = new Element('input', { type: 'hidden', name: param, value: token });
- form.appendChild(field);
+ var param = csrf_param.readAttribute('content'),
+ token = csrf_token.readAttribute('content'),
+ field = new Element('input', { type: 'hidden', name: param, value: token });
+ form.insert(field);
}
form.submit();
}
- $(document.body).observe("click", function(event) {
- var message = event.findElement().readAttribute('data-confirm');
- if (message && !confirm(message)) {
- event.stop();
- return false;
- }
- var element = event.findElement("a[data-remote]");
- if (element) {
- handleRemote(element);
- event.stop();
- return true;
- }
+ document.on("click", "*[data-confirm]", function(event, element) {
+ var message = element.readAttribute('data-confirm');
+ if (!confirm(message)) event.stop();
+ });
- var element = event.findElement("a[data-method]");
- if (element) {
- handleMethod(element);
- event.stop();
- return true;
- }
+ document.on("click", "a[data-remote]", function(event, element) {
+ if (event.stopped) return;
+ handleRemote(element);
+ event.stop();
+ });
+
+ document.on("click", "a[data-method]", function(event, element) {
+ if (event.stopped) return;
+ handleMethod(element);
+ event.stop();
});
- // TODO: I don't think submit bubbles in IE
- $(document.body).observe("submit", function(event) {
+ document.on("submit", function(event) {
var element = event.findElement(),
message = element.readAttribute('data-confirm');
if (message && !confirm(message)) {
@@ -103,16 +164,12 @@ document.observe("dom:loaded", function() {
}
});
- $(document.body).observe("ajax:after", function(event) {
- var element = event.findElement();
-
- if (element.tagName.toLowerCase() === 'form') {
- var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
- inputs.each(function(input) {
- input.value = input.readAttribute('data-original-value');
- input.writeAttribute('data-original-value', null);
- input.disabled = false;
- });
- }
+ document.on("ajax:after", "form", function(event, element) {
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
+ inputs.each(function(input) {
+ input.value = input.readAttribute('data-original-value');
+ input.removeAttribute('data-original-value');
+ input.disabled = false;
+ });
});
-}); \ No newline at end of file
+})();
diff --git a/railties/lib/rails/generators/rails/model/USAGE b/railties/lib/rails/generators/rails/model/USAGE
index db98a2dd1b..67f76aad01 100644
--- a/railties/lib/rails/generators/rails/model/USAGE
+++ b/railties/lib/rails/generators/rails/model/USAGE
@@ -40,6 +40,6 @@ Examples:
Module: app/models/admin.rb
Model: app/models/admin/account.rb
Test: test/unit/admin/account_test.rb
- Fixtures: test/fixtures/admin_accounts.yml
+ Fixtures: test/fixtures/admin/accounts.yml
Migration: db/migrate/XXX_add_admin_accounts.rb
diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb
index 8a46708009..fc070026d6 100644
--- a/railties/lib/rails/generators/rails/resource/resource_generator.rb
+++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb
@@ -14,23 +14,13 @@ module Rails
class_option :actions, :type => :array, :banner => "ACTION ACTION", :default => [],
:desc => "Actions for the resource controller"
- class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
-
def add_resource_route
return if options[:actions].present?
- route "resource#{:s unless options[:singleton]} :#{pluralize?(file_name)}"
+ route_config = class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ")
+ route_config << "resources :#{file_name.pluralize}"
+ route_config << " end" * class_path.size
+ route route_config
end
-
- protected
-
- def pluralize?(name)
- if options[:singleton]
- name
- else
- name.pluralize
- end
- 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 49af2974cd..2271c6f9c1 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,6 @@ module Rails
class_option :orm, :banner => "NAME", :type => :string, :required => true,
:desc => "ORM to generate the controller for"
- class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
-
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 bbdce669dc..b21340f755 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb
@@ -1,53 +1,51 @@
class <%= controller_class_name %>Controller < ApplicationController
-<% unless options[:singleton] -%>
- # GET /<%= table_name %>
- # GET /<%= table_name %>.xml
+ # GET <%= route_url %>
+ # GET <%= route_url %>.xml
def index
- @<%= table_name %> = <%= orm_class.all(class_name) %>
+ @<%= plural_table_name %> = <%= orm_class.all(class_name) %>
respond_to do |format|
format.html # index.html.erb
- format.xml { render :xml => @<%= table_name %> }
+ format.xml { render :xml => @<%= plural_table_name %> }
end
end
-<% end -%>
- # GET /<%= table_name %>/1
- # GET /<%= table_name %>/1.xml
+ # GET <%= route_url %>/1
+ # GET <%= route_url %>/1.xml
def show
- @<%= file_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
format.html # show.html.erb
- format.xml { render :xml => @<%= file_name %> }
+ format.xml { render :xml => @<%= singular_table_name %> }
end
end
- # GET /<%= table_name %>/new
- # GET /<%= table_name %>/new.xml
+ # GET <%= route_url %>/new
+ # GET <%= route_url %>/new.xml
def new
- @<%= file_name %> = <%= orm_class.build(class_name) %>
+ @<%= singular_table_name %> = <%= orm_class.build(class_name) %>
respond_to do |format|
format.html # new.html.erb
- format.xml { render :xml => @<%= file_name %> }
+ format.xml { render :xml => @<%= singular_table_name %> }
end
end
- # GET /<%= table_name %>/1/edit
+ # GET <%= route_url %>/1/edit
def edit
- @<%= file_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
end
- # POST /<%= table_name %>
- # POST /<%= table_name %>.xml
+ # POST <%= route_url %>
+ # POST <%= route_url %>.xml
def create
- @<%= file_name %> = <%= orm_class.build(class_name, "params[:#{file_name}]") %>
+ @<%= singular_table_name %> = <%= orm_class.build(class_name, "params[:#{singular_table_name}]") %>
respond_to do |format|
if @<%= orm_instance.save %>
- format.html { redirect_to(@<%= file_name %>, :notice => '<%= human_name %> was successfully created.') }
- format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> }
+ format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully created.') }
+ format.xml { render :xml => @<%= singular_table_name %>, :status => :created, :location => @<%= singular_table_name %> }
else
format.html { render :action => "new" }
format.xml { render :xml => @<%= orm_instance.errors %>, :status => :unprocessable_entity }
@@ -55,14 +53,14 @@ class <%= controller_class_name %>Controller < ApplicationController
end
end
- # PUT /<%= table_name %>/1
- # PUT /<%= table_name %>/1.xml
+ # PUT <%= route_url %>/1
+ # PUT <%= route_url %>/1.xml
def update
- @<%= file_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
respond_to do |format|
- if @<%= orm_instance.update_attributes("params[:#{file_name}]") %>
- format.html { redirect_to(@<%= file_name %>, :notice => '<%= human_name %> was successfully updated.') }
+ if @<%= orm_instance.update_attributes("params[:#{singular_table_name}]") %>
+ format.html { redirect_to(@<%= singular_table_name %>, :notice => '<%= human_name %> was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
@@ -71,14 +69,14 @@ class <%= controller_class_name %>Controller < ApplicationController
end
end
- # DELETE /<%= table_name %>/1
- # DELETE /<%= table_name %>/1.xml
+ # DELETE <%= route_url %>/1
+ # DELETE <%= route_url %>/1.xml
def destroy
- @<%= file_name %> = <%= orm_class.find(class_name, "params[:id]") %>
+ @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
@<%= orm_instance.destroy %>
respond_to do |format|
- format.html { redirect_to(<%= table_name %>_url) }
+ format.html { redirect_to(<%= index_helper %>_url) }
format.xml { head :ok }
end
end
diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb
index 3a98a8f9c1..829f4b200a 100644
--- a/railties/lib/rails/generators/resource_helpers.rb
+++ b/railties/lib/rails/generators/resource_helpers.rb
@@ -72,7 +72,7 @@ module Rails
end
# Initialize ORM::Generators::ActiveModel to access instance methods.
- def orm_instance(name=file_name)
+ def orm_instance(name=singular_table_name)
@orm_instance ||= @orm_class.new(name)
end
end
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 609b815683..c1dd535dd3 100644
--- a/railties/lib/rails/generators/test_unit/model/model_generator.rb
+++ b/railties/lib/rails/generators/test_unit/model/model_generator.rb
@@ -16,7 +16,7 @@ module TestUnit
def create_fixture_file
if options[:fixture] && options[:fixture_replacement].nil?
- template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml")
+ template 'fixtures.yml', File.join('test/fixtures', class_path, "#{plural_file_name}.yml")
end
end
end
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 c0315c7fe6..f7e907a017 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -6,7 +6,6 @@ module TestUnit
class ScaffoldGenerator < Base
include Rails::Generators::ResourceHelpers
- class_option :singleton, :type => :boolean, :desc => "Supply to create a singleton controller"
check_class_collision :suffix => "ControllerTest"
def create_test_files
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 4f8ddbffcf..f23e495450 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
@@ -2,50 +2,48 @@ require 'test_helper'
class <%= controller_class_name %>ControllerTest < ActionController::TestCase
setup do
- @<%= file_name %> = <%= table_name %>(:one)
+ @<%= singular_table_name %> = <%= table_name %>(:one)
end
-<% unless options[:singleton] -%>
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:<%= table_name %>)
end
-<% end -%>
test "should get new" do
get :new
assert_response :success
end
- test "should create <%= file_name %>" do
+ test "should create <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count') do
- post :create, :<%= file_name %> => @<%= file_name %>.attributes
+ post :create, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
end
- assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
+ assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
- test "should show <%= file_name %>" do
- get :show, :id => @<%= file_name %>.to_param
+ test "should show <%= singular_table_name %>" do
+ get :show, :id => @<%= singular_table_name %>.to_param
assert_response :success
end
test "should get edit" do
- get :edit, :id => @<%= file_name %>.to_param
+ get :edit, :id => @<%= singular_table_name %>.to_param
assert_response :success
end
- test "should update <%= file_name %>" do
- put :update, :id => @<%= file_name %>.to_param, :<%= file_name %> => @<%= file_name %>.attributes
- assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>))
+ test "should update <%= singular_table_name %>" do
+ put :update, :id => @<%= singular_table_name %>.to_param, :<%= singular_table_name %> => @<%= singular_table_name %>.attributes
+ assert_redirected_to <%= singular_table_name %>_path(assigns(:<%= singular_table_name %>))
end
- test "should destroy <%= file_name %>" do
+ test "should destroy <%= singular_table_name %>" do
assert_difference('<%= class_name %>.count', -1) do
- delete :destroy, :id => @<%= file_name %>.to_param
+ delete :destroy, :id => @<%= singular_table_name %>.to_param
end
- assert_redirected_to <%= table_name %>_path
+ assert_redirected_to <%= index_helper %>_path
end
end
diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb
index 9a82e051e7..686a2dc0cb 100644
--- a/railties/lib/rails/initializable.rb
+++ b/railties/lib/rails/initializable.rb
@@ -39,11 +39,6 @@ module Rails
select { |i| i.before == initializer.name || i.name == initializer.after }.each(&block)
end
- def initialize(initializers = [])
- super(initializers)
- replace(tsort)
- end
-
def +(other)
Collection.new(to_a + other.to_a)
end
@@ -51,7 +46,7 @@ module Rails
def run_initializers(*args)
return if instance_variable_defined?(:@ran)
- initializers.each do |initializer|
+ initializers.tsort.each do |initializer|
initializer.run(*args)
end
@ran = true
@@ -63,7 +58,7 @@ module Rails
module ClassMethods
def initializers
- @initializers ||= []
+ @initializers ||= Collection.new
end
def initializers_chain
@@ -84,14 +79,6 @@ module Rails
opts[:after] ||= initializers.last.name unless initializers.empty? || initializers.find { |i| i.name == opts[:before] }
initializers << Initializer.new(name, nil, opts, &blk)
end
-
- def run_initializers(*args)
- return if @ran
- initializers_chain.each do |initializer|
- instance_exec(*args, &initializer.block)
- end
- @ran = true
- end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 1c9e308631..7a65188a9a 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -25,9 +25,7 @@ module Rails
def initialize(path)
raise if path.is_a?(Array)
-
@children = {}
-
@path = path
@root = self
@all_paths = []
@@ -38,14 +36,18 @@ module Rails
@all_paths
end
- def load_once
- filter_by(:load_once?)
+ def autoload_once
+ filter_by(:autoload_once?)
end
def eager_load
filter_by(:eager_load?)
end
+ def autoload_paths
+ filter_by(:autoload?)
+ end
+
def load_paths
filter_by(:load_path?)
end
@@ -61,15 +63,17 @@ module Rails
protected
def filter_by(constraint)
- all_paths.map do |path|
+ all = []
+ all_paths.each do |path|
if path.send(constraint)
paths = path.paths
paths -= path.children.values.map { |p| p.send(constraint) ? [] : p.paths }.flatten
- paths
- else
- []
+ all.concat(paths)
end
- end.flatten.uniq.select { |p| File.exists?(p) }
+ end
+ all.uniq!
+ all.reject! { |p| !File.exists?(p) }
+ all
end
end
@@ -80,15 +84,16 @@ module Rails
attr_accessor :glob
def initialize(root, *paths)
- @options = paths.last.is_a?(::Hash) ? paths.pop : {}
+ options = paths.last.is_a?(::Hash) ? paths.pop : {}
@children = {}
@root = root
@paths = paths.flatten
- @glob = @options.delete(:glob)
+ @glob = options[:glob]
- @load_once = @options[:load_once]
- @eager_load = @options[:eager_load]
- @load_path = @options[:load_path] || @eager_load || @load_once
+ autoload_once! if options[:autoload_once]
+ eager_load! if options[:eager_load]
+ autoload! if options[:autoload]
+ load_path! if options[:load_path]
@root.all_paths << self
end
@@ -111,24 +116,30 @@ module Rails
@paths.concat paths
end
- def load_once!
- @load_once = true
- @load_path = true
+ def autoload_once!
+ @autoload_once = true
end
- def load_once?
- @load_once
+ def autoload_once?
+ @autoload_once
end
def eager_load!
@eager_load = true
- @load_path = true
end
def eager_load?
@eager_load
end
+ def autoload!
+ @autoload = true
+ end
+
+ def autoload?
+ @autoload
+ end
+
def load_path!
@load_path = true
end
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index fcdd099135..be229cc9a2 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -61,6 +61,16 @@ module Rails
@config ||= Engine::Configuration.new
end
+ initializer :handle_lib_autoload, :before => :set_load_path do |app|
+ paths = if app.config.reload_plugins
+ config.autoload_paths
+ else
+ config.autoload_once_paths
+ end
+
+ paths.concat config.paths.lib.to_a
+ end
+
initializer :load_init_rb, :before => :load_config_initializers do |app|
files = %w(rails/init.rb init.rb).map { |path| File.expand_path path, root }
if initrb = files.find { |path| File.file? path }
@@ -77,11 +87,5 @@ module Rails
raise "\"#{name}\" is a Railtie/Engine and cannot be installed as plugin"
end
end
-
- protected
-
- def reloadable?(app)
- app.config.reload_plugins
- end
end
end
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index 73e9af3b41..b3dc1f894c 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -1,10 +1,9 @@
-require 'rails/log_subscriber'
require 'active_support/core_ext/time/conversions'
module Rails
module Rack
# Log the request started and flush all loggers after it.
- class Logger < Rails::LogSubscriber
+ class Logger < ActiveSupport::LogSubscriber
def initialize(app)
@app = app
end
@@ -16,20 +15,19 @@ module Rails
after_dispatch(env)
end
- protected
+ protected
- def before_dispatch(env)
- request = ActionDispatch::Request.new(env)
- path = request.fullpath
+ def before_dispatch(env)
+ request = ActionDispatch::Request.new(env)
+ path = request.fullpath
- info "\n\nStarted #{env["REQUEST_METHOD"]} \"#{path}\" " \
- "for #{request.ip} at #{Time.now.to_default_s}"
- end
-
- def after_dispatch(env)
- Rails::LogSubscriber.flush_all!
- end
+ info "\n\nStarted #{env["REQUEST_METHOD"]} \"#{path}\" " \
+ "for #{request.ip} at #{Time.now.to_default_s}"
+ end
+ def after_dispatch(env)
+ ActiveSupport::LogSubscriber.flush_all!
+ end
end
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index ad776933f2..dbdbfea509 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -114,36 +114,6 @@ module Rails
# end
# end
#
- # == Adding your subscriber
- #
- # Since version 3.0, Rails ships with a notification system which is used for several
- # purposes, including logging. If you are sending notifications in your Railtie, you may
- # want to add a subscriber to consume such notifications for logging purposes.
- #
- # The subscriber is added under the railtie_name namespace and only consumes notifications
- # under the given namespace. For example, let's suppose your railtie is publishing the
- # following "something_expensive" instrumentation:
- #
- # ActiveSupport::Notifications.instrument "my_railtie.something_expensive" do
- # # something expensive
- # end
- #
- # You can log this instrumentation with your own Rails::Subscriber:
- #
- # class MyRailtie::Subscriber < Rails::Subscriber
- # def something_expensive(event)
- # info("Something expensive took %.1fms" % event.duration)
- # end
- # end
- #
- # By registering it:
- #
- # class MyRailtie < Railtie
- # subscriber :my_gem, MyRailtie::Subscriber.new
- # end
- #
- # Take a look in Rails::Subscriber docs for more information.
- #
# == Application, Plugin and Engine
#
# A Rails::Engine is nothing more than a Railtie with some initializers already set.
@@ -176,8 +146,8 @@ module Rails
ActiveSupport::Deprecation.warn "railtie_name is deprecated and has no effect", caller
end
- def log_subscriber(name, log_subscriber)
- Rails::LogSubscriber.add(name, log_subscriber)
+ def log_subscriber(*)
+ ActiveSupport::Deprecation.warn "log_subscriber is deprecated and has no effect", caller
end
def rake_tasks(&blk)
diff --git a/railties/lib/rails/webrick_server.rb b/railties/lib/rails/webrick_server.rb
deleted file mode 100644
index f3b74c28d3..0000000000
--- a/railties/lib/rails/webrick_server.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-# Donated by Florian Gross
-
-require 'webrick'
-require 'cgi'
-require 'stringio'
-require 'dispatcher'
-
-include WEBrick
-
-class CGI #:nodoc:
- def stdinput
- @stdin || $stdin
- end
-
- def env_table
- @env_table || ENV
- end
-
- def initialize(type = "query", table = nil, stdin = nil)
- @env_table, @stdin = table, stdin
-
- if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
- Apache.request.setup_cgi_env
- end
-
- extend QueryExtension
- @multipart = false
- if defined?(CGI_PARAMS)
- warn "do not use CGI_PARAMS and CGI_COOKIES"
- @params = CGI_PARAMS.dup
- @cookies = CGI_COOKIES.dup
- else
- initialize_query() # set @params, @cookies
- end
- @output_cookies = nil
- @output_hidden = nil
- end
-end
-
-# A custom dispatch servlet for use with WEBrick. It dispatches requests
-# (using the Rails Dispatcher) to the appropriate controller/action. By default,
-# it restricts WEBrick to a managing a single Rails request at a time, but you
-# can change this behavior by setting ActionController::Base.allow_concurrency
-# to true.
-class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
- # Start the WEBrick server with the given options, mounting the
- # DispatchServlet at <tt>/</tt>.
- def self.dispatch(options = {})
- Socket.do_not_reverse_lookup = true # patch for OS X
-
- params = { :Port => options[:port].to_i,
- :ServerType => options[:server_type],
- :BindAddress => options[:ip] }
- params[:MimeTypes] = options[:mime_types] if options[:mime_types]
-
- server = WEBrick::HTTPServer.new(params)
- server.mount('/', DispatchServlet, options)
-
- trap("INT") { server.shutdown }
- server.start
- end
-
- def initialize(server, options) #:nodoc:
- @server_options = options
- @file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])
- # Change to the Rails.root, since Webrick::Daemon.start does a Dir::cwd("/")
- # OPTIONS['working_directory'] is an absolute path of the Rails.root, set in railties/lib/commands/servers/webrick.rb
- Dir.chdir(OPTIONS['working_directory']) if defined?(OPTIONS) && File.directory?(OPTIONS['working_directory'])
- super
- end
-
- def service(req, res) #:nodoc:
- unless handle_file(req, res)
- unless handle_dispatch(req, res)
- raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
- end
- end
- end
-
- def handle_file(req, res) #:nodoc:
- begin
- req = req.dup
- path = req.path.dup
-
- # Add .html if the last path piece has no . in it
- path << '.html' if path != '/' && (%r{(^|/)[^./]+$} =~ path)
- path.gsub!('+', ' ') # Unescape + since FileHandler doesn't do so.
-
- req.instance_variable_set(:@path_info, path) # Set the modified path...
-
- @file_handler.send(:service, req, res)
- return true
- rescue HTTPStatus::PartialContent, HTTPStatus::NotModified => err
- res.set_error(err)
- return true
- rescue => err
- return false
- end
- end
-
- def handle_dispatch(req, res, origin = nil) #:nodoc:
- data = StringIO.new
- Dispatcher.dispatch(
- CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")),
- ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
- data
- )
-
- header, body = extract_header_and_body(data)
-
- set_charset(header)
- assign_status(res, header)
- res.cookies.concat(header.delete('set-cookie') || [])
- header.each { |key, val| res[key] = val.join(", ") }
-
- res.body = body
- return true
- rescue => err
- p err, err.backtrace
- return false
- end
-
- private
- def create_env_table(req, origin)
- env = req.meta_vars.clone
- env.delete "SCRIPT_NAME"
- env["QUERY_STRING"] = req.request_uri.query
- env["REQUEST_URI"] = origin if origin
- return env
- end
-
- def extract_header_and_body(data)
- data.rewind
- data = data.read
-
- raw_header, body = *data.split(/^[\xd\xa]{2}/on, 2)
- header = WEBrick::HTTPUtils::parse_header(raw_header)
-
- return header, body
- end
-
- def set_charset(header)
- ct = header["content-type"]
- if ct.any? { |x| x =~ /^text\// } && ! ct.any? { |x| x =~ /charset=/ }
- ch = @server_options[:charset] || "UTF-8"
- ct.find { |x| x =~ /^text\// } << ("; charset=" + ch)
- end
- end
-
- def assign_status(res, header)
- if /^(\d+)/ =~ header['status'][0]
- res.status = $1.to_i
- header.delete('status')
- end
- end
-end
diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb
index d04a2aa1f3..a05bae5dcc 100644
--- a/railties/test/abstract_unit.rb
+++ b/railties/test/abstract_unit.rb
@@ -1,5 +1,3 @@
-ORIG_ARGV = ARGV.dup
-
require File.expand_path("../../../load_paths", __FILE__)
require 'stringio'
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 35ea2729d3..7269a7c5a8 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -28,8 +28,10 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- ActionController::Base.view_paths.include?(File.expand_path("app/views", app_path))
- ActionMailer::Base.view_paths.include?(File.expand_path("app/views", app_path))
+
+ expanded_path = File.expand_path("app/views", app_path)
+ assert_equal ActionController::Base.view_paths[0].to_s, expanded_path
+ assert_equal ActionMailer::Base.view_paths[0].to_s, expanded_path
end
test "allows me to configure default url options for ActionMailer" do
@@ -40,7 +42,7 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
- assert "test.rails", ActionMailer::Base.default_url_options[:host]
+ assert_equal "test.rails", ActionMailer::Base.default_url_options[:host]
end
# AS
@@ -57,7 +59,7 @@ module ApplicationTests
Dir.chdir("#{app_path}/app") do
require "#{app_path}/config/environment"
- assert_raises(NoMethodError) { [1,2,3].sample }
+ assert_raises(NoMethodError) { [1,2,3].forty_two }
end
end
diff --git a/railties/test/application/initializers/i18n_test.rb b/railties/test/application/initializers/i18n_test.rb
index 99b2d86013..a1fcba3310 100644
--- a/railties/test/application/initializers/i18n_test.rb
+++ b/railties/test/application/initializers/i18n_test.rb
@@ -8,48 +8,143 @@ module ApplicationTests
build_app
boot_rails
FileUtils.rm_rf "#{app_path}/config/environments"
+ require "rails/all"
end
- # i18n
+ def load_app
+ require "#{app_path}/config/environment"
+ end
+
+ def app
+ @app ||= Rails::Application
+ end
+
+ def assert_fallbacks(fallbacks)
+ fallbacks.each do |locale, expected|
+ actual = I18n.fallbacks[locale]
+ assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}"
+ end
+ end
+
+ def assert_no_fallbacks
+ assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ end
+
+ # Locales
test "setting another default locale" do
add_to_config <<-RUBY
- config.root = "#{app_path}"
config.i18n.default_locale = :de
RUBY
- require "#{app_path}/config/environment"
+ load_app
assert_equal :de, I18n.default_locale
end
+ # Load paths
test "no config locales dir present should return empty load path" do
FileUtils.rm_rf "#{app_path}/config/locales"
- add_to_config <<-RUBY
- config.root = "#{app_path}"
- RUBY
- require "#{app_path}/config/environment"
-
+ load_app
assert_equal [], Rails.application.config.i18n.load_path
end
- test "config locales dir present should be added to load path" do
+ test "locale files should be added to the load path" do
+ app_file "config/another_locale.yml", ""
+
add_to_config <<-RUBY
- config.root = "#{app_path}"
+ config.i18n.load_path << config.root.join("config/another_locale.yml").to_s
RUBY
- require "#{app_path}/config/environment"
- assert_equal ["#{app_path}/config/locales/en.yml"], Rails.application.config.i18n.load_path
+ load_app
+ assert_equal [
+ "#{app_path}/config/locales/en.yml", "#{app_path}/config/another_locale.yml"
+ ], Rails.application.config.i18n.load_path
+
+ assert I18n.load_path.include?("#{app_path}/config/locales/en.yml")
+ assert I18n.load_path.include?("#{app_path}/config/another_locale.yml")
end
- test "config defaults should be added with config settings" do
+ test "locales are reloaded if they change between requests" do
add_to_config <<-RUBY
- config.root = "#{app_path}"
- config.i18n.load_path << "my/other/locale.yml"
+ config.cache_classes = false
RUBY
- require "#{app_path}/config/environment"
- assert_equal [
- "#{app_path}/config/locales/en.yml", "my/other/locale.yml"
- ], Rails.application.config.i18n.load_path
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "1"
+ YAML
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match '/i18n', :to => lambda { |env| [200, {}, [I18n.t(:foo)]] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+ load_app
+
+ get "/i18n"
+ assert_equal "1", last_response.body
+
+ app_file "config/locales/en.yml", <<-YAML
+en:
+ foo: "2"
+ YAML
+
+ get "/i18n"
+ assert_equal "2", last_response.body
+ end
+
+ # Fallbacks
+ test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
+ I18n.backend = Class.new { include I18n::Backend::Base }.new
+ load_app
+ assert_no_fallbacks
+ end
+
+ test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do
+ I18n::Railtie.config.i18n.fallbacks = true
+ load_app
+ assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_fallbacks :de => [:de, :en]
+ end
+
+ test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do
+ I18n::Railtie.config.i18n.fallbacks = true
+ I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new
+ load_app
+ assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
+ assert_fallbacks :de => [:de, :en]
+ end
+
+ test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
+ I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US']
+ load_app
+ assert_fallbacks :de => [:de, :'en-US', :en]
+ end
+
+ test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do
+ I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
+ I18n::Railtie.config.i18n.fallbacks = [:'en-US']
+ load_app
+ assert_fallbacks :de => [:de, :'en-US', :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do
+ I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
+ end
+
+ test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do
+ I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }]
+ load_app
+ assert_fallbacks :ca => [:ca, :"es-ES", :es, :'en-US', :en]
end
end
end \ No newline at end of file
diff --git a/railties/test/application/initializers/load_path_test.rb b/railties/test/application/initializers/load_path_test.rb
index d31915e129..714d62311d 100644
--- a/railties/test/application/initializers/load_path_test.rb
+++ b/railties/test/application/initializers/load_path_test.rb
@@ -19,7 +19,7 @@ module ApplicationTests
assert $:.include?("#{app_path}/app/models")
end
- test "initializing an application adds lib path on inheritance hook" do
+ test "initializing an application allows to load code on lib path inside application class definitation" do
app_file "lib/foo.rb", <<-RUBY
module Foo; end
RUBY
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
new file mode 100644
index 0000000000..340ce67511
--- /dev/null
+++ b/railties/test/application/loading_test.rb
@@ -0,0 +1,86 @@
+require 'isolation/abstract_unit'
+
+class LoadingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+ end
+
+ def app
+ @app ||= Rails.application
+ end
+
+ def test_constants_in_app_are_autoloaded
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ validates_acceptance_of :title, :accept => "omg"
+ end
+ MODEL
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ p = Post.create(:title => 'omg')
+ assert_equal 1, Post.count
+ assert_equal 'omg', p.title
+ p = Post.first
+ assert_equal 'omg', p.title
+ end
+
+ def test_models_without_table_do_not_panic_on_scope_definitions_when_loaded
+ app_file "app/models/user.rb", <<-MODEL
+ class User < ActiveRecord::Base
+ default_scope where(:published => true)
+ end
+ MODEL
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ User
+ end
+
+ def test_descendants_are_cleaned_on_each_request_without_cache_classes
+ add_to_config <<-RUBY
+ config.cache_classes = false
+ RUBY
+
+ app_file "app/models/post.rb", <<-MODEL
+ class Post < ActiveRecord::Base
+ end
+ MODEL
+
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match '/load', :to => lambda { |env| [200, {}, Post.all] }
+ match '/unload', :to => lambda { |env| [200, {}, []] }
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ require "#{rails_root}/config/environment"
+ setup_ar!
+
+ assert_equal [], ActiveRecord::Base.descendants
+ get "/load"
+ assert_equal [Post], ActiveRecord::Base.descendants
+ get "/unload"
+ assert_equal [], ActiveRecord::Base.descendants
+ end
+
+ protected
+
+ def setup_ar!
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
+ ActiveRecord::Migration.verbose = false
+ ActiveRecord::Schema.define(:version => 1) do
+ create_table :posts do |t|
+ t.string :title
+ end
+ end
+ end
+end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index 999f666a64..e66e81ea2c 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -83,6 +83,17 @@ module ApplicationTests
assert_equal "Rack::Config", middleware.second
end
+ test "RAILS_CACHE does not respond to middleware" do
+ add_to_config "config.cache_store = :memory_store"
+ boot!
+ assert_equal "Rack::Runtime", middleware.third
+ end
+
+ test "RAILS_CACHE does respond to middleware" do
+ boot!
+ assert_equal "Rack::Runtime", middleware.fourth
+ end
+
test "insert middleware before" do
add_to_config "config.middleware.insert_before ActionDispatch::Static, Rack::Config"
boot!
diff --git a/railties/test/application/model_initialization_test.rb b/railties/test/application/model_initialization_test.rb
deleted file mode 100644
index 6a22f8d8df..0000000000
--- a/railties/test/application/model_initialization_test.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'isolation/abstract_unit'
-
-class PostTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- end
-
- def test_reload_should_reload_constants
- app_file "app/models/post.rb", <<-MODEL
- class Post < ActiveRecord::Base
- validates_acceptance_of :title, :accept => "omg"
- end
- MODEL
-
- require "#{rails_root}/config/environment"
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
- ActiveRecord::Migration.verbose = false
- ActiveRecord::Schema.define(:version => 1) do
- create_table :posts do |t|
- t.string :title
- end
- end
-
- p = Post.create(:title => 'omg')
- assert_equal 1, Post.count
- assert_equal 'omg', p.title
- p = Post.first
- assert_equal 'omg', p.title
- end
-end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 6b7a471494..40fb446b16 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -9,7 +9,7 @@ module ApplicationTests
boot_rails
FileUtils.rm_rf("#{app_path}/config/environments")
end
-
+
def test_gems_tasks_are_loaded_first_than_application_ones
app_file "lib/tasks/app.rake", <<-RUBY
$task_loaded = Rake::Task.task_defined?("db:create:all")
diff --git a/railties/test/application/runner_test.rb b/railties/test/application/runner_test.rb
new file mode 100644
index 0000000000..d37b7649e2
--- /dev/null
+++ b/railties/test/application/runner_test.rb
@@ -0,0 +1,49 @@
+require 'isolation/abstract_unit'
+
+module ApplicationTests
+ class RunnerTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ boot_rails
+
+ # Lets create a model so we have something to play with
+ app_file "app/models/user.rb", <<-MODEL
+ class User
+ def self.count
+ 42
+ end
+ end
+ MODEL
+ end
+
+ def test_should_run_ruby_statement
+ assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "puts User.count"` }
+ end
+
+ def test_should_run_file
+ app_file "script/count_users.rb", <<-SCRIPT
+ puts User.count
+ SCRIPT
+
+ assert_match "42", Dir.chdir(app_path) { `bundle exec rails runner "script/count_users.rb"` }
+ end
+
+ def test_should_set_dollar_0_to_file
+ app_file "script/dollar0.rb", <<-SCRIPT
+ puts $0
+ SCRIPT
+
+ assert_match "script/dollar0.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/dollar0.rb"` }
+ end
+
+ def test_should_set_dollar_program_name_to_file
+ app_file "script/program_name.rb", <<-SCRIPT
+ puts $PROGRAM_NAME
+ SCRIPT
+
+ assert_match "script/program_name.rb", Dir.chdir(app_path) { `bundle exec rails runner "script/program_name.rb"` }
+ end
+ end
+end
diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb
index 72cae23985..2b6ec26cd0 100644
--- a/railties/test/application/url_generation_test.rb
+++ b/railties/test/application/url_generation_test.rb
@@ -16,6 +16,7 @@ module ApplicationTests
class MyApp < Rails::Application
config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
config.session_store :cookie_store, :key => "_myapp_session"
+ config.active_support.deprecation = :log
end
MyApp.initialize!
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 65fbf61902..0472ca73a8 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -130,9 +130,9 @@ class ActionsTest < Rails::Generators::TestCase
def test_environment_should_include_data_in_environment_initializer_block
run_generator
- load_paths = 'config.load_paths += %w["#{Rails.root}/app/extras"]'
- action :environment, load_paths
- assert_file 'config/application.rb', /#{Regexp.escape(load_paths)}/
+ autoload_paths = 'config.autoload_paths += %w["#{Rails.root}/app/extras"]'
+ action :environment, autoload_paths
+ assert_file 'config/application.rb', /#{Regexp.escape(autoload_paths)}/
end
def test_environment_with_block_should_include_block_contents_in_environment_initializer_block
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 6ea722e239..f9d1e42d24 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -62,4 +62,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
end
+
+ def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove
+ migration = "create_books"
+ run_generator [migration, "title:string", "content:text"]
+
+ assert_migration "db/migrate/#{migration}.rb" do |content|
+ assert_class_method :up, content do |up|
+ assert_match /^\s*$/, up
+ end
+
+ assert_class_method :down, content do |down|
+ assert_match /^\s*$/, down
+ end
+ end
+ end
end
diff --git a/railties/test/generators/named_base_test.rb b/railties/test/generators/named_base_test.rb
index e73dd237fb..1badae0713 100644
--- a/railties/test/generators/named_base_test.rb
+++ b/railties/test/generators/named_base_test.rb
@@ -93,6 +93,16 @@ class NamedBaseTest < Rails::Generators::TestCase
assert_name g, "application", :application_name
end
+ def test_index_helper
+ g = generator ['Post']
+ assert_name g, 'posts', :index_helper
+ end
+
+ def test_index_helper_with_uncountable
+ g = generator ['Sheep']
+ assert_name g, 'sheep_index', :index_helper
+ end
+
protected
def assert_name(generator, value, method)
diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb
index 96fd7a0a72..55d5bd6f83 100644
--- a/railties/test/generators/resource_generator_test.rb
+++ b/railties/test/generators/resource_generator_test.rb
@@ -59,14 +59,6 @@ class ResourceGeneratorTest < Rails::Generators::TestCase
end
end
- def test_singleton_resource
- run_generator ["account", "--singleton"]
-
- assert_file "config/routes.rb" do |route|
- assert_match /resource :account$/, route
- end
- end
-
def test_plural_names_are_singularized
content = run_generator ["accounts".freeze]
assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 8040b22fe6..d55ed22975 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -78,20 +78,6 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
end
end
- def test_generates_singleton_controller
- run_generator ["User", "name:string", "age:integer", "--singleton"]
-
- assert_file "app/controllers/users_controller.rb" do |content|
- assert_no_match /def index/, content
- end
-
- assert_file "test/functional/users_controller_test.rb" do |content|
- assert_no_match /test "should get index"/, content
- end
-
- assert_no_file "app/views/users/index.html.erb"
- end
-
def test_skip_helper_if_required
run_generator ["User", "name:string", "age:integer", "--no-helper"]
assert_no_file "app/helpers/users_helper.rb"
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index e8e622fe5c..ea469cb3c8 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -110,4 +110,110 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Stylesheets (should not be removed)
assert_file "public/stylesheets/scaffold.css"
end
+
+ def test_scaffold_with_namespace_on_invoke
+ run_generator [ "admin/role", "name:string", "description:string" ]
+
+ # Model
+ assert_file "app/models/admin.rb", /module Admin/
+ assert_file "app/models/admin/role.rb", /class Admin::Role < ActiveRecord::Base/
+ assert_file "test/unit/admin/role_test.rb", /class Admin::RoleTest < ActiveSupport::TestCase/
+ assert_file "test/fixtures/admin/roles.yml"
+ assert_migration "db/migrate/create_admin_roles.rb"
+
+ # Route
+ assert_file "config/routes.rb" do |route|
+ assert_match /namespace :admin do resources :roles end$/, route
+ end
+
+ # Controller
+ assert_file "app/controllers/admin/roles_controller.rb" do |content|
+ assert_match /class Admin::RolesController < ApplicationController/, content
+
+ assert_instance_method :index, content do |m|
+ 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 :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 :create, content do |m|
+ assert_match /@admin_role = Admin::Role\.new\(params\[:admin_role\]\)/, m
+ assert_match /@admin_role\.save/, m
+ assert_match /@admin_role\.errors/, m
+ end
+
+ assert_instance_method :update, content do |m|
+ assert_match /@admin_role = Admin::Role\.find\(params\[:id\]\)/, m
+ assert_match /@admin_role\.update_attributes\(params\[:admin_role\]\)/, 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
+ end
+
+ assert_file "test/functional/admin/roles_controller_test.rb",
+ /class Admin::RolesControllerTest < ActionController::TestCase/
+
+ # Views
+ %w(
+ index
+ edit
+ new
+ show
+ _form
+ ).each { |view| assert_file "app/views/admin/roles/#{view}.html.erb" }
+ assert_no_file "app/views/layouts/admin/roles.html.erb"
+
+ # Helpers
+ assert_file "app/helpers/admin/roles_helper.rb"
+ assert_file "test/unit/helpers/admin/roles_helper_test.rb"
+
+ # Stylesheets
+ assert_file "public/stylesheets/scaffold.css"
+ end
+
+ def test_scaffold_with_namespace_on_revoke
+ run_generator [ "admin/role", "name:string", "description:string" ]
+ run_generator [ "admin/role" ], :behavior => :revoke
+
+ # Model
+ assert_file "app/models/admin.rb" # ( should not be remove )
+ assert_no_file "app/models/admin/role.rb"
+ assert_no_file "test/unit/admin/role_test.rb"
+ assert_no_file "test/fixtures/admin/roles.yml"
+ assert_no_migration "db/migrate/create_admin_roles.rb"
+
+ # Route
+ assert_file "config/routes.rb" do |route|
+ assert_no_match /namespace :admin do resources :roles end$/, route
+ end
+
+ # Controller
+ assert_no_file "app/controllers/admin/roles_controller.rb"
+ assert_no_file "test/functional/admin/roles_controller_test.rb"
+
+ # Views
+ assert_no_file "app/views/admin/roles"
+ assert_no_file "app/views/layouts/admin/roles.html.erb"
+
+ # Helpers
+ assert_no_file "app/helpers/admin/roles_helper.rb"
+ assert_no_file "test/unit/helpers/admin/roles_helper_test.rb"
+
+ # Stylesheets (should not be removed)
+ assert_file "public/stylesheets/scaffold.css"
+ end
end
diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb
index 74301a5dc5..72c35879c5 100644
--- a/railties/test/initializable_test.rb
+++ b/railties/test/initializable_test.rb
@@ -5,10 +5,7 @@ module InitializableTests
class Foo
include Rails::Initializable
-
- class << self
- attr_accessor :foo, :bar
- end
+ attr_accessor :foo, :bar
initializer :start do
@foo ||= 0
@@ -158,30 +155,22 @@ module InitializableTests
include ActiveSupport::Testing::Isolation
test "initializers run" do
- Foo.run_initializers
- assert_equal 1, Foo.foo
+ foo = Foo.new
+ foo.run_initializers
+ assert_equal 1, foo.foo
end
test "initializers are inherited" do
- Bar.run_initializers
- assert_equal [1, 1], [Bar.foo, Bar.bar]
+ bar = Bar.new
+ bar.run_initializers
+ assert_equal [1, 1], [bar.foo, bar.bar]
end
test "initializers only get run once" do
- Foo.run_initializers
- Foo.run_initializers
- assert_equal 1, Foo.foo
- end
-
- test "running initializers on children does not effect the parent" do
- Bar.run_initializers
- assert_nil Foo.foo
- assert_nil Foo.bar
- end
-
- test "initializing with modules" do
- Word.run_initializers
- assert_equal "bird", $word
+ foo = Foo.new
+ foo.run_initializers
+ foo.run_initializers
+ assert_equal 1, foo.foo
end
test "creating initializer without a block raises an error" do
@@ -198,19 +187,19 @@ module InitializableTests
class BeforeAfter < ActiveSupport::TestCase
test "running on parent" do
$arr = []
- Parent.run_initializers
+ Parent.new.run_initializers
assert_equal [5, 1, 2], $arr
end
test "running on child" do
$arr = []
- Child.run_initializers
+ Child.new.run_initializers
assert_equal [5, 3, 1, 4, 2], $arr
end
test "handles dependencies introduced before all initializers are loaded" do
$arr = []
- Interdependent::Application.run_initializers
+ Interdependent::Application.new.run_initializers
assert_equal [1, 2, 3, 4], $arr
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index b46ac0efaf..390c0ab543 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -100,7 +100,7 @@ module TestHelpers
end
end
- add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"'
+ add_to_config 'config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"; config.session_store :cookie_store, :key => "_myapp_session"; config.active_support.deprecation = :log'
end
def make_basic_app
@@ -110,6 +110,7 @@ module TestHelpers
app = Class.new(Rails::Application)
app.config.secret_token = "3b7cd727ee24e8444053437c36cc66c4"
app.config.session_store :cookie_store, :key => "_myapp_session"
+ app.config.active_support.deprecation = :log
yield app if block_given?
app.initialize!
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 92c7b2ba0e..008247cb1a 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -120,36 +120,36 @@ class PathsTest < ActiveSupport::TestCase
test "it is possible to add a path that should be loaded only once" do
@root.app = "/app"
- @root.app.load_once!
- assert @root.app.load_once?
- assert @root.load_once.include?(@root.app.paths.first)
+ @root.app.autoload_once!
+ assert @root.app.autoload_once?
+ assert @root.autoload_once.include?(@root.app.paths.first)
end
test "it is possible to add a path without assignment and specify it should be loaded only once" do
- @root.app "/app", :load_once => true
- assert @root.app.load_once?
- assert @root.load_once.include?("/app")
+ @root.app "/app", :autoload_once => true
+ assert @root.app.autoload_once?
+ assert @root.autoload_once.include?("/app")
end
test "it is possible to add multiple paths without assignment and specify it should be loaded only once" do
- @root.app "/app", "/app2", :load_once => true
- assert @root.app.load_once?
- assert @root.load_once.include?("/app")
- assert @root.load_once.include?("/app2")
+ @root.app "/app", "/app2", :autoload_once => true
+ assert @root.app.autoload_once?
+ assert @root.autoload_once.include?("/app")
+ assert @root.autoload_once.include?("/app2")
end
- test "making a path load_once more than once only includes it once in @root.load_once" do
+ test "making a path autoload_once more than once only includes it once in @root.load_once" do
@root.app = "/app"
- @root.app.load_once!
- @root.app.load_once!
- assert_equal 1, @root.load_once.select {|p| p == @root.app.paths.first }.size
+ @root.app.autoload_once!
+ @root.app.autoload_once!
+ assert_equal 1, @root.autoload_once.select {|p| p == @root.app.paths.first }.size
end
- test "paths added to a load_once path should be added to the load_once collection" do
+ test "paths added to a load_once path should be added to the autoload_once collection" do
@root.app = "/app"
- @root.app.load_once!
+ @root.app.autoload_once!
@root.app << "/app2"
- assert_equal 2, @root.load_once.size
+ assert_equal 2, @root.autoload_once.size
end
test "it is possible to mark a path as eager" do
@@ -173,11 +173,11 @@ class PathsTest < ActiveSupport::TestCase
end
test "it is possible to create a path without assignment and mark it both as eager and load once" do
- @root.app "/app", :eager_load => true, :load_once => true
+ @root.app "/app", :eager_load => true, :autoload_once => true
assert @root.app.eager_load?
- assert @root.app.load_once?
+ assert @root.app.autoload_once?
assert @root.eager_load.include?("/app")
- assert @root.load_once.include?("/app")
+ assert @root.autoload_once.include?("/app")
end
test "making a path eager more than once only includes it once in @root.eager_paths" do
@@ -218,16 +218,16 @@ class PathsTest < ActiveSupport::TestCase
assert_equal ["/app"], @root.load_paths
end
- test "adding a path to the eager paths also adds it to the load path" do
+ test "a path can be marked as autoload path" do
@root.app = "app"
- @root.app.eager_load!
- assert_equal ["/foo/bar/app"], @root.load_paths
+ @root.app.autoload!
+ @root.app.models = "app/models"
+ assert_equal ["/foo/bar/app"], @root.autoload_paths
end
- test "adding a path to the load once paths also adds it to the load path" do
- @root.app = "app"
- @root.app.load_once!
- assert_equal ["/foo/bar/app"], @root.load_paths
+ test "a path can be marked as autoload on creation" do
+ @root.app "/app", :autoload => true
+ assert @root.app.autoload?
+ assert_equal ["/app"], @root.autoload_paths
end
-
end
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index b3f65fd00d..7410a10712 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -20,10 +20,6 @@ module RailtiesTest
end
end
- def reload_config
- :reload_engines
- end
-
test "Rails::Engine itself does not respond to config" do
boot_rails
assert !Rails::Engine.respond_to?(:config)
@@ -41,7 +37,7 @@ module RailtiesTest
boot_rails
- initializers = Rails.application.initializers
+ initializers = Rails.application.initializers.tsort
index = initializers.index { |i| i.name == "dummy_initializer" }
selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s)
diff --git a/railties/test/railties/i18n_railtie_test.rb b/railties/test/railties/i18n_railtie_test.rb
deleted file mode 100644
index 2b1950b3d5..0000000000
--- a/railties/test/railties/i18n_railtie_test.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require "isolation/abstract_unit"
-
-module RailtiesTest
- class I18nRailtieTest < Test::Unit::TestCase
- include ActiveSupport::Testing::Isolation
-
- def setup
- build_app
- boot_rails
- FileUtils.rm_rf("#{app_path}/config/environments")
- require "rails/all"
- end
-
- def load_app
- require "#{app_path}/config/environment"
- end
-
- def assert_fallbacks(fallbacks)
- fallbacks.each do |locale, expected|
- actual = I18n.fallbacks[locale]
- assert_equal expected, actual, "expected fallbacks for #{locale.inspect} to be #{expected.inspect}, but were #{actual.inspect}"
- end
- end
-
- def assert_no_fallbacks
- assert !I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- end
-
- test "config.i18n.load_path gets added to I18n.load_path" do
- I18n.load_path = ['existing/path/to/locales']
- I18n::Railtie.config.i18n.load_path = ['new/path/to/locales']
- load_app
-
- assert I18n.load_path.include?('existing/path/to/locales')
- assert I18n.load_path.include?('new/path/to/locales')
- end
-
- test "not using config.i18n.fallbacks does not initialize I18n.fallbacks" do
- I18n.backend = Class.new { include I18n::Backend::Base }.new
- load_app
- assert_no_fallbacks
- end
-
- test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings" do
- I18n::Railtie.config.i18n.fallbacks = true
- load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- assert_fallbacks :de => [:de, :en]
- end
-
- test "config.i18n.fallbacks = true initializes I18n.fallbacks with default settings even when backend changes" do
- I18n::Railtie.config.i18n.fallbacks = true
- I18n::Railtie.config.i18n.backend = Class.new { include I18n::Backend::Base }.new
- load_app
- assert I18n.backend.class.included_modules.include?(I18n::Backend::Fallbacks)
- assert_fallbacks :de => [:de, :en]
- end
-
- test "config.i18n.fallbacks.defaults = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
- I18n::Railtie.config.i18n.fallbacks.defaults = [:'en-US']
- load_app
- assert_fallbacks :de => [:de, :'en-US', :en]
- end
-
- test "config.i18n.fallbacks.map = { :ca => :'es-ES' } initializes fallbacks with a mapping ca => es-ES" do
- I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [:'en-US'] initializes fallbacks with en-US as a fallback default" do
- I18n::Railtie.config.i18n.fallbacks = [:'en-US']
- load_app
- assert_fallbacks :de => [:de, :'en-US', :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [{ :ca => :'es-ES' }] initializes fallbacks with a mapping de-AT => de-DE" do
- I18n::Railtie.config.i18n.fallbacks.map = { :ca => :'es-ES' }
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :en]
- end
-
- test "[shortcut] config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }] initializes fallbacks with the given arguments" do
- I18n::Railtie.config.i18n.fallbacks = [:'en-US', { :ca => :'es-ES' }]
- load_app
- assert_fallbacks :ca => [:ca, :"es-ES", :es, :'en-US', :en]
- end
- end
-end \ No newline at end of file
diff --git a/railties/test/railties/plugin_test.rb b/railties/test/railties/plugin_test.rb
index 0f5f29468c..b143f56484 100644
--- a/railties/test/railties/plugin_test.rb
+++ b/railties/test/railties/plugin_test.rb
@@ -15,10 +15,6 @@ module RailtiesTest
end
end
- def reload_config
- :reload_plugins
- end
-
test "Rails::Plugin itself does not respond to config" do
boot_rails
assert !Rails::Plugin.respond_to?(:config)
@@ -37,6 +33,32 @@ module RailtiesTest
assert_equal "Bukkits", Bukkits.name
end
+ test "plugin gets added to dependency list" do
+ boot_rails
+ assert_equal "Another", Another.name
+ end
+
+ test "plugin constants get reloaded if config.reload_plugins is set to true" do
+ add_to_config <<-RUBY
+ config.reload_plugins = true
+ RUBY
+
+ boot_rails
+
+ assert_equal "Another", Another.name
+ ActiveSupport::Dependencies.clear
+ @plugin.delete("lib/another.rb")
+ assert_raises(NameError) { Another }
+ end
+
+ test "plugin constants are not reloaded by default" do
+ boot_rails
+ assert_equal "Another", Another.name
+ ActiveSupport::Dependencies.clear
+ @plugin.delete("lib/another.rb")
+ assert_nothing_raised { Another }
+ end
+
test "it loads the plugin's init.rb file" do
boot_rails
assert_equal "loaded", BUKKITS
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index 2accaca855..c74cc01dc1 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -48,15 +48,6 @@ module RailtiesTest
assert_equal "hello", AppTemplate::Application.config.foo.greetings
end
- test "railtie can add log subscribers" do
- begin
- class Foo < Rails::Railtie ; log_subscriber(:foo, Rails::LogSubscriber.new) ; end
- assert_kind_of Rails::LogSubscriber, Rails::LogSubscriber.log_subscribers[0]
- ensure
- Rails::LogSubscriber.log_subscribers.clear
- end
- end
-
test "railtie can add to_prepare callbacks" do
$to_prepare = false
class Foo < Rails::Railtie ; config.to_prepare { $to_prepare = true } ; end
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index 3f78d7d3fe..ce7c55c11c 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -10,51 +10,25 @@ module RailtiesTest
@app ||= Rails.application
end
- def test_plugin_puts_its_lib_directory_on_load_path
+ def test_puts_its_lib_directory_on_load_path
boot_rails
require "another"
assert_equal "Another", Another.name
end
- def test_plugin_paths_get_added_to_as_dependency_list
- boot_rails
- assert_equal "Another", Another.name
- end
-
- def test_plugins_constants_are_not_reloaded_by_default
- boot_rails
- assert_equal "Another", Another.name
- ActiveSupport::Dependencies.clear
- @plugin.delete("lib/another.rb")
- assert_nothing_raised { Another }
- end
-
- def test_plugin_constants_get_reloaded_if_config_reload_plugins
- add_to_config <<-RUBY
- config.#{reload_config} = true
- RUBY
-
- boot_rails
-
- assert_equal "Another", Another.name
- ActiveSupport::Dependencies.clear
- @plugin.delete("lib/another.rb")
- assert_raises(NameError) { Another }
- end
-
- def test_plugin_puts_its_models_directory_on_load_path
+ def test_puts_its_models_directory_on_autoload_path
@plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end"
boot_rails
assert_nothing_raised { MyBukkit }
end
- def test_plugin_puts_its_controllers_directory_on_the_load_path
+ def test_puts_its_controllers_directory_on_autoload_path
@plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end"
boot_rails
assert_nothing_raised { BukkitController }
end
- def test_plugin_adds_its_views_to_view_paths
+ def test_adds_its_views_to_view_paths
@plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
class BukkitController < ActionController::Base
def index
@@ -72,7 +46,7 @@ module RailtiesTest
assert_equal "Hello bukkits\n", response[2].body
end
- def test_plugin_adds_its_views_to_view_paths_with_lower_proriority
+ def test_adds_its_views_to_view_paths_with_lower_proriority_than_app_ones
@plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
class BukkitController < ActionController::Base
def index
@@ -91,7 +65,7 @@ module RailtiesTest
assert_equal "Hi bukkits\n", response[2].body
end
- def test_plugin_adds_helpers_to_controller_views
+ def test_adds_helpers_to_controller_views
@plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY
class BukkitController < ActionController::Base
def index
@@ -116,11 +90,10 @@ module RailtiesTest
assert_equal "Hello bukkits\n", response[2].body
end
- def test_plugin_eager_load_any_path_under_app
+ def test_autoload_any_path_under_app
@plugin.write "app/anything/foo.rb", <<-RUBY
module Foo; end
RUBY
-
boot_rails
assert Foo
end
@@ -269,7 +242,7 @@ YAML
assert_equal "Rendered from namespace", last_response.body
end
- def test_plugin_initializers
+ def test_initializers
$plugin_initializer = false
@plugin.write "config/initializers/foo.rb", <<-RUBY
$plugin_initializer = true
@@ -279,7 +252,7 @@ YAML
assert $plugin_initializer
end
- def test_plugin_midleware_referenced_in_configuration
+ def test_midleware_referenced_in_configuration
@plugin.write "lib/bukkits.rb", <<-RUBY
class Bukkits
def initialize(app)
diff --git a/tools/profile b/tools/profile
index f02f1b5057..a6e3b41900 100755
--- a/tools/profile
+++ b/tools/profile
@@ -39,6 +39,7 @@ end
GC.start
before = GC.allocated_size
+before_gctime, before_gcruns = GC.time, GC.collections
before_rss = `ps -o rss= -p #{Process.pid}`.to_i
before_live_objects = ObjectSpace.live_objects
@@ -54,6 +55,7 @@ end
elapsed = Benchmark.realtime { require path }
results = RubyProf.stop if mode
+after_gctime, after_gcruns = GC.time, GC.collections
GC.start
after_live_objects = ObjectSpace.live_objects
after_rss = `ps -o rss= -p #{Process.pid}`.to_i
@@ -61,7 +63,9 @@ after = GC.allocated_size
usage = (after - before) / 1024.0
if mode
- if RubyProf.const_defined?(:CallStackPrinter)
+ if printer = ARGV.shift
+ RubyProf.const_get("#{printer.to_s.classify}Printer").new(results).print($stdout)
+ elsif RubyProf.const_defined?(:CallStackPrinter)
File.open("#{File.basename(path, '.rb')}.#{mode}.html", 'w') do |out|
RubyProf::CallStackPrinter.new(results).print(out)
end
@@ -79,4 +83,4 @@ RequireProfiler.stats.each do |file, depth, sec, bytes, objects|
puts "#{' ' * (42 + depth)}#{file}"
end
end
-puts "%10.2f KB %10d obj %8.1f ms %d KB RSS" % [usage, after_live_objects - before_live_objects, elapsed * 1000, after_rss - before_rss]
+puts "%10.2f KB %10d obj %8.1f ms %d KB RSS %8.1f ms GC time %d GC runs" % [usage, after_live_objects - before_live_objects, elapsed * 1000, after_rss - before_rss, (after_gctime - before_gctime) / 1000.0, after_gcruns - before_gcruns]
diff --git a/version.rb b/version.rb
index c10876134a..34d96fd0f3 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta3"
+ BUILD = "beta4"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end