aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--actionmailer/lib/action_mailer/base.rb31
-rw-r--r--actionmailer/test/delivery_method_test.rb22
-rw-r--r--actionmailer/test/mail_service_test.rb27
-rw-r--r--actionpack/CHANGELOG4
-rw-r--r--actionpack/examples/minimal.rb88
-rw-r--r--actionpack/examples/very_simple.rb10
-rw-r--r--actionpack/examples/views/_collection.erb1
-rw-r--r--actionpack/examples/views/_hello.erb1
-rw-r--r--actionpack/examples/views/_many_partials.erb10
-rw-r--r--actionpack/examples/views/_partial.erb10
-rw-r--r--actionpack/examples/views/template.html.erb2
-rw-r--r--actionpack/lib/abstract_controller.rb16
-rw-r--r--actionpack/lib/abstract_controller/base.rb (renamed from actionpack/lib/action_controller/abstract/base.rb)2
-rw-r--r--actionpack/lib/abstract_controller/benchmarker.rb (renamed from actionpack/lib/action_controller/abstract/benchmarker.rb)0
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb (renamed from actionpack/lib/action_controller/abstract/callbacks.rb)0
-rw-r--r--actionpack/lib/abstract_controller/exceptions.rb (renamed from actionpack/lib/action_controller/abstract/exceptions.rb)0
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb (renamed from actionpack/lib/action_controller/abstract/helpers.rb)12
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb (renamed from actionpack/lib/action_controller/abstract/layouts.rb)24
-rw-r--r--actionpack/lib/abstract_controller/logger.rb (renamed from actionpack/lib/action_controller/abstract/logger.rb)0
-rw-r--r--actionpack/lib/abstract_controller/rendering_controller.rb (renamed from actionpack/lib/action_controller/abstract/renderer.rb)22
-rw-r--r--actionpack/lib/action_controller.rb76
-rw-r--r--actionpack/lib/action_controller/abstract.rb16
-rw-r--r--actionpack/lib/action_controller/base.rb (renamed from actionpack/lib/action_controller/base/base.rb)10
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb3
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb4
-rw-r--r--actionpack/lib/action_controller/legacy/layout.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb (renamed from actionpack/lib/action_controller/base/http.rb)12
-rw-r--r--actionpack/lib/action_controller/metal/compatibility.rb (renamed from actionpack/lib/action_controller/base/compatibility.rb)6
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb (renamed from actionpack/lib/action_controller/base/conditional_get.rb)30
-rw-r--r--actionpack/lib/action_controller/metal/cookies.rb (renamed from actionpack/lib/action_controller/base/cookies.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb (renamed from actionpack/lib/action_controller/base/exceptions.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/filter_parameter_logging.rb (renamed from actionpack/lib/action_controller/base/filter_parameter_logging.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb (renamed from actionpack/lib/action_controller/base/flash.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb (renamed from actionpack/lib/action_controller/base/helpers.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb (renamed from actionpack/lib/action_controller/base/hide_actions.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb (renamed from actionpack/lib/action_controller/base/http_authentication.rb)5
-rw-r--r--actionpack/lib/action_controller/metal/layouts.rb (renamed from actionpack/lib/action_controller/base/layouts.rb)2
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb (renamed from actionpack/lib/action_controller/base/mime_responds.rb)115
-rw-r--r--actionpack/lib/action_controller/metal/rack_convenience.rb (renamed from actionpack/lib/action_controller/base/rack_convenience.rb)2
-rw-r--r--actionpack/lib/action_controller/metal/redirector.rb (renamed from actionpack/lib/action_controller/base/redirector.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/render_options.rb (renamed from actionpack/lib/action_controller/base/render_options.rb)24
-rw-r--r--actionpack/lib/action_controller/metal/rendering_controller.rb (renamed from actionpack/lib/action_controller/base/renderer.rb)22
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb (renamed from actionpack/lib/action_controller/base/request_forgery_protection.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/rescuable.rb (renamed from actionpack/lib/action_controller/base/rescuable.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb181
-rw-r--r--actionpack/lib/action_controller/metal/session.rb (renamed from actionpack/lib/action_controller/base/session.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/session_management.rb (renamed from actionpack/lib/action_controller/base/session_management.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb (renamed from actionpack/lib/action_controller/base/streaming.rb)8
-rw-r--r--actionpack/lib/action_controller/metal/testing.rb (renamed from actionpack/lib/action_controller/base/testing.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb (renamed from actionpack/lib/action_controller/base/url_for.rb)0
-rw-r--r--actionpack/lib/action_controller/metal/verification.rb (renamed from actionpack/lib/action_controller/base/verification.rb)4
-rw-r--r--actionpack/lib/action_controller/old_base.rb84
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb15
-rw-r--r--actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb28
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb7
-rw-r--r--actionpack/lib/action_controller/testing/process.rb2
-rwxr-xr-xactionpack/lib/action_dispatch/http/request.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb68
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/selector.rb13
-rw-r--r--actionpack/lib/action_view/base.rb42
-rw-r--r--actionpack/lib/action_view/context.rb4
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb12
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb21
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb32
-rw-r--r--actionpack/lib/action_view/paths.rb10
-rw-r--r--actionpack/lib/action_view/render/partials.rb190
-rw-r--r--actionpack/lib/action_view/render/rendering.rb107
-rw-r--r--actionpack/lib/action_view/template/resolver.rb2
-rw-r--r--actionpack/lib/action_view/template/template.rb12
-rw-r--r--actionpack/lib/action_view/template/text.rb4
-rw-r--r--actionpack/lib/action_view/test_case.rb6
-rw-r--r--actionpack/test/abstract_controller/abstract_controller_test.rb10
-rw-r--r--actionpack/test/abstract_controller/helper_test.rb4
-rw-r--r--actionpack/test/abstract_controller/layouts_test.rb2
-rw-r--r--actionpack/test/abstract_controller/test_helper.rb2
-rw-r--r--actionpack/test/abstract_unit.rb3
-rw-r--r--actionpack/test/activerecord/polymorphic_routes_test.rb55
-rw-r--r--actionpack/test/controller/assert_select_test.rb7
-rw-r--r--actionpack/test/controller/caching_test.rb15
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb25
-rw-r--r--actionpack/test/controller/http_digest_authentication_test.rb35
-rw-r--r--actionpack/test/controller/logging_test.rb3
-rw-r--r--actionpack/test/controller/mime_responds_test.rb198
-rw-r--r--actionpack/test/controller/record_identifier_test.rb57
-rw-r--r--actionpack/test/controller/redirect_test.rb10
-rw-r--r--actionpack/test/controller/render_test.rb23
-rw-r--r--actionpack/test/controller/render_xml_test.rb4
-rw-r--r--actionpack/test/controller/routing_test.rb2
-rw-r--r--actionpack/test/controller/send_file_test.rb17
-rw-r--r--actionpack/test/dispatch/request/multipart_params_parsing_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb28
-rw-r--r--actionpack/test/dispatch/response_test.rb10
-rw-r--r--actionpack/test/fixtures/respond_with/edit.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/new.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.html.erb1
-rw-r--r--actionpack/test/fixtures/respond_with/using_resource.js.rjs1
-rw-r--r--actionpack/test/fixtures/test/greeting.xml.erb1
-rw-r--r--actionpack/test/lib/controller/fake_models.rb22
-rw-r--r--actionpack/test/new_base/base_test.rb3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb16
-rw-r--r--actionpack/test/template/atom_feed_helper_test.rb29
-rw-r--r--actionpack/test/template/body_parts_test.rb2
-rw-r--r--actionpack/test/template/form_helper_test.rb10
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb12
-rw-r--r--actionpack/test/template/number_helper_test.rb1
-rw-r--r--actionpack/test/template/tag_helper_test.rb13
-rw-r--r--actionpack/test/template/text_helper_test.rb17
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb267
-rw-r--r--activemodel/lib/active_model/errors.rb6
-rw-r--r--activemodel/lib/active_model/state_machine.rb7
-rw-r--r--activemodel/lib/active_model/state_machine/event.rb12
-rw-r--r--activemodel/lib/active_model/state_machine/machine.rb29
-rw-r--r--activemodel/lib/active_model/state_machine/state_transition.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb24
-rw-r--r--activemodel/lib/active_model/validations/length.rb21
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb23
-rw-r--r--activemodel/test/cases/validations/presence_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations_test.rb14
-rw-r--r--activemodel/test/models/custom_reader.rb17
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/Rakefile4
-rw-r--r--activerecord/lib/active_record.rb12
-rwxr-xr-xactiverecord/lib/active_record/associations.rb4
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb372
-rw-r--r--activerecord/lib/active_record/attribute_methods/before_type_cast.rb33
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb204
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb44
-rw-r--r--activerecord/lib/active_record/attribute_methods/query.rb37
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb116
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb60
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb37
-rwxr-xr-xactiverecord/lib/active_record/base.rb115
-rw-r--r--activerecord/lib/active_record/calculations.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb1
-rw-r--r--activerecord/lib/active_record/dirty.rb186
-rw-r--r--activerecord/lib/active_record/migration.rb25
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb1
-rw-r--r--activerecord/lib/active_record/state_machine.rb24
-rw-r--r--activerecord/test/cases/adapter_test.rb25
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/eager_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb80
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb10
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb75
-rw-r--r--activerecord/test/cases/autosave_association_test.rb46
-rwxr-xr-xactiverecord/test/cases/base_test.rb91
-rw-r--r--activerecord/test/cases/calculations_test.rb13
-rw-r--r--activerecord/test/cases/database_statements_test.rb9
-rw-r--r--activerecord/test/cases/dirty_test.rb20
-rw-r--r--activerecord/test/cases/finder_test.rb111
-rw-r--r--activerecord/test/cases/fixtures_test.rb2
-rw-r--r--activerecord/test/cases/inheritance_test.rb3
-rw-r--r--activerecord/test/cases/invalid_date_test.rb14
-rw-r--r--activerecord/test/cases/method_scoping_test.rb3
-rw-r--r--activerecord/test/cases/migration_test.rb83
-rw-r--r--activerecord/test/cases/modules_test.rb42
-rw-r--r--activerecord/test/cases/named_scope_test.rb3
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb37
-rw-r--r--activerecord/test/cases/query_cache_test.rb7
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb20
-rw-r--r--activerecord/test/cases/state_machine_test.rb42
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb23
-rw-r--r--activerecord/test/connections/native_oracle/connection.rb55
-rw-r--r--activerecord/test/models/company.rb8
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/organization.rb2
-rw-r--r--activerecord/test/models/subject.rb10
-rw-r--r--activerecord/test/models/traffic_light.rb27
-rw-r--r--activerecord/test/schema/oracle_specific_schema.rb27
-rw-r--r--activerecord/test/schema/schema.rb38
-rw-r--r--activeresource/CHANGELOG5
-rw-r--r--activeresource/lib/active_resource/base.rb22
-rw-r--r--activeresource/lib/active_resource/connection.rb32
-rw-r--r--activeresource/test/base_test.rb64
-rw-r--r--activeresource/test/connection_test.rb21
-rw-r--r--activeresource/test/fixtures/proxy.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/string/bytesize.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/interpolation.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb16
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb18
-rw-r--r--activesupport/lib/active_support/json/backends/yaml.rb17
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb2
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb9
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb46
-rw-r--r--activesupport/test/deprecation_test.rb4
-rw-r--r--activesupport/test/json/decoding_test.rb8
-rw-r--r--railties/lib/commands/server.rb7
-rw-r--r--railties/lib/generators.rb66
-rw-r--r--railties/lib/generators/actions.rb11
-rw-r--r--railties/lib/generators/active_model.rb (renamed from railties/lib/generators/action_orm.rb)14
-rw-r--r--railties/lib/generators/active_record.rb4
-rw-r--r--railties/lib/generators/active_record/model/model_generator.rb6
-rw-r--r--railties/lib/generators/active_record/session_migration/session_migration_generator.rb6
-rw-r--r--railties/lib/generators/named_base.rb13
-rw-r--r--railties/lib/generators/rails/app/app_generator.rb21
-rw-r--r--railties/lib/generators/rails/app/templates/config/boot.rb2
-rw-r--r--railties/lib/generators/rails/app/templates/config/environment.rb10
-rw-r--r--railties/lib/generators/rails/mailer/templates/mailer.rb3
-rw-r--r--railties/lib/generators/test_unit/plugin/templates/test_helper.rb4
-rw-r--r--railties/lib/initializer.rb1
-rw-r--r--railties/lib/rails/configuration.rb3
-rw-r--r--railties/lib/tasks/databases.rake23
-rw-r--r--railties/lib/tasks/routes.rake7
-rw-r--r--railties/lib/vendor/thor-0.11.3/lib/thor/error.rb27
-rw-r--r--railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb4
-rw-r--r--railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb35
-rw-r--r--railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb31
-rw-r--r--railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb70
-rw-r--r--railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc (renamed from railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc)6
-rw-r--r--railties/lib/vendor/thor-0.11.5/LICENSE (renamed from railties/lib/vendor/thor-0.11.3/LICENSE)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/README.rdoc (renamed from railties/lib/vendor/thor-0.11.3/README.markdown)75
-rwxr-xr-xrailties/lib/vendor/thor-0.11.5/bin/rake2thor (renamed from railties/lib/vendor/thor-0.11.3/bin/rake2thor)0
-rwxr-xr-xrailties/lib/vendor/thor-0.11.5/bin/thor (renamed from railties/lib/vendor/thor-0.11.3/bin/thor)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor.rb)39
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb)30
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/base.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/base.rb)42
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb)4
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb)4
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/error.rb27
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/group.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/group.rb)6
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb)42
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb)0
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb67
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb)22
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb)2
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb)38
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb)16
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/task.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/task.rb)24
-rw-r--r--railties/lib/vendor/thor-0.11.5/lib/thor/util.rb (renamed from railties/lib/vendor/thor-0.11.3/lib/thor/util.rb)41
-rw-r--r--railties/test/fixtures/lib/template.rb1
-rw-r--r--railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb (renamed from railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb)0
-rw-r--r--railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb (renamed from railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb)0
-rw-r--r--railties/test/generators/actions_test.rb15
-rw-r--r--railties/test/generators/app_generator_test.rb5
-rw-r--r--railties/test/generators/generators_test_helper.rb2
-rw-r--r--railties/test/generators/model_generator_test.rb44
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb2
-rw-r--r--railties/test/generators/session_migration_generator_test.rb18
-rw-r--r--railties/test/initializer/path_test.rb3
264 files changed, 3933 insertions, 2176 deletions
diff --git a/.gitignore b/.gitignore
index 2d879499fa..43c4d7b124 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ actionpack/pkg
activemodel/test/fixtures/fixture_database.sqlite3
actionmailer/pkg
activesupport/pkg
+activesupport/test/fixtures/isolation_test
railties/pkg
railties/test/500.html
railties/test/fixtures/tmp
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index b5a0d0ab96..5ecefe7c09 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -1,3 +1,5 @@
+require 'tmpdir'
+
require "active_support/core_ext/class"
# Use the old layouts until actionmailer gets refactored
require "action_controller/legacy/layout"
@@ -224,9 +226,13 @@ module ActionMailer #:nodoc:
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
# * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt>.
#
+ # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
+ # * <tt>:location</tt> - The directory into which emails will be written. Defaults to the application <tt>tmp/mails</tt>.
+ #
# * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered.
#
- # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, and <tt>:test</tt>.
+ # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:test</tt>,
+ # and <tt>:file</tt>.
#
# * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are,
# but this can be turned off to help functional testing.
@@ -279,6 +285,12 @@ module ActionMailer #:nodoc:
}
cattr_accessor :sendmail_settings
+ @@file_settings = {
+ :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
+ }
+
+ cattr_accessor :file_settings
+
@@raise_delivery_errors = true
cattr_accessor :raise_delivery_errors
@@ -499,7 +511,7 @@ module ActionMailer #:nodoc:
# ====
# TODO: Revisit this
# template_exists = @parts.empty?
- # template_exists ||= template_root.find_by_parts("#{mailer_name}/#{@template}")
+ # template_exists ||= template_root.find("#{mailer_name}/#{@template}")
# @body = render_message(@template, @body) if template_exists
# Finally, if there are other message parts and a textual body exists,
@@ -556,6 +568,7 @@ module ActionMailer #:nodoc:
@headers ||= {}
@body ||= {}
@mime_version = @@default_mime_version.dup if @@default_mime_version
+ @sent_on ||= Time.now
end
def render_template(template, body)
@@ -566,7 +579,7 @@ module ActionMailer #:nodoc:
@template = initialize_template_class(body)
layout = _pick_layout(layout, true) unless
ActionController::Base.exempt_from_layout.include?(template.handler)
- @template._render_template_with_layout(template, layout, {})
+ @template._render_template(template, layout, {})
ensure
@current_template_content_type = nil
end
@@ -585,14 +598,14 @@ module ActionMailer #:nodoc:
if file
prefix = mailer_name unless file =~ /\//
- template = view_paths.find_by_parts(file, {:formats => formats}, prefix)
+ template = view_paths.find(file, {:formats => formats}, prefix)
end
layout = _pick_layout(layout,
!template || ActionController::Base.exempt_from_layout.include?(template.handler))
if template
- @template._render_template_with_layout(template, layout, opts)
+ @template._render_template(template, layout, opts)
elsif inline = opts[:inline]
@template._render_inline(inline, layout, opts)
end
@@ -723,6 +736,14 @@ module ActionMailer #:nodoc:
def perform_delivery_test(mail)
deliveries << mail
end
+
+ def perform_delivery_file(mail)
+ FileUtils.mkdir_p file_settings[:location]
+
+ (mail.to + mail.cc + mail.bcc).uniq.each do |to|
+ File.open(File.join(file_settings[:location], to), 'a') { |f| f.write(mail) }
+ end
+ end
end
Base.class_eval do
diff --git a/actionmailer/test/delivery_method_test.rb b/actionmailer/test/delivery_method_test.rb
index 0731512ea4..1b8c3ba523 100644
--- a/actionmailer/test/delivery_method_test.rb
+++ b/actionmailer/test/delivery_method_test.rb
@@ -7,6 +7,10 @@ class NonDefaultDeliveryMethodMailer < ActionMailer::Base
self.delivery_method = :sendmail
end
+class FileDeliveryMethodMailer < ActionMailer::Base
+ self.delivery_method = :file
+end
+
class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase
def setup
set_delivery_method :smtp
@@ -49,3 +53,21 @@ class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase
end
end
+class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase
+ def setup
+ set_delivery_method :smtp
+ end
+
+ def teardown
+ restore_delivery_method
+ end
+
+ def test_should_be_the_set_delivery_method
+ assert_equal :file, FileDeliveryMethodMailer.delivery_method
+ end
+
+ def test_should_default_location_to_the_tmpdir
+ assert_equal "#{Dir.tmpdir}/mails", ActionMailer::Base.file_settings[:location]
+ end
+end
+
diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb
index 30d1b836c4..5584afa8be 100644
--- a/actionmailer/test/mail_service_test.rb
+++ b/actionmailer/test/mail_service_test.rb
@@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base
@recipients = recipient
@subject = "[Signed up] Welcome #{recipient}"
@from = "system@loudthinking.com"
- @sent_on = Time.local(2004, 12, 12)
@body["recipient"] = recipient
end
@@ -357,12 +356,14 @@ class ActionMailerTest < Test::Unit::TestCase
end
def test_signed_up
+ Time.stubs(:now => Time.now)
+
expected = new_mail
expected.to = @recipient
expected.subject = "[Signed up] Welcome #{@recipient}"
expected.body = "Hello there, \n\nMr. #{@recipient}"
expected.from = "system@loudthinking.com"
- expected.date = Time.local(2004, 12, 12)
+ expected.date = Time.now
created = nil
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
@@ -573,12 +574,14 @@ class ActionMailerTest < Test::Unit::TestCase
@info_contents, @debug_contents = "", ""
end
- def info(str)
- @info_contents << str
+ def info(str = nil, &blk)
+ @info_contents << str if str
+ @info_contents << blk.call if block_given?
end
- def debug(str)
- @debug_contents << str
+ def debug(str = nil, &blk)
+ @debug_contents << str if str
+ @debug_contents << blk.call if block_given?
end
end
@@ -886,6 +889,18 @@ EOF
assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
end
+ def test_file_delivery_should_create_a_file
+ ActionMailer::Base.delivery_method = :file
+ tmp_location = ActionMailer::Base.file_settings[:location]
+
+ TestMailer.deliver_cc_bcc(@recipient)
+ assert File.exists?(tmp_location)
+ assert File.directory?(tmp_location)
+ assert File.exists?(File.join(tmp_location, @recipient))
+ assert File.exists?(File.join(tmp_location, 'nobody@loudthinking.com'))
+ assert File.exists?(File.join(tmp_location, 'root@loudthinking.com'))
+ end
+
def test_recursive_multipart_processing
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
mail = TMail::Mail.parse(fixture)
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index e758983d7a..75de1fe2a6 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs]
+
+* Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov]
+
* Ruby 1.9: ERB template encoding using a magic comment at the top of the file. [Jeremy Kemper]
<%# encoding: utf-8 %>
diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb
index 9eb92cd8e7..a9015da053 100644
--- a/actionpack/examples/minimal.rb
+++ b/actionpack/examples/minimal.rb
@@ -3,13 +3,19 @@ ENV['RAILS_ENV'] ||= 'production'
ENV['NO_RELOAD'] ||= '1'
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"
+$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
require 'action_controller'
require 'action_controller/new_base' if ENV['NEW']
+require 'action_view'
require 'benchmark'
class Runner
- def initialize(app)
- @app = app
+ def initialize(app, output)
+ @app, @output = app, output
+ end
+
+ def puts(*)
+ super if @output
end
def call(env)
@@ -18,16 +24,22 @@ class Runner
end
def report(env, response)
+ return unless ENV["DEBUG"]
out = env['rack.errors']
out.puts response[0], response[1].to_yaml, '---'
response[2].each { |part| out.puts part }
out.puts '---'
end
- def self.run(app, n, label = nil)
- puts '=' * label.size, label, '=' * label.size if label
- env = { 'n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout }
- t = Benchmark.realtime { new(app).call(env) }
+ def self.puts(*)
+ super if @output
+ end
+
+ def self.run(app, n, label, output = true)
+ @output = output
+ puts label, '=' * label.size if label
+ env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout)
+ t = Benchmark.realtime { new(app, output).call(env) }
puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n]
puts
end
@@ -36,23 +48,71 @@ end
N = (ENV['N'] || 1000).to_i
+module ActionController::Rails2Compatibility
+ instance_methods.each do |name|
+ remove_method name
+ end
+end
+
class BasePostController < ActionController::Base
+ append_view_path "#{File.dirname(__FILE__)}/views"
+
+ def overhead
+ self.response_body = ''
+ end
+
def index
render :text => ''
end
+
+ def partial
+ render :partial => "/partial"
+ end
+
+ def many_partials
+ render :partial => "/many_partials"
+ end
+
+ def partial_collection
+ render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10]
+ end
+
+ def show_template
+ render :template => "template"
+ end
end
OK = [200, {}, []]
MetalPostController = lambda { OK }
-if ActionController.const_defined?(:Http)
- class HttpPostController < ActionController::Http
- def index
- self.response_body = ''
- end
+class HttpPostController < ActionController::Metal
+ def index
+ self.response_body = ''
end
end
-Runner.run(MetalPostController, N, 'metal')
-Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController
-Runner.run(BasePostController.action(:index), N, 'base')
+unless ENV["PROFILE"]
+ Runner.run(BasePostController.action(:overhead), N, 'overhead', false)
+ Runner.run(BasePostController.action(:index), N, 'index', false)
+ Runner.run(BasePostController.action(:partial), N, 'partial', false)
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false)
+ Runner.run(BasePostController.action(:partial_collection), N, 'collection', false)
+ Runner.run(BasePostController.action(:show_template), N, 'template', false)
+
+ (ENV["M"] || 1).to_i.times do
+ Runner.run(BasePostController.action(:overhead), N, 'overhead')
+ Runner.run(BasePostController.action(:index), N, 'index')
+ Runner.run(BasePostController.action(:partial), N, 'partial')
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ Runner.run(BasePostController.action(:partial_collection), N, 'collection')
+ Runner.run(BasePostController.action(:show_template), N, 'template')
+ end
+else
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ require "ruby-prof"
+ RubyProf.start
+ Runner.run(BasePostController.action(:many_partials), N, 'many_partials')
+ result = RubyProf.stop
+ printer = RubyProf::CallStackPrinter.new(result)
+ printer.print(File.open("output.html", "w"))
+end \ No newline at end of file
diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb
index 8d01cb39bd..6714185172 100644
--- a/actionpack/examples/very_simple.rb
+++ b/actionpack/examples/very_simple.rb
@@ -3,17 +3,17 @@ $:.push "rails/actionpack/lib"
require "action_controller"
-class Kaigi < ActionController::Http
+class Kaigi < ActionController::Metal
include AbstractController::Callbacks
include ActionController::RackConvenience
- include ActionController::Renderer
+ include ActionController::RenderingController
include ActionController::Layouts
include ActionView::Context
before_filter :set_name
append_view_path "views"
- def _action_view
+ def view_context
self
end
@@ -23,7 +23,7 @@ class Kaigi < ActionController::Http
DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end }
- def _render_template_from_controller(template, layout = DEFAULT_LAYOUT, options = {}, partial = false)
+ def render_template(template, layout = DEFAULT_LAYOUT, options = {}, partial = false)
ret = template.render(self, {})
layout.render(self, {}) { ret }
end
@@ -47,4 +47,4 @@ app = Rack::Builder.new do
map("/kaigi/alt") { run Kaigi.action(:alt) }
end.to_app
-Rack::Handler::Mongrel.run app, :Port => 3000 \ No newline at end of file
+Rack::Handler::Mongrel.run app, :Port => 3000
diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb
new file mode 100644
index 0000000000..bcfe958e2c
--- /dev/null
+++ b/actionpack/examples/views/_collection.erb
@@ -0,0 +1 @@
+<%= collection %> \ No newline at end of file
diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb
new file mode 100644
index 0000000000..5ab2f8a432
--- /dev/null
+++ b/actionpack/examples/views/_hello.erb
@@ -0,0 +1 @@
+Hello \ No newline at end of file
diff --git a/actionpack/examples/views/_many_partials.erb b/actionpack/examples/views/_many_partials.erb
new file mode 100644
index 0000000000..7e379d46f5
--- /dev/null
+++ b/actionpack/examples/views/_many_partials.erb
@@ -0,0 +1,10 @@
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %>
+<%= render :partial => '/hello' %> \ No newline at end of file
diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb
new file mode 100644
index 0000000000..3ca8e80b52
--- /dev/null
+++ b/actionpack/examples/views/_partial.erb
@@ -0,0 +1,10 @@
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
+<%= "Hello" %>
diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb
index 3108e9ad70..5ab2f8a432 100644
--- a/actionpack/examples/views/template.html.erb
+++ b/actionpack/examples/views/template.html.erb
@@ -1 +1 @@
-Hello <%= @name %> \ No newline at end of file
+Hello \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
new file mode 100644
index 0000000000..cdeb55b915
--- /dev/null
+++ b/actionpack/lib/abstract_controller.rb
@@ -0,0 +1,16 @@
+require "active_support/core_ext/module/attr_internal"
+require "active_support/core_ext/module/delegation"
+
+module AbstractController
+ autoload :Base, "abstract_controller/base"
+ autoload :Benchmarker, "abstract_controller/benchmarker"
+ autoload :Callbacks, "abstract_controller/callbacks"
+ autoload :Helpers, "abstract_controller/helpers"
+ autoload :Layouts, "abstract_controller/layouts"
+ autoload :Logger, "abstract_controller/logger"
+ autoload :RenderingController, "abstract_controller/rendering_controller"
+ # === Exceptions
+ autoload :ActionNotFound, "abstract_controller/exceptions"
+ autoload :DoubleRenderError, "abstract_controller/exceptions"
+ autoload :Error, "abstract_controller/exceptions"
+end
diff --git a/actionpack/lib/action_controller/abstract/base.rb b/actionpack/lib/abstract_controller/base.rb
index ca00e66349..b93e6ce634 100644
--- a/actionpack/lib/action_controller/abstract/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -30,7 +30,7 @@ module AbstractController
# instance methods on that abstract class. Public instance methods of
# a controller would normally be considered action methods, so we
# are removing those methods on classes declared as abstract
- # (ActionController::Http and ActionController::Base are defined
+ # (ActionController::Metal and ActionController::Base are defined
# as abstract)
def internal_methods
controller = self
diff --git a/actionpack/lib/action_controller/abstract/benchmarker.rb b/actionpack/lib/abstract_controller/benchmarker.rb
index 58e9564c2f..58e9564c2f 100644
--- a/actionpack/lib/action_controller/abstract/benchmarker.rb
+++ b/actionpack/lib/abstract_controller/benchmarker.rb
diff --git a/actionpack/lib/action_controller/abstract/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index ea4b59466e..ea4b59466e 100644
--- a/actionpack/lib/action_controller/abstract/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
diff --git a/actionpack/lib/action_controller/abstract/exceptions.rb b/actionpack/lib/abstract_controller/exceptions.rb
index b671516de1..b671516de1 100644
--- a/actionpack/lib/action_controller/abstract/exceptions.rb
+++ b/actionpack/lib/abstract_controller/exceptions.rb
diff --git a/actionpack/lib/action_controller/abstract/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 5efa37fde3..f3072fad74 100644
--- a/actionpack/lib/action_controller/abstract/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -2,10 +2,18 @@ module AbstractController
module Helpers
extend ActiveSupport::Concern
- include Renderer
+ include RenderingController
+
+ def self.next_serial
+ @helper_serial ||= 0
+ @helper_serial += 1
+ end
included do
extlib_inheritable_accessor(:_helpers) { Module.new }
+ extlib_inheritable_accessor(:_helper_serial) do
+ AbstractController::Helpers.next_serial
+ end
end
module ClassMethods
@@ -58,6 +66,8 @@ module AbstractController
# of the helper module. Any methods defined in the block
# will be helpers.
def helper(*args, &block)
+ self._helper_serial = AbstractController::Helpers.next_serial + 1
+
args.flatten.each do |arg|
case arg
when Module
diff --git a/actionpack/lib/action_controller/abstract/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index f021dd8b62..0063d54149 100644
--- a/actionpack/lib/action_controller/abstract/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -2,7 +2,7 @@ module AbstractController
module Layouts
extend ActiveSupport::Concern
- include Renderer
+ include RenderingController
included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
@@ -76,7 +76,7 @@ module AbstractController
when nil
self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
def _layout(details)
- if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts")
+ if view_paths.exists?("#{_implied_layout_name}", details, "layouts")
"#{_implied_layout_name}"
else
super
@@ -88,6 +88,24 @@ module AbstractController
end
end
+ def render_to_body(options = {})
+ # In the case of a partial with a layout, handle the layout
+ # here, and make sure the view does not try to handle it
+ layout = options.delete(:layout) if options.key?(:partial)
+
+ response = super
+
+ # This is a little bit messy. We need to explicitly handle partial
+ # layouts here since the core lookup logic is in the view, but
+ # we need to determine the layout based on the controller
+ if layout
+ layout = _layout_for_option(layout, options[:_template].details)
+ response = layout.render(view_context, options[:locals] || {}) { response }
+ end
+
+ response
+ end
+
private
# This will be overwritten by _write_layout_method
def _layout(details) end
@@ -115,7 +133,7 @@ module AbstractController
def _find_layout(name, details)
# TODO: Make prefix actually part of details in ViewPath#find_by_parts
prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts"
- view_paths.find_by_parts(name, details, prefix)
+ view_paths.find(name, details, prefix)
end
# Returns the default layout for this controller and a given set of details.
diff --git a/actionpack/lib/action_controller/abstract/logger.rb b/actionpack/lib/abstract_controller/logger.rb
index fd33bd2ddd..fd33bd2ddd 100644
--- a/actionpack/lib/action_controller/abstract/logger.rb
+++ b/actionpack/lib/abstract_controller/logger.rb
diff --git a/actionpack/lib/action_controller/abstract/renderer.rb b/actionpack/lib/abstract_controller/rendering_controller.rb
index 41b7d47458..bb7891fbfd 100644
--- a/actionpack/lib/action_controller/abstract/renderer.rb
+++ b/actionpack/lib/abstract_controller/rendering_controller.rb
@@ -1,7 +1,7 @@
-require "action_controller/abstract/logger"
+require "abstract_controller/logger"
module AbstractController
- module Renderer
+ module RenderingController
extend ActiveSupport::Concern
include AbstractController::Logger
@@ -19,11 +19,11 @@ module AbstractController
# The view class must have the following methods:
# View.for_controller[controller] Create a new ActionView instance for a
# controller
- # View#_render_partial_from_controller[options]
+ # View#render_partial[options]
# - responsible for setting options[:_template]
# - Returns String with the rendered partial
# options<Hash>:: see _render_partial in ActionView::Base
- # View#_render_template_from_controller[template, layout, options, partial]
+ # View#render_template[template, layout, options, partial]
# - Returns String with the rendered template
# template<ActionView::Template>:: The template to render
# layout<ActionView::Template>:: The layout to render around the template
@@ -31,8 +31,8 @@ module AbstractController
# partial<Boolean>:: Whether or not the template to render is a partial
#
# Override this method in a to change the default behavior.
- def _action_view
- @_action_view ||= ActionView::Base.for_controller(self)
+ def view_context
+ @_view_context ||= ActionView::Base.for_controller(self)
end
# Mostly abstracts the fact that calling render twice is a DoubleRenderError.
@@ -54,8 +54,8 @@ module AbstractController
# :api: plugin
def render_to_body(options = {})
# TODO: Refactor so we can just use the normal template logic for this
- if options[:_partial_object]
- _action_view._render_partial_from_controller(options)
+ if options.key?(:partial)
+ view_context.render_partial(options)
else
_determine_template(options)
_render_template(options)
@@ -67,7 +67,7 @@ module AbstractController
#
# :api: plugin
def render_to_string(options = {})
- AbstractController::Renderer.body_to_s(render_to_body(options))
+ AbstractController::RenderingController.body_to_s(render_to_body(options))
end
# Renders the template from an object.
@@ -77,7 +77,7 @@ module AbstractController
# _layout<ActionView::Template>:: The layout to wrap the template in (optional)
# _partial<TrueClass, FalseClass>:: Whether or not the template to be rendered is a partial
def _render_template(options)
- _action_view._render_template_from_controller(options[:_template], options[:_layout], options, options[:_partial])
+ view_context.render_template(options)
end
# The list of view paths for this controller. See ActionView::ViewPathSet for
@@ -111,7 +111,7 @@ module AbstractController
def _determine_template(options)
name = (options[:_template_name] || action_name).to_s
- options[:_template] ||= view_paths.find_by_parts(
+ options[:_template] ||= view_paths.find(
name, { :formats => formats }, options[:_prefix], options[:_partial]
)
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 32572c93c0..37ff618edd 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -1,62 +1,62 @@
module ActionController
- autoload :Base, "action_controller/base/base"
- autoload :ConditionalGet, "action_controller/base/conditional_get"
- autoload :HideActions, "action_controller/base/hide_actions"
- autoload :Http, "action_controller/base/http"
- autoload :Layouts, "action_controller/base/layouts"
- autoload :RackConvenience, "action_controller/base/rack_convenience"
- autoload :Rails2Compatibility, "action_controller/base/compatibility"
- autoload :Redirector, "action_controller/base/redirector"
- autoload :Renderer, "action_controller/base/renderer"
- autoload :RenderOptions, "action_controller/base/render_options"
- autoload :Renderers, "action_controller/base/render_options"
- autoload :Rescue, "action_controller/base/rescuable"
- autoload :Testing, "action_controller/base/testing"
- autoload :UrlFor, "action_controller/base/url_for"
- autoload :Session, "action_controller/base/session"
- autoload :Helpers, "action_controller/base/helpers"
+ autoload :Base, "action_controller/base"
+ autoload :ConditionalGet, "action_controller/metal/conditional_get"
+ autoload :HideActions, "action_controller/metal/hide_actions"
+ autoload :Metal, "action_controller/metal"
+ autoload :Layouts, "action_controller/metal/layouts"
+ autoload :RackConvenience, "action_controller/metal/rack_convenience"
+ autoload :Rails2Compatibility, "action_controller/metal/compatibility"
+ autoload :Redirector, "action_controller/metal/redirector"
+ autoload :RenderingController, "action_controller/metal/rendering_controller"
+ autoload :RenderOptions, "action_controller/metal/render_options"
+ autoload :Rescue, "action_controller/metal/rescuable"
+ autoload :Responder, "action_controller/metal/responder"
+ autoload :Testing, "action_controller/metal/testing"
+ autoload :UrlFor, "action_controller/metal/url_for"
+ autoload :Session, "action_controller/metal/session"
+ autoload :Helpers, "action_controller/metal/helpers"
# Ported modules
# require 'action_controller/routing'
autoload :Caching, 'action_controller/caching'
autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
autoload :Integration, 'action_controller/testing/integration'
- autoload :MimeResponds, 'action_controller/base/mime_responds'
+ autoload :MimeResponds, 'action_controller/metal/mime_responds'
autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Resources, 'action_controller/routing/resources'
- autoload :SessionManagement, 'action_controller/base/session_management'
+ autoload :SessionManagement, 'action_controller/metal/session_management'
autoload :TestCase, 'action_controller/testing/test_case'
autoload :TestProcess, 'action_controller/testing/process'
autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter'
autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter'
- autoload :Verification, 'action_controller/base/verification'
- autoload :Flash, 'action_controller/base/flash'
- autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection'
- autoload :Streaming, 'action_controller/base/streaming'
- autoload :HttpAuthentication, 'action_controller/base/http_authentication'
- autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging'
+ autoload :Verification, 'action_controller/metal/verification'
+ autoload :Flash, 'action_controller/metal/flash'
+ autoload :RequestForgeryProtection, 'action_controller/metal/request_forgery_protection'
+ autoload :Streaming, 'action_controller/metal/streaming'
+ autoload :HttpAuthentication, 'action_controller/metal/http_authentication'
+ autoload :FilterParameterLogging, 'action_controller/metal/filter_parameter_logging'
autoload :Translation, 'action_controller/translation'
- autoload :Cookies, 'action_controller/base/cookies'
+ autoload :Cookies, 'action_controller/metal/cookies'
- autoload :ActionControllerError, 'action_controller/base/exceptions'
- autoload :SessionRestoreError, 'action_controller/base/exceptions'
- autoload :RenderError, 'action_controller/base/exceptions'
- autoload :RoutingError, 'action_controller/base/exceptions'
- autoload :MethodNotAllowed, 'action_controller/base/exceptions'
- autoload :NotImplemented, 'action_controller/base/exceptions'
- autoload :UnknownController, 'action_controller/base/exceptions'
- autoload :MissingFile, 'action_controller/base/exceptions'
- autoload :RenderError, 'action_controller/base/exceptions'
- autoload :SessionOverflowError, 'action_controller/base/exceptions'
- autoload :UnknownHttpMethod, 'action_controller/base/exceptions'
+ autoload :ActionControllerError, 'action_controller/metal/exceptions'
+ autoload :SessionRestoreError, 'action_controller/metal/exceptions'
+ autoload :RenderError, 'action_controller/metal/exceptions'
+ autoload :RoutingError, 'action_controller/metal/exceptions'
+ autoload :MethodNotAllowed, 'action_controller/metal/exceptions'
+ autoload :NotImplemented, 'action_controller/metal/exceptions'
+ autoload :UnknownController, 'action_controller/metal/exceptions'
+ autoload :MissingFile, 'action_controller/metal/exceptions'
+ autoload :RenderError, 'action_controller/metal/exceptions'
+ autoload :SessionOverflowError, 'action_controller/metal/exceptions'
+ autoload :UnknownHttpMethod, 'action_controller/metal/exceptions'
autoload :Routing, 'action_controller/routing'
end
autoload :HTML, 'action_controller/vendor/html-scanner'
-autoload :AbstractController, 'action_controller/abstract'
+autoload :AbstractController, 'abstract_controller'
autoload :Rack, 'action_dispatch'
autoload :ActionDispatch, 'action_dispatch'
@@ -69,4 +69,4 @@ require 'active_support/core_ext/load_error'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/name_error'
-require 'active_support/inflector' \ No newline at end of file
+require 'active_support/inflector'
diff --git a/actionpack/lib/action_controller/abstract.rb b/actionpack/lib/action_controller/abstract.rb
deleted file mode 100644
index d0eba253b8..0000000000
--- a/actionpack/lib/action_controller/abstract.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-require "active_support/core_ext/module/attr_internal"
-require "active_support/core_ext/module/delegation"
-
-module AbstractController
- autoload :Base, "action_controller/abstract/base"
- autoload :Benchmarker, "action_controller/abstract/benchmarker"
- autoload :Callbacks, "action_controller/abstract/callbacks"
- autoload :Helpers, "action_controller/abstract/helpers"
- autoload :Layouts, "action_controller/abstract/layouts"
- autoload :Logger, "action_controller/abstract/logger"
- autoload :Renderer, "action_controller/abstract/renderer"
- # === Exceptions
- autoload :ActionNotFound, "action_controller/abstract/exceptions"
- autoload :DoubleRenderError, "action_controller/abstract/exceptions"
- autoload :Error, "action_controller/abstract/exceptions"
-end
diff --git a/actionpack/lib/action_controller/base/base.rb b/actionpack/lib/action_controller/base.rb
index e541d24e31..698189bd46 100644
--- a/actionpack/lib/action_controller/base/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,5 +1,5 @@
module ActionController
- class Base < Http
+ class Base < Metal
abstract!
include AbstractController::Benchmarker
@@ -10,8 +10,8 @@ module ActionController
include ActionController::HideActions
include ActionController::UrlFor
include ActionController::Redirector
- include ActionController::Renderer
- include ActionController::Renderers::All
+ include ActionController::RenderingController
+ include ActionController::RenderOptions::All
include ActionController::Layouts
include ActionController::ConditionalGet
include ActionController::RackConvenience
@@ -41,7 +41,7 @@ module ActionController
module ImplicitRender
def send_action(*)
ret = super
- default_render unless performed?
+ default_render unless response_body
ret
end
@@ -51,7 +51,7 @@ module ActionController
def method_for_action(action_name)
super || begin
- if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path)
+ if view_paths.exists?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path)
"default_render"
end
end
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index d8a1662acc..cb0c3a1384 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -129,8 +129,7 @@ module ActionController #:nodoc:
end
def content_for_layout(controller)
- # TODO: Remove this when new base is merged in
- template = controller.respond_to?(:template) ? controller.template : controller._action_view
+ template = controller.view_context
template.layout && template.instance_variable_get('@cached_content_for_layout')
end
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 95cba0e411..4ef600bea0 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -36,8 +36,8 @@ module ActionController #:nodoc:
def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
if perform_caching
- if cache = read_fragment(name, options)
- buffer.concat(cache)
+ if fragment_exist?(name,options)
+ buffer.concat(read_fragment(name, options))
else
pos = buffer.length
block.call
diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb
index 3f3d20b95f..43aea0eba2 100644
--- a/actionpack/lib/action_controller/legacy/layout.rb
+++ b/actionpack/lib/action_controller/legacy/layout.rb
@@ -191,7 +191,7 @@ module ActionController #:nodoc:
def memoized_find_layout(layout, formats) #:nodoc:
return layout if layout.nil? || layout.respond_to?(:render)
prefix = layout.to_s =~ /layouts\// ? nil : "layouts"
- view_paths.find_by_parts(layout.to_s, {:formats => formats}, prefix)
+ view_paths.find(layout.to_s, {:formats => formats}, prefix)
end
def find_layout(*args)
diff --git a/actionpack/lib/action_controller/base/http.rb b/actionpack/lib/action_controller/metal.rb
index 3efd1b656f..5333ca497c 100644
--- a/actionpack/lib/action_controller/base/http.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,13 +1,11 @@
-require 'action_controller/abstract'
-
module ActionController
- # ActionController::Http provides a way to get a valid Rack application from a controller.
+ # ActionController::Metal provides a way to get a valid Rack application from a controller.
#
# In AbstractController, dispatching is triggered directly by calling #process on a new controller.
- # ActionController::Http provides an #action method that returns a valid Rack application for a
+ # ActionController::Metal provides an #action method that returns a valid Rack application for a
# given action. Other rack builders, such as Rack::Builder, Rack::URLMap, and the Rails router,
# can dispatch directly to the action returned by FooController.action(:index).
- class Http < AbstractController::Base
+ class Metal < AbstractController::Base
abstract!
# :api: public
@@ -72,11 +70,11 @@ module ActionController
def call(name, env)
@_env = env
process(name)
- to_rack
+ to_a
end
# :api: private
- def to_rack
+ def to_a
[status, headers, response_body]
end
diff --git a/actionpack/lib/action_controller/base/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb
index 13813ffd17..f94d1c669c 100644
--- a/actionpack/lib/action_controller/base/compatibility.rb
+++ b/actionpack/lib/action_controller/metal/compatibility.rb
@@ -64,6 +64,8 @@ module ActionController
cattr_accessor :ip_spoofing_check
self.ip_spoofing_check = true
+
+ cattr_accessor :trusted_proxies
end
# For old tests
@@ -72,7 +74,7 @@ module ActionController
# TODO: Remove this after we flip
def template
- @template ||= _action_view
+ @template ||= view_context
end
def process_action(*)
@@ -141,7 +143,7 @@ module ActionController
end
def view_paths
- _action_view.view_paths
+ view_context.view_paths
end
end
end
diff --git a/actionpack/lib/action_controller/base/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index d287ec4994..8575d30335 100644
--- a/actionpack/lib/action_controller/base/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -29,11 +29,7 @@ module ActionController
response.last_modified = options[:last_modified] if options[:last_modified]
if options[:public]
- cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
- cache_control.delete("private")
- cache_control.delete("no-cache")
- cache_control << "public"
- response.headers["Cache-Control"] = cache_control.join(', ')
+ response.cache_control[:public] = true
end
if request.fresh?(response)
@@ -59,14 +55,15 @@ module ActionController
elsif args.empty?
raise ArgumentError, "too few arguments to head"
end
- options = args.extract_options!
- status = args.shift || options.delete(:status) || :ok
+ options = args.extract_options!
+ status = args.shift || options.delete(:status) || :ok
+ location = options.delete(:location)
options.each do |key, value|
headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
end
- render :nothing => true, :status => status
+ render :nothing => true, :status => status, :location => location
end
# Sets the etag and/or last_modified on the response and checks it against
@@ -107,21 +104,10 @@ module ActionController
# This method will overwrite an existing Cache-Control header.
# See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
def expires_in(seconds, options = {}) #:doc:
- cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip }
+ response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public))
+ options.delete(:private)
- cache_control << "max-age=#{seconds}"
- cache_control.delete("no-cache")
- if options[:public]
- cache_control.delete("private")
- cache_control << "public"
- else
- cache_control << "private"
- end
-
- # This allows for additional headers to be passed through like 'max-stale' => 5.hours
- cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
-
- response.headers["Cache-Control"] = cache_control.join(', ')
+ response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"}
end
# Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
diff --git a/actionpack/lib/action_controller/base/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb
index d4806623c3..d4806623c3 100644
--- a/actionpack/lib/action_controller/base/cookies.rb
+++ b/actionpack/lib/action_controller/metal/cookies.rb
diff --git a/actionpack/lib/action_controller/base/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index d0811254cb..d0811254cb 100644
--- a/actionpack/lib/action_controller/base/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
diff --git a/actionpack/lib/action_controller/base/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
index 065e62a37f..065e62a37f 100644
--- a/actionpack/lib/action_controller/base/filter_parameter_logging.rb
+++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb
diff --git a/actionpack/lib/action_controller/base/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index 590f9be3ac..590f9be3ac 100644
--- a/actionpack/lib/action_controller/base/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
diff --git a/actionpack/lib/action_controller/base/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 7c52779064..7c52779064 100644
--- a/actionpack/lib/action_controller/base/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
diff --git a/actionpack/lib/action_controller/base/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index af68c772b1..af68c772b1 100644
--- a/actionpack/lib/action_controller/base/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
diff --git a/actionpack/lib/action_controller/base/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 525787bf92..2b62a1be85 100644
--- a/actionpack/lib/action_controller/base/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -141,7 +141,7 @@ module ActionController
end
def decode_credentials(request)
- ActiveSupport::Base64.decode64(authorization(request).split.last || '')
+ ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '')
end
def encode_credentials(user_name, password)
@@ -197,9 +197,10 @@ module ActionController
return false unless password
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
+ uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url
[true, false].any? do |password_is_ha1|
- expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
+ expected = expected_response(method, uri, credentials, password, password_is_ha1)
expected == credentials[:response]
end
end
diff --git a/actionpack/lib/action_controller/base/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb
index 365351b421..cac529b1ae 100644
--- a/actionpack/lib/action_controller/base/layouts.rb
+++ b/actionpack/lib/action_controller/metal/layouts.rb
@@ -158,7 +158,7 @@ module ActionController
module Layouts
extend ActiveSupport::Concern
- include ActionController::Renderer
+ include ActionController::RenderingController
include AbstractController::Layouts
module ClassMethods
diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f4a4007a43..c8d042acb5 100644
--- a/actionpack/lib/action_controller/base/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -177,106 +177,67 @@ module ActionController #:nodoc:
# Be sure to check respond_with and respond_to documentation for more examples.
#
def respond_to(*mimes, &block)
- options = mimes.extract_options!
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- resource = options.delete(:with)
- responder = Responder.new
-
+ collector = Collector.new
mimes = collect_mimes_from_class_level if mimes.empty?
- mimes.each { |mime| responder.send(mime) }
- block.call(responder) if block_given?
+ mimes.each { |mime| collector.send(mime) }
+ block.call(collector) if block_given?
+
+ if format = request.negotiate_mime(collector.order)
+ self.formats = [format.to_sym]
- if format = request.negotiate_mime(responder.order)
- respond_to_block_or_template_or_resource(format, resource,
- options, &responder.response_for(format))
+ if response = collector.response_for(format)
+ response.call
+ else
+ default_render
+ end
else
head :not_acceptable
end
end
- # respond_with allows you to respond an action with a given resource. It
- # requires that you set your class with a :respond_to method with the
- # formats allowed:
+ # respond_with wraps a resource around a responder for default representation.
+ # First it invokes respond_to, if a response cannot be found (ie. no block
+ # for the request was given and template was not available), it instantiates
+ # an ActionController::Responder with the controller and resource.
#
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
+ # ==== Example
#
- # def index
- # @people = Person.find(:all)
- # respond_with(@person)
- # end
+ # def index
+ # @users = User.all
+ # respond_with(@users)
# end
#
- # When a request comes with format :xml, the respond_with will first search
- # for a template as person/index.xml, if the template is not available, it
- # will see if the given resource responds to :to_xml.
- #
- # If neither are available, it will raise an error.
+ # It also accepts a block to be given. It's used to overwrite a default
+ # response:
#
- # Extra parameters given to respond_with are used when :to_format is invoked.
- # This allows you to set status and location for several formats at the same
- # time. Consider this restful controller response on create for both xml
- # and json formats:
+ # def destroy
+ # @user = User.find(params[:id])
+ # flash[:notice] = "User was successfully created." if @user.save
#
- # class PeopleController < ApplicationController
- # respond_to :xml, :json
- #
- # def create
- # @person = Person.new(params[:person])
- #
- # if @person.save
- # respond_with(@person, :status => :ok, :location => person_url(@person))
- # else
- # respond_with(@person.errors, :status => :unprocessable_entity)
- # end
+ # respond_with(@user) do |format|
+ # format.html { render }
# end
# end
#
- # Finally, respond_with also accepts blocks, as in respond_to. Let's take
- # the same controller and create action above and add common html behavior:
- #
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
- #
- # def create
- # @person = Person.new(params[:person])
- #
- # if @person.save
- # options = { :status => :ok, :location => person_url(@person) }
- #
- # respond_with(@person, options) do |format|
- # format.html { redirect_to options[:location] }
- # end
- # else
- # respond_with(@person.errors, :status => :unprocessable_entity) do
- # format.html { render :action => :new }
- # end
- # end
- # end
- # end
+ # All options given to respond_with are sent to the underlying responder,
+ # except for the option :responder itself. Since the responder interface
+ # is quite simple (it just needs to respond to call), you can even give
+ # a proc to it.
#
def respond_with(resource, options={}, &block)
- respond_to(options.merge!(:with => resource), &block)
+ respond_to(&block)
+ rescue ActionView::MissingTemplate
+ (options.delete(:responder) || responder).call(self, resource, options)
end
- protected
-
- def respond_to_block_or_template_or_resource(format, resource, options)
- self.formats = [format.to_sym]
- return yield if block_given?
-
- begin
- default_render
- rescue ActionView::MissingTemplate => e
- if resource && resource.respond_to?(:"to_#{format.to_sym}")
- render options.merge(format.to_sym => resource)
- else
- raise e
- end
- end
+ def responder
+ ActionController::Responder
end
+ protected
+
# Collect mimes declared in the class method respond_to valid for the
# current action.
#
@@ -296,7 +257,7 @@ module ActionController #:nodoc:
end
end
- class Responder #:nodoc:
+ class Collector #:nodoc:
attr_accessor :order
def initialize
diff --git a/actionpack/lib/action_controller/base/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb
index 805157b0e3..5fac445dab 100644
--- a/actionpack/lib/action_controller/base/rack_convenience.rb
+++ b/actionpack/lib/action_controller/metal/rack_convenience.rb
@@ -20,7 +20,7 @@ module ActionController
end
# :api: private
- def to_rack
+ def to_a
@_response.prepare!
@_response.to_a
end
diff --git a/actionpack/lib/action_controller/base/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb
index 20060b001f..20060b001f 100644
--- a/actionpack/lib/action_controller/base/redirector.rb
+++ b/actionpack/lib/action_controller/metal/redirector.rb
diff --git a/actionpack/lib/action_controller/base/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb
index fc9a02626f..0d69ca10df 100644
--- a/actionpack/lib/action_controller/base/render_options.rb
+++ b/actionpack/lib/action_controller/metal/render_options.rb
@@ -13,7 +13,7 @@ module ActionController
<<-RUBY_EVAL
if options.key?(:#{r})
_process_options(options)
- return _render_#{r}(options[:#{r}], options)
+ return render_#{r}(options[:#{r}], options)
end
RUBY_EVAL
end
@@ -47,12 +47,12 @@ module ActionController
end
end
- module Renderers
+ module RenderOptions
module Json
extend RenderOption
register_renderer :json
- def _render_json(json, options)
+ def render_json(json, options)
json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
self.content_type ||= Mime::JSON
@@ -64,9 +64,9 @@ module ActionController
extend RenderOption
register_renderer :js
- def _render_js(js, options)
+ def render_js(js, options)
self.content_type ||= Mime::JS
- self.response_body = js
+ self.response_body = js.respond_to?(:to_js) ? js.to_js : js
end
end
@@ -74,7 +74,7 @@ module ActionController
extend RenderOption
register_renderer :xml
- def _render_xml(xml, options)
+ def render_xml(xml, options)
self.content_type ||= Mime::XML
self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml
end
@@ -84,8 +84,8 @@ module ActionController
extend RenderOption
register_renderer :update
- def _render_update(proc, options)
- generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(_action_view, &proc)
+ def render_update(proc, options)
+ generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(view_context, &proc)
self.content_type = Mime::JS
self.response_body = generator.to_s
end
@@ -94,10 +94,10 @@ module ActionController
module All
extend ActiveSupport::Concern
- include ActionController::Renderers::Json
- include ActionController::Renderers::Js
- include ActionController::Renderers::Xml
- include ActionController::Renderers::RJS
+ include ActionController::RenderOptions::Json
+ include ActionController::RenderOptions::Js
+ include ActionController::RenderOptions::Xml
+ include ActionController::RenderOptions::RJS
end
end
end
diff --git a/actionpack/lib/action_controller/base/renderer.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb
index 572da451ff..5b1be763ad 100644
--- a/actionpack/lib/action_controller/base/renderer.rb
+++ b/actionpack/lib/action_controller/metal/rendering_controller.rb
@@ -1,8 +1,8 @@
module ActionController
- module Renderer
+ module RenderingController
extend ActiveSupport::Concern
- include AbstractController::Renderer
+ include AbstractController::RenderingController
def process_action(*)
self.formats = request.formats.map {|x| x.to_sym}
@@ -22,7 +22,8 @@ module ActionController
_process_options(options)
if options.key?(:partial)
- _render_partial(options[:partial], options)
+ options[:partial] = action_name if options[:partial] == true
+ options[:_details] = {:formats => formats}
end
super
@@ -52,21 +53,6 @@ module ActionController
super
end
- def _render_partial(partial, options)
- case partial
- when true
- options[:_prefix] = _prefix
- when String
- options[:_prefix] = _prefix unless partial.index('/')
- options[:_template_name] = partial
- else
- options[:_partial_object] = true
- return
- end
-
- options[:_partial] = options[:object] || true
- end
-
def _process_options(options)
status, content_type, location = options.values_at(:status, :content_type, :location)
self.status = status if status
diff --git a/actionpack/lib/action_controller/base/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index ad06657f86..ad06657f86 100644
--- a/actionpack/lib/action_controller/base/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
diff --git a/actionpack/lib/action_controller/base/rescuable.rb b/actionpack/lib/action_controller/metal/rescuable.rb
index 029e643d93..029e643d93 100644
--- a/actionpack/lib/action_controller/base/rescuable.rb
+++ b/actionpack/lib/action_controller/metal/rescuable.rb
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
new file mode 100644
index 0000000000..9ed99ca623
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -0,0 +1,181 @@
+module ActionController #:nodoc:
+ # Responder is responsible to expose a resource for different mime requests,
+ # usually depending on the HTTP verb. The responder is triggered when
+ # respond_with is called. The simplest case to study is a GET request:
+ #
+ # class PeopleController < ApplicationController
+ # respond_to :html, :xml, :json
+ #
+ # def index
+ # @people = Person.find(:all)
+ # respond_with(@people)
+ # end
+ # end
+ #
+ # When a request comes, for example with format :xml, three steps happen:
+ #
+ # 1) respond_with searches for a template at people/index.xml;
+ #
+ # 2) if the template is not available, it will create a responder, passing
+ # the controller and the resource and invoke :to_xml on it;
+ #
+ # 3) if the responder does not respond_to :to_xml, call to_format on it.
+ #
+ # === Builtin HTTP verb semantics
+ #
+ # Rails default responder holds semantics for each HTTP verb. Depending on the
+ # content type, verb and the resource status, it will behave differently.
+ #
+ # Using Rails default responder, a POST request for creating an object could
+ # be written as:
+ #
+ # def create
+ # @user = User.new(params[:user])
+ # flash[:notice] = 'User was successfully created.' if @user.save
+ # respond_with(@user)
+ # end
+ #
+ # Which is exactly the same as:
+ #
+ # def create
+ # @user = User.new(params[:user])
+ #
+ # respond_to do |format|
+ # if @user.save
+ # flash[:notice] = 'User was successfully created.'
+ # format.html { redirect_to(@user) }
+ # format.xml { render :xml => @user, :status => :created, :location => @user }
+ # else
+ # format.html { render :action => "new" }
+ # format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ # end
+ # end
+ # end
+ #
+ # The same happens for PUT and DELETE requests.
+ #
+ # === Nested resources
+ #
+ # You can given nested resource as you do in form_for and polymorphic_url.
+ # Consider the project has many tasks example. The create action for
+ # TasksController would be like:
+ #
+ # def create
+ # @project = Project.find(params[:project_id])
+ # @task = @project.comments.build(params[:task])
+ # flash[:notice] = 'Task was successfully created.' if @task.save
+ # respond_with([@project, @task])
+ # end
+ #
+ # Giving an array of resources, you ensure that the responder will redirect to
+ # project_task_url instead of task_url.
+ #
+ # Namespaced and singleton resources requires a symbol to be given, as in
+ # polymorphic urls. If a project has one manager which has many tasks, it
+ # should be invoked as:
+ #
+ # respond_with([@project, :manager, @task])
+ #
+ # Check polymorphic_url documentation for more examples.
+ #
+ class Responder
+ attr_reader :controller, :request, :format, :resource, :resource_location, :options
+
+ def initialize(controller, resource, options={})
+ @controller = controller
+ @request = controller.request
+ @format = controller.formats.first
+ @resource = resource.is_a?(Array) ? resource.last : resource
+ @resource_location = options[:location] || resource
+ @options = options
+ end
+
+ delegate :head, :render, :redirect_to, :to => :controller
+ delegate :get?, :post?, :put?, :delete?, :to => :request
+
+ # Undefine :to_json since it's defined on Object
+ undef_method :to_json
+
+ # Initializes a new responder an invoke the proper format. If the format is
+ # not defined, call to_format.
+ #
+ def self.call(*args)
+ responder = new(*args)
+ method = :"to_#{responder.format}"
+ responder.respond_to?(method) ? responder.send(method) : responder.to_format
+ end
+
+ # HTML format does not render the resource, it always attempt to render a
+ # template.
+ #
+ def to_html
+ if get?
+ render
+ elsif has_errors?
+ render :action => default_action
+ else
+ redirect_to resource_location
+ end
+ end
+
+ # All others formats try to render the resource given instead. For this
+ # purpose a helper called display as a shortcut to render a resource with
+ # the current format.
+ #
+ def to_format
+ return render unless resourceful?
+
+ if get?
+ display resource
+ elsif has_errors?
+ display resource.errors, :status => :unprocessable_entity
+ elsif post?
+ display resource, :status => :created, :location => resource_location
+ else
+ head :ok
+ end
+ end
+
+ protected
+
+ # Checks whether the resource responds to the current format or not.
+ #
+ def resourceful?
+ resource.respond_to?(:"to_#{format}")
+ end
+
+ # display is just a shortcut to render a resource with the current format.
+ #
+ # display @user, :status => :ok
+ #
+ # For xml request is equivalent to:
+ #
+ # render :xml => @user, :status => :ok
+ #
+ # Options sent by the user are also used:
+ #
+ # respond_with(@user, :status => :created)
+ # display(@user, :status => :ok)
+ #
+ # Results in:
+ #
+ # render :xml => @user, :status => :created
+ #
+ def display(resource, given_options={})
+ render given_options.merge!(options).merge!(format => resource)
+ end
+
+ # Check if the resource has errors or not.
+ #
+ def has_errors?
+ resource.respond_to?(:errors) && !resource.errors.empty?
+ end
+
+ # By default, render the :edit action for html requests with failure, unless
+ # the verb is post.
+ #
+ def default_action
+ request.post? ? :new : :edit
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/base/session.rb b/actionpack/lib/action_controller/metal/session.rb
index bcedd6e1c7..bcedd6e1c7 100644
--- a/actionpack/lib/action_controller/base/session.rb
+++ b/actionpack/lib/action_controller/metal/session.rb
diff --git a/actionpack/lib/action_controller/base/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb
index ffce8e1bd1..ffce8e1bd1 100644
--- a/actionpack/lib/action_controller/base/session_management.rb
+++ b/actionpack/lib/action_controller/metal/session_management.rb
diff --git a/actionpack/lib/action_controller/base/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 9ff4f25f43..57318e8747 100644
--- a/actionpack/lib/action_controller/base/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,10 +1,12 @@
+require 'active_support/core_ext/string/bytesize'
+
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
module Streaming
extend ActiveSupport::Concern
- include ActionController::Renderer
+ include ActionController::RenderingController
DEFAULT_SEND_FILE_OPTIONS = {
:type => 'application/octet-stream'.freeze,
@@ -142,7 +144,7 @@ module ActionController #:nodoc:
# instead. See ActionController::Base#render for more information.
def send_data(data, options = {}) #:doc:
logger.info "Sending data #{options[:filename]}" if logger
- send_file_headers! options.merge(:length => data.size)
+ send_file_headers! options.merge(:length => data.bytesize)
@performed_render = false
render :status => options[:status], :text => data
end
@@ -179,7 +181,7 @@ module ActionController #:nodoc:
# after it displays the "open/save" dialog, which means that if you
# hit "open" the file isn't there anymore when the application that
# is called for handling the download is run, so let's workaround that
- headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
+ response.cache_control[:public] ||= false
end
end
end
diff --git a/actionpack/lib/action_controller/base/testing.rb b/actionpack/lib/action_controller/metal/testing.rb
index a4a1116d9e..a4a1116d9e 100644
--- a/actionpack/lib/action_controller/base/testing.rb
+++ b/actionpack/lib/action_controller/metal/testing.rb
diff --git a/actionpack/lib/action_controller/base/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 7119c14cd3..7119c14cd3 100644
--- a/actionpack/lib/action_controller/base/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
diff --git a/actionpack/lib/action_controller/base/verification.rb b/actionpack/lib/action_controller/metal/verification.rb
index 951ae1bee1..d3d78e3749 100644
--- a/actionpack/lib/action_controller/base/verification.rb
+++ b/actionpack/lib/action_controller/metal/verification.rb
@@ -2,7 +2,7 @@ module ActionController #:nodoc:
module Verification #:nodoc:
extend ActiveSupport::Concern
- include AbstractController::Callbacks, Session, Flash, Renderer
+ include AbstractController::Callbacks, Session, Flash, RenderingController
# This module provides a class-level method for specifying that certain
# actions are guarded against being called without certain prerequisites
@@ -127,4 +127,4 @@ module ActionController #:nodoc:
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/old_base.rb b/actionpack/lib/action_controller/old_base.rb
deleted file mode 100644
index dd22bfd617..0000000000
--- a/actionpack/lib/action_controller/old_base.rb
+++ /dev/null
@@ -1,84 +0,0 @@
-#--
-# Copyright (c) 2004-2009 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-#++
-
-activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
-$:.unshift activesupport_path if File.directory?(activesupport_path)
-require 'active_support'
-
-require File.join(File.dirname(__FILE__), "action_pack")
-
-module ActionController
- # TODO: Review explicit to see if they will automatically be handled by
- # the initilizer if they are really needed.
- def self.load_all!
- [Base, Request, Response, UrlRewriter, UrlWriter]
- [ActionDispatch::Http::Headers]
- end
-
- autoload :Base, 'action_controller/base/base'
- autoload :Benchmarking, 'action_controller/base/chained/benchmarking'
- autoload :Caching, 'action_controller/caching'
- autoload :Cookies, 'action_controller/base/cookies'
- autoload :Dispatcher, 'action_controller/dispatch/dispatcher'
- autoload :Filters, 'action_controller/base/chained/filters'
- autoload :Flash, 'action_controller/base/chained/flash'
- autoload :Helpers, 'action_controller/base/helpers'
- autoload :HttpAuthentication, 'action_controller/base/http_authentication'
- autoload :Integration, 'action_controller/testing/integration'
- autoload :IntegrationTest, 'action_controller/testing/integration'
- autoload :Layout, 'action_controller/base/layout'
- autoload :MimeResponds, 'action_controller/base/mime_responds'
- autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes'
- autoload :RecordIdentifier, 'action_controller/record_identifier'
- autoload :Redirector, 'action_controller/base/redirect'
- autoload :Renderer, 'action_controller/base/render'
- autoload :RequestForgeryProtection, 'action_controller/base/request_forgery_protection'
- autoload :Rescue, 'action_controller/base/rescue'
- autoload :Resources, 'action_controller/routing/resources'
- autoload :Responder, 'action_controller/base/responder'
- autoload :Routing, 'action_controller/routing'
- autoload :SessionManagement, 'action_controller/base/session_management'
- autoload :Streaming, 'action_controller/base/streaming'
- autoload :TestCase, 'action_controller/testing/test_case'
- autoload :TestProcess, 'action_controller/testing/process'
- autoload :Translation, 'action_controller/translation'
- autoload :UrlEncodedPairParser, 'action_controller/dispatch/url_encoded_pair_parser'
- autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter'
- autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter'
- autoload :Verification, 'action_controller/base/verification'
- autoload :FilterParameterLogging, 'action_controller/base/filter_parameter_logging'
-
- module Assertions
- autoload :DomAssertions, 'action_controller/testing/assertions/dom'
- autoload :ModelAssertions, 'action_controller/testing/assertions/model'
- autoload :ResponseAssertions, 'action_controller/testing/assertions/response'
- autoload :RoutingAssertions, 'action_controller/testing/assertions/routing'
- autoload :SelectorAssertions, 'action_controller/testing/assertions/selector'
- autoload :TagAssertions, 'action_controller/testing/assertions/tag'
- end
-end
-
-autoload :HTML, 'action_controller/vendor/html-scanner'
-
-require 'action_dispatch'
-require 'action_view'
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index b4408e4e1d..1165c3b7c5 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -36,21 +36,6 @@ module ActionController
JOIN = '_'.freeze
NEW = 'new'.freeze
- # Returns plural/singular for a record or class. Example:
- #
- # partial_path(post) # => "posts/post"
- # partial_path(Person) # => "people/person"
- # partial_path(Person, "admin/games") # => "admin/people/person"
- def partial_path(record_or_class, controller_path = nil)
- name = model_name_from_record_or_class(record_or_class)
-
- if controller_path && controller_path.include?("/")
- "#{File.dirname(controller_path)}/#{name.partial_path}"
- else
- name.partial_path
- end
- end
-
# The DOM class convention is to use the singular form of an object or class. Examples:
#
# dom_class(post) # => "post"
diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
index 159d869a54..2adf3575a7 100644
--- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb
@@ -50,6 +50,7 @@ module ActionController
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
+ # polymorphic_url(Comment) # => "http://example.com/comments"
#
# ==== Options
#
@@ -70,6 +71,9 @@ module ActionController
# record = Comment.new
# polymorphic_url(record) # same as comments_url()
#
+ # # the class of a record will also map to the collection
+ # polymorphic_url(Comment) # same as comments_url()
+ #
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
@@ -86,17 +90,19 @@ module ActionController
else [ record_or_hash_or_array ]
end
- inflection =
- case
- when options[:action].to_s == "new"
- args.pop
- :singular
- when record.respond_to?(:new_record?) && record.new_record?
- args.pop
- :plural
- else
- :singular
- end
+ inflection = if options[:action].to_s == "new"
+ args.pop
+ :singular
+ elsif (record.respond_to?(:new_record?) && record.new_record?) ||
+ (record.respond_to?(:destroyed?) && record.destroyed?)
+ args.pop
+ :plural
+ elsif record.is_a?(Class)
+ args.pop
+ :plural
+ else
+ :singular
+ end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index 040a7e2cb6..0c499f3b46 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -463,13 +463,6 @@ module ActionController
routes_by_controller[controller][action][merged.keys]
end
- def routes_for_controller_and_action(controller, action)
- selected = routes.select do |route|
- route.matches_controller_and_action? controller, action
- end
- (selected.length == routes.length) ? routes : selected
- end
-
def routes_for_controller_and_action_and_keys(controller, action, keys)
selected = routes.select do |route|
route.matches_controller_and_action? controller, action
diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb
index e7c64d0942..d32d5562e8 100644
--- a/actionpack/lib/action_controller/testing/process.rb
+++ b/actionpack/lib/action_controller/testing/process.rb
@@ -52,7 +52,7 @@ module ActionController #:nodoc:
class TestResponse < ActionDispatch::TestResponse
def recycle!
@status = 200
- @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
+ @header = Rack::Utils::HeaderHash.new
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 5f9463eb91..4190fa21cd 100755
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -246,7 +246,7 @@ module ActionDispatch
remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
unless remote_addr_list.blank?
- not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
+ not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies}
return not_trusted_addrs.first unless not_trusted_addrs.empty?
end
remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
@@ -265,7 +265,7 @@ EOM
end
if remote_ips
- while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip
+ while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip)
remote_ips.pop
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index e58b4b5e19..03d1780b77 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -32,8 +32,8 @@ module ActionDispatch # :nodoc:
# end
# end
class Response < Rack::Response
- DEFAULT_HEADERS = { "Cache-Control" => "no-cache" }
attr_accessor :request
+ attr_reader :cache_control
attr_writer :header
alias_method :headers=, :header=
@@ -42,21 +42,26 @@ module ActionDispatch # :nodoc:
def initialize
super
- @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
+ @cache_control = {}
+ @header = Rack::Utils::HeaderHash.new
+ end
+
+ def status=(status)
+ @status = status.to_i
end
# The response code of the request
def response_code
- status.to_s[0,3].to_i rescue 0
+ @status
end
# Returns a String to ensure compatibility with Net::HTTPResponse
def code
- status.to_s.split(' ')[0]
+ @status.to_s
end
def message
- status.to_s.split(' ',2)[1] || StatusCodes::STATUS_CODES[response_code]
+ StatusCodes::STATUS_CODES[@status]
end
alias_method :status_message, :message
@@ -142,10 +147,7 @@ module ActionDispatch # :nodoc:
def prepare!
assign_default_content_type_and_charset!
handle_conditional_get!
- set_content_length!
- convert_content_type!
- convert_language!
- convert_cookies!
+ self["Set-Cookie"] ||= ""
end
def each(&callback)
@@ -196,22 +198,24 @@ module ActionDispatch # :nodoc:
private
def handle_conditional_get!
- if etag? || last_modified?
+ if etag? || last_modified? || !cache_control.empty?
set_conditional_cache_control!
elsif nonempty_ok_response?
self.etag = body
if request && request.etag_matches?(etag)
- self.status = '304 Not Modified'
+ self.status = 304
self.body = []
end
set_conditional_cache_control!
+ else
+ headers["Cache-Control"] = "no-cache"
end
end
def nonempty_ok_response?
- ok = !status || status.to_s[0..2] == '200'
+ ok = !@status || @status == 200
ok && string_body?
end
@@ -220,40 +224,20 @@ module ActionDispatch # :nodoc:
end
def set_conditional_cache_control!
- if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control']
- headers['Cache-Control'] = 'private, max-age=0, must-revalidate'
+ if cache_control.empty?
+ cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true)
end
- end
- def convert_content_type!
- headers['Content-Type'] ||= "text/html"
- headers['Content-Type'] += "; charset=" + headers.delete('charset') if headers['charset']
- end
+ public_cache, max_age, must_revalidate, extras =
+ cache_control.values_at(:public, :max_age, :must_revalidate, :extras)
- # Don't set the Content-Length for block-based bodies as that would mean
- # reading it all into memory. Not nice for, say, a 2GB streaming file.
- def set_content_length!
- if status && status.to_s[0..2] == '204'
- headers.delete('Content-Length')
- elsif length = headers['Content-Length']
- headers['Content-Length'] = length.to_s
- elsif string_body? && (!status || status.to_s[0..2] != '304')
- headers["Content-Length"] = Rack::Utils.bytesize(body).to_s
- end
- end
-
- def convert_language!
- headers["Content-Language"] = headers.delete("language") if headers["language"]
- end
+ options = []
+ options << "max-age=#{max_age}" if max_age
+ options << (public_cache ? "public" : "private")
+ options << "must-revalidate" if must_revalidate
+ options.concat(extras) if extras
- def convert_cookies!
- headers['Set-Cookie'] =
- if header = headers['Set-Cookie']
- header = header.split("\n") if header.respond_to?(:to_str)
- header.compact
- else
- []
- end
+ headers["Cache-Control"] = options.join(", ")
end
end
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
index dd75cda6b9..d22adfa749 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb
@@ -345,14 +345,17 @@ module ActionDispatch
#
# Use the first argument to narrow down assertions to only statements
# of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>,
- # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and
- # <tt>:insert_html</tt>.
+ # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>,
+ # <tt>:insert_html</tt> and <tt>:redirect</tt>.
#
# Use the argument <tt>:insert</tt> followed by an insertion position to narrow
# down the assertion to only statements that insert elements in that
# 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
+ # 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
# be ignored as there is no HTML passed for this statement.
#
@@ -399,6 +402,9 @@ module ActionDispatch
#
# # The same, but shorter.
# assert_select "ol>li", 4
+ #
+ # # Checking for a redirect.
+ # assert_select_rjs :redirect, root_path
def assert_select_rjs(*args, &block)
rjs_type = args.first.is_a?(Symbol) ? args.shift : nil
id = args.first.is_a?(String) ? args.shift : nil
@@ -576,7 +582,8 @@ module ActionDispatch
: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}\\)"
+ :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}\\)"
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 9e696af83b..c171a5a8f5 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -164,6 +164,9 @@ module ActionView #:nodoc:
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
+ module Subclasses
+ end
+
include Helpers, Rendering, Partials, ::ERB::Util
extend ActiveSupport::Memoizable
@@ -195,14 +198,16 @@ module ActionView #:nodoc:
attr_internal :request, :layout
- delegate :controller_path, :to => :controller, :allow_nil => true
+ def controller_path
+ @controller_path ||= controller && controller.controller_path
+ end
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
:flash, :action_name, :controller_name, :to => :controller
delegate :logger, :to => :controller, :allow_nil => true
- delegate :find_by_parts, :to => :view_paths
+ delegate :find, :to => :view_paths
include Context
@@ -210,30 +215,35 @@ module ActionView #:nodoc:
ActionView::PathSet.new(Array(value))
end
+ extlib_inheritable_accessor :helpers
attr_reader :helpers
- class ProxyModule < Module
- def initialize(receiver)
- @receiver = receiver
- end
+ def self.for_controller(controller)
+ @views ||= {}
- def include(*args)
- super(*args)
- @receiver.extend(*args)
- end
- end
+ # TODO: Decouple this so helpers are a separate concern in AV just like
+ # they are in AC.
+ if controller.class.respond_to?(:_helper_serial)
+ klass = @views[controller.class._helper_serial] ||= Class.new(self) do
+ Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self)
- def self.for_controller(controller)
- new(controller.class.view_paths, {}, controller).tap do |view|
- view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers)
+ if controller.respond_to?(:_helpers)
+ include controller._helpers
+ self.helpers = controller._helpers
+ end
+ end
+ else
+ klass = self
end
+
+ klass.new(controller.class.view_paths, {}, controller)
end
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc:
@formats = formats || [:html]
@assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) }
@controller = controller
- @helpers = ProxyModule.new(self)
+ @helpers = self.class.helpers || Module.new
@_content_for = Hash.new {|h,k| h[k] = "" }
self.view_paths = view_paths
end
@@ -248,7 +258,7 @@ module ActionView #:nodoc:
def with_template(current_template)
_evaluate_assigns_and_ivars
last_template, self.template = template, current_template
- last_formats, self.formats = formats, [current_template.mime_type.to_sym] + Mime::SET.symbols
+ last_formats, self.formats = formats, current_template.formats
yield
ensure
self.template, self.formats = last_template, last_formats
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index f212fe25eb..df078a7151 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -12,11 +12,11 @@ module ActionView
#
# Context.for_controller[controller] Create a new ActionView instance for a
# controller
- # Context#_render_partial_from_controller[options]
+ # Context#render_partial[options]
# - responsible for setting options[:_template]
# - Returns String with the rendered partial
# options<Hash>:: see _render_partial in ActionView::Base
- # Context#_render_template_from_controller[template, layout, options, partial]
+ # Context#render_template[template, layout, options, partial]
# - Returns String with the rendered template
# template<ActionView::Template>:: The template to render
# layout<ActionView::Template>:: The layout to render around the template
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 4fd7f7d83c..3e6e62237d 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -278,9 +278,7 @@ module ActionView
end
%w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth|
- define_method meth do |*|
- error_wrapping(super)
- end
+ module_eval "def #{meth}(*) error_wrapping(super) end"
end
def error_wrapping(html_tag)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 3fde79dfa4..c71840d41f 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -171,7 +171,7 @@ module ActionView
end
# Computes the path to a javascript asset in the public javascripts directory.
- # If the +source+ filename has no extension, .js will be appended.
+ # If the +source+ filename has no extension, .js will be appended (except for explicit URIs)
# Full paths from the document root will be passed through.
# Used internally by javascript_include_tag to build the script path.
#
@@ -179,7 +179,7 @@ module ActionView
# javascript_path "xmlhr" # => /javascripts/xmlhr.js
# javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js
# javascript_path "/dir/xmlhr" # => /dir/xmlhr.js
- # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
+ # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
def javascript_path(source)
compute_public_path(source, 'javascripts', 'js')
@@ -337,7 +337,7 @@ module ActionView
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.
+ # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
#
@@ -345,8 +345,8 @@ module ActionView
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
- # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
- # stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
+ # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style
+ # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css
def stylesheet_path(source)
compute_public_path(source, 'stylesheets', 'css')
end
@@ -557,7 +557,7 @@ module ActionView
# video_tag("trailer.ogg") # =>
# <video src="/videos/trailer.ogg" />
# video_tag("trailer.ogg", :controls => true, :autobuffer => true) # =>
- # <video autobuffer="true" controls="true" src="/videos/trailer.ogg" />
+ # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" />
# video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # =>
# <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" />
# video_tag("/trailers/hd.avi", :size => "16x16") # =>
@@ -629,11 +629,11 @@ module ActionView
has_request = @controller.respond_to?(:request)
source_ext = File.extname(source)[1..-1]
- if ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))))
+ if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))))
source += ".#{ext}"
end
- unless source =~ %r{^[-a-z]+://}
+ unless is_uri?(source)
source = "/#{dir}/#{source}" unless source[0] == ?/
source = rewrite_asset_path(source)
@@ -645,10 +645,10 @@ module ActionView
end
end
- if include_host && source !~ %r{^[-a-z]+://}
+ if include_host && !is_uri?(source)
host = compute_asset_host(source)
- if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
+ if has_request && !host.blank? && !is_uri?(host)
host = "#{@controller.request.protocol}#{host}"
end
@@ -658,6 +658,10 @@ module ActionView
end
end
+ def is_uri?(path)
+ path =~ %r{^[-a-z]+://}
+ end
+
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
@@ -798,7 +802,7 @@ module ActionView
end
def asset_file_path!(path)
- unless path =~ %r{^[-a-z]+://}
+ unless is_uri?(path)
absolute_path = asset_file_path(path)
raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
return absolute_path
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index dc4497581c..9951e11a37 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -98,7 +98,7 @@ module ActionView
options[:schema_date] = "2005" # The Atom spec copyright date
end
- xml = options[:xml] || eval("xml", block.binding)
+ xml = options.delete(:xml) || eval("xml", block.binding)
xml.instruct!
if options[:instruct]
options[:instruct].each do |target,attrs|
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 2d1d19d5f3..72f162cb20 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -872,8 +872,8 @@ module ActionView
private
def add_default_name_and_id_for_value(tag_value, options)
- if tag_value
- pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
+ unless tag_value.nil?
+ 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}" unless specified_id
@@ -932,6 +932,14 @@ module ActionView
attr_accessor :object_name, :object, :options
+ def self.model_name
+ @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
+ end
+
+ def to_model
+ self
+ end
+
def initialize(object_name, object, template, options, proc)
@nested_child_index = {}
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e126b35e90..1d851ecbd7 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -79,6 +79,13 @@ module ActionView
# # <option>Paris</option><option>Rome</option></select>
def select_tag(name, option_tags = nil, options = {})
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
+ if blank = options.delete(:include_blank)
+ if blank.kind_of?(String)
+ option_tags = "<option value=\"\">#{blank}</option>" + option_tags
+ else
+ option_tags = "<option value=\"\"></option>" + option_tags
+ end
+ end
content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
end
@@ -263,7 +270,7 @@ module ActionView
escape = options.key?("escape") ? options.delete("escape") : true
content = html_escape(content) if escape
- content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
+ content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options)
end
# Creates a check box form input tag.
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 999d5b34fc..897a7cc348 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -215,7 +215,7 @@ module ActionView
delimiter ||= (options[:delimiter] || defaults[:delimiter])
begin
- rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
+ rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision
number_with_delimiter("%01.#{precision}f" % rounded_number,
:separator => separator,
:delimiter => delimiter)
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index 66d7592874..ff5a2134ff 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -8,7 +8,10 @@ module ActionView
module TagHelper
include ERB::Util
- BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked).to_set
+ BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
+ autoplay controls loop selected hidden scoped async
+ defer reversed ismap seemless muted required
+ autofocus novalidate formnovalidate open).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attr| attr.to_sym })
# Returns an empty HTML tag of type +name+ which by default is XHTML
@@ -131,16 +134,14 @@ module ActionView
def tag_options(options, escape = true)
unless options.blank?
attrs = []
- if escape
- options.each_pair do |key, value|
- if BOOLEAN_ATTRIBUTES.include?(key)
- attrs << %(#{key}="#{key}") if value
- else
- attrs << %(#{key}="#{escape_once(value)}") if !value.nil?
- end
+ options.each_pair do |key, value|
+ if BOOLEAN_ATTRIBUTES.include?(key)
+ 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
+ attrs << %(#{key}="#{final_value}")
end
- else
- attrs = options.map { |key, value| %(#{key}="#{value}") }
end
" #{attrs.sort * ' '}" unless attrs.empty?
end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index c3ce4c671e..1d92bcb763 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -33,13 +33,15 @@ module ActionView
end
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
- # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...").
+ # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...")
+ # for a total length not exceeding <tt>:length</tt>.
+ #
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
# ==== Examples
#
# truncate("Once upon a time in a world far far away")
- # # => Once upon a time in a world f...
+ # # => Once upon a time in a world...
#
# truncate("Once upon a time in a world far far away", :separator => ' ')
# # => Once upon a time in a world...
@@ -48,19 +50,19 @@ module ActionView
# # => Once upon a...
#
# truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)")
- # # => And they found that many (clipped)
+ # # => And they found t(clipped)
#
- # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15)
- # # => And they found... (continued)
+ # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25)
+ # # => And they f... (continued)
#
# You can still use <tt>truncate</tt> with the old API that accepts the
# +length+ as its optional second and the +ellipsis+ as its
# optional third parameter:
# truncate("Once upon a time in a world far far away", 14)
- # # => Once upon a time in a world f...
+ # # => Once upon a...
#
- # truncate("And they found that many people were sleeping better.", 15, "... (continued)")
- # # => And they found... (continued)
+ # truncate("And they found that many people were sleeping better.", 25, "... (continued)")
+ # # => And they f... (continued)
def truncate(text, *args)
options = args.extract_options!
unless args.empty?
@@ -239,12 +241,20 @@ module ActionView
#
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
- def textilize(text)
+ #
+ # textilize("This is worded <strong>strongly</strong>")
+ # # => "<p>This is worded <strong>strongly</strong></p>"
+ #
+ # textilize("This is worded <strong>strongly</strong>", :filter_html)
+ # # => "<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>"
+ #
+ def textilize(text, *options)
+ options ||= [:hard_breaks]
+
if text.blank?
""
else
- textilized = RedCloth.new(text, [ :hard_breaks ])
- textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=)
+ textilized = RedCloth.new(text, options)
textilized.to_html
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
index 074b475819..4001757a9b 100644
--- a/actionpack/lib/action_view/paths.rb
+++ b/actionpack/lib/action_view/paths.rb
@@ -33,12 +33,12 @@ module ActionView #:nodoc:
super(*objs.map { |obj| self.class.type_cast(obj) })
end
- def find_by_parts(path, details = {}, prefix = nil, partial = false)
+ def find(path, details = {}, prefix = nil, partial = false)
# template_path = path.sub(/^\//, '')
template_path = path
each do |load_path|
- if template = load_path.find_by_parts(template_path, details, prefix, partial)
+ if template = load_path.find(template_path, details, prefix, partial)
return template
end
end
@@ -48,11 +48,11 @@ module ActionView #:nodoc:
raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}")
end
- def find_by_parts?(path, extension = nil, prefix = nil, partial = false)
+ def exists?(path, extension = nil, prefix = nil, partial = false)
template_path = path.sub(/^\//, '')
each do |load_path|
- return true if template = load_path.find_by_parts(template_path, extension, prefix, partial)
+ return true if template = load_path.find(template_path, extension, prefix, partial)
end
false
end
@@ -62,7 +62,7 @@ module ActionView #:nodoc:
template_path = original_template_path.sub(/^\//, '')
each do |load_path|
- if template = load_path.find_by_parts(template_path, format)
+ if template = load_path.find(template_path, format)
return template
# Try to find html version if the format is javascript
elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"]
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index ccb14d513a..64f08c447d 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -170,133 +170,117 @@ module ActionView
# <%- end -%>
# <% end %>
module Partials
- extend ActiveSupport::Memoizable
extend ActiveSupport::Concern
-
- included do
- attr_accessor :_partial
- end
- def _render_partial_from_controller(*args)
- @assigns_added = false
- _render_partial(*args)
- end
-
- def _render_partial(options = {}) #:nodoc:
- options[:locals] ||= {}
+ class PartialRenderer
+ def self.partial_names
+ @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new }
+ end
- case path = partial = options[:partial]
- when *_array_like_objects
- return _render_partial_collection(partial, options)
- else
- if partial.is_a?(ActionView::Helpers::FormBuilder)
- path = partial.class.to_s.demodulize.underscore.sub(/_builder$/, '')
- options[:locals].merge!(path.to_sym => partial)
- elsif !partial.is_a?(String)
- options[:object] = object = partial
- path = ActionController::RecordIdentifier.partial_path(object, controller_path)
- end
- _, _, prefix, object = parts = partial_parts(path, options)
- parts[1] = {:formats => parts[1]}
- template = find_by_parts(*parts)
- _render_partial_object(template, options, (object unless object == true))
+ def self.formats
+ @formats ||= Hash.new {|h,k| h[k] = Hash.new{|h,k| h[k] = Hash.new {|h,k| h[k] = {}}}}
end
- end
- private
- def partial_parts(name, options)
- segments = name.split("/")
- parts = segments.pop.split(".")
+ def initialize(view_context, options, block)
+ partial = options[:partial]
- case parts.size
- when 1
- parts
- when 2, 3
- extension = parts.delete_at(1).to_sym
- if formats.include?(extension)
- self.formats.replace [extension]
- end
- parts.pop if parts.size == 2
- end
+ @view = view_context
+ @options = options
+ @locals = options[:locals] || {}
+ @block = block
+
+ # Set up some instance variables to speed up memoizing
+ @partial_names = self.class.partial_names[@view.controller.class]
+ @templates = self.class.formats
+ @format = view_context.formats
- path = parts.join(".")
- prefix = segments[0..-1].join("/")
- prefix = prefix.blank? ? controller_path : prefix
- parts = [path, formats, prefix]
- parts.push options[:object] || true
+ # Set up the object and path
+ @object = partial.is_a?(String) ? options[:object] : partial
+ @path = partial_path(partial)
end
- def _render_partial_with_block(layout, block, options)
- @_proc_for_layout = block
- concat(_render_partial(options.merge(:partial => layout)))
- ensure
- @_proc_for_layout = nil
+ def render
+ return render_collection if collection
+
+ template = find_template
+ render_template(template, @object || @locals[template.variable_name])
end
- def _render_partial_with_layout(layout, options)
- if layout
- prefix = controller && !layout.include?("/") ? controller.controller_path : nil
- layout = find_by_parts(layout, {:formats => formats}, prefix, true)
+ def render_collection
+ # Even if no template is rendered, this will ensure that the MIME type
+ # for the empty response is the same as the provided template
+ @options[:_template] = default_template = find_template
+
+ return nil if collection.blank?
+
+ if @options.key?(:spacer_template)
+ spacer = find_template(@options[:spacer_template]).render(@view, @locals)
end
- content = _render_partial(options)
- return _render_content_with_layout(content, layout, options[:locals])
- end
- def _array_like_objects
- array_like = [Array]
- if defined?(ActiveRecord)
- array_like.push(ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope)
+ segments = []
+
+ collection.each_with_index do |object, index|
+ template = default_template || find_template(partial_path(object))
+ @locals[template.counter_name] = index
+ segments << render_template(template, object)
end
- array_like
+
+ segments.join(spacer)
end
- def _render_partial_object(template, options, object = nil)
- if options.key?(:collection)
- _render_partial_collection(options.delete(:collection), options, template)
- else
- locals = (options[:locals] ||= {})
- object ||= locals[:object] || locals[template.variable_name]
-
- _set_locals(object, locals, template, options)
-
- options[:_template] = template
-
- _render_template(template, locals)
+ def render_template(template, object = @object)
+ @options[:_template] ||= template
+
+ # TODO: is locals[:object] really necessary?
+ @locals[:object] = @locals[template.variable_name] = object
+ @locals[@options[:as]] = object if @options[:as]
+
+ content = @view._render_single_template(template, @locals, &@block)
+ return content if @block || !@options[:layout]
+ find_template(@options[:layout]).render(@view, @locals) { content }
+ end
+
+
+ private
+ def collection
+ @collection ||= if @object.respond_to?(:to_ary)
+ @object
+ elsif @options.key?(:collection)
+ @options[:collection] || []
end
end
- def _set_locals(object, locals, template, options)
- locals[:object] = locals[template.variable_name] = object
- locals[options[:as]] = object if options[:as]
+ def find_template(path = @path)
+ return if !path
+ @templates[path][@view.controller_path][@format][I18n.locale] ||= begin
+ prefix = @view.controller.controller_path unless path.include?(?/)
+ @view.find(path, {:formats => @view.formats}, prefix, true)
+ end
end
- def _render_partial_collection(collection, options = {}, passed_template = nil) #:nodoc:
- return nil if collection.blank?
-
- spacer = options[:spacer_template] ? _render_partial(:partial => options[:spacer_template]) : ''
+ def partial_path(object = @object)
+ return object if object.is_a?(String)
+ @partial_names[object.class] ||= begin
+ return nil unless object.respond_to?(:to_model)
- locals = (options[:locals] ||= {})
- index, @_partial_path = 0, nil
- collection.map do |object|
- options[:_template] = template = passed_template || begin
- _partial_path =
- ActionController::RecordIdentifier.partial_path(object, controller_path)
- template = _pick_partial_template(_partial_path)
+ object.to_model.class.model_name.partial_path.dup.tap do |partial|
+ path = @view.controller_path
+ partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/)
end
-
- _set_locals(object, locals, template, options)
- locals[template.counter_name] = index
-
- index += 1
-
- _render_template(template, locals)
- end.join(spacer)
+ end
end
+ end
+
+ def render_partial(options)
+ @assigns_added = false
+ # TODO: Handle other details here.
+ self.formats = options[:_details][:formats] if options[:_details]
+ _render_partial(options)
+ end
+
+ def _render_partial(options, &block) #:nodoc:
+ PartialRenderer.new(self, options, block).render
+ end
- def _pick_partial_template(partial_path) #:nodoc:
- prefix = controller_path unless partial_path.include?('/')
- find_by_parts(partial_path, {:formats => formats}, prefix, true)
- end
- memoize :_pick_partial_template
end
end
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index 162e38c484..c7afc56e3b 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -10,24 +10,24 @@ module ActionView
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
- def render(options = {}, local_assigns = {}, &block) #:nodoc:
- local_assigns ||= {}
-
- @exempt_from_layout = true
-
+ def render(options = {}, locals = {}, &block) #:nodoc:
case options
+ when String, NilClass
+ _render_partial(:partial => options, :locals => locals || {})
when Hash
- options[:locals] ||= {}
layout = options[:layout]
-
- return _render_partial_with_layout(layout, options) if options.key?(:partial)
- return _render_partial_with_block(layout, block, options) if block_given?
-
- layout = find_by_parts(layout, {:formats => formats}) if layout
-
+
+ if block_given?
+ return concat(_render_partial(options.merge(:partial => layout), &block))
+ elsif options.key?(:partial)
+ return _render_partial(options)
+ end
+
+ layout = find(layout, {:formats => formats}) if layout
+
if file = options[:file]
- template = find_by_parts(file, {:formats => formats})
- _render_template_with_layout(template, layout, :locals => options[:locals])
+ template = find(file, {:formats => formats})
+ _render_template(template, layout, :locals => options[:locals] || {})
elsif inline = options[:inline]
_render_inline(inline, layout, options)
elsif text = options[:text]
@@ -35,35 +35,33 @@ module ActionView
end
when :update
update_page(&block)
- when String, NilClass
- _render_partial(:partial => options, :locals => local_assigns)
end
end
-
- def _render_content_with_layout(content, layout, locals)
+
+ def _render_content(content, layout, locals)
return content unless layout
-
+
locals ||= {}
if controller && layout
@_layout = layout.identifier
logger.info("Rendering template within #{layout.identifier}") if logger
end
-
+
begin
old_content, @_content_for[:layout] = @_content_for[:layout], content
@cached_content_for_layout = @_content_for[:layout]
- _render_template(layout, locals)
+ _render_single_template(layout, locals)
ensure
@_content_for[:layout] = old_content
end
end
- # You can think of a layout as a method that is called with a block. This method
- # returns the block that the layout is called with. If the user calls yield :some_name,
- # the block, by default, returns content_for(:some_name). If the user calls yield,
- # the default block returns content_for(:layout).
+ # You can think of a layout as a method that is called with a block. _layout_for
+ # returns the contents that are yielded to the layout. If the user calls yield
+ # :some_name, the block, by default, returns content_for(:some_name). If the user
+ # calls yield, the default block returns content_for(:layout).
#
# The user can override this default by passing a block to the layout.
#
@@ -92,15 +90,28 @@ module ActionView
# In this case, the layout would receive the block passed into <tt>render :layout</tt>,
# and the Struct specified in the layout would be passed into the block. The result
# would be <html>Hello David</html>.
- def layout_proc(name)
- @_default_layout ||= proc { |*names| @_content_for[names.first || :layout] }
- !@_content_for.key?(name) && @_proc_for_layout || @_default_layout
+ def _layout_for(names, &block)
+ with_output_buffer do
+ # This is due to the potentially ambiguous use of yield when
+ # a block is passed in to a template *and* there is a content_for()
+ # of the same name. Suggested solution: require explicit use of content_for
+ # in these ambiguous cases.
+ #
+ # We would be able to continue supporting yield in all non-ambiguous
+ # cases. Question: should we deprecate yield in favor of content_for
+ # and reserve yield for cases where there is a yield into a real block?
+ if @_content_for.key?(names.first) || !block_given?
+ return @_content_for[names.first || :layout]
+ else
+ return yield(names)
+ end
+ end
end
- def _render_template(template, local_assigns = {})
+ def _render_single_template(template, locals = {}, &block)
with_template(template) do
- template.render(self, local_assigns) do |*names|
- capture(*names, &layout_proc(names.first))
+ template.render(self, locals) do |*names|
+ _layout_for(names, &block)
end
end
rescue Exception => e
@@ -115,32 +126,42 @@ module ActionView
def _render_inline(inline, layout, options)
handler = Template.handler_class_for_extension(options[:type] || "erb")
template = Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {})
- content = _render_template(template, options[:locals] || {})
- layout ? _render_content_with_layout(content, layout, options[:locals]) : content
+ content = _render_single_template(template, options[:locals] || {})
+ layout ? _render_content(content, layout, options[:locals]) : content
end
def _render_text(text, layout, options)
- layout ? _render_content_with_layout(text, layout, options[:locals]) : text
+ layout ? _render_content(text, layout, options[:locals]) : text
end
- def _render_template_from_controller(*args)
+ # This is the API to render a ViewContext's template from a controller.
+ #
+ # Internal Options:
+ # _template:: The Template object to render
+ # _layout:: The layout, if any, to wrap the Template in
+ # _partial:: true if the template is a partial
+ def render_template(options)
@assigns_added = nil
- _render_template_with_layout(*args)
+ template, layout, partial = options.values_at(:_template, :_layout, :_partial)
+ _render_template(template, layout, options, partial)
end
- def _render_template_with_layout(template, layout = nil, options = {}, partial = false)
- logger && logger.info("Rendering #{template.identifier}#{' (#{options[:status]})' if options[:status]}")
+ def _render_template(template, layout = nil, options = {}, partial = nil)
+ logger && logger.info do
+ msg = "Rendering #{template.identifier}"
+ msg << " (#{options[:status]})" if options[:status]
+ msg
+ end
locals = options[:locals] || {}
content = if partial
- object = partial unless partial == true
- _render_partial_object(template, options, object)
+ _render_partial_object(template, options)
else
- _render_template(template, locals)
+ _render_single_template(template, locals)
end
-
- layout ? _render_content_with_layout(content, layout, locals) : content
+
+ _render_content(content, layout, locals)
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index d15f53a11b..ebfc6cc8ce 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -10,7 +10,7 @@ module ActionView
end
# Normalizes the arguments and passes it on to find_template
- def find_by_parts(*args)
+ def find(*args)
find_all_by_parts(*args).first
end
diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb
index 4145045e2d..33d3f79ad3 100644
--- a/actionpack/lib/action_view/template/template.rb
+++ b/actionpack/lib/action_view/template/template.rb
@@ -7,19 +7,22 @@ require "action_view/template/resolver"
module ActionView
class Template
extend TemplateHandlers
- attr_reader :source, :identifier, :handler, :mime_type, :details
+ attr_reader :source, :identifier, :handler, :mime_type, :formats, :details
def initialize(source, identifier, handler, details)
@source = source
@identifier = identifier
@handler = handler
@details = details
+ @method_names = {}
format = details.delete(:format) || begin
# TODO: Clean this up
handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html"
end
@mime_type = Mime::Type.lookup_by_extension(format.to_s)
+ @formats = [format.to_sym]
+ @formats << :html if format == :js
@details[:formats] = Array.wrap(format.to_sym)
end
@@ -30,12 +33,12 @@ module ActionView
# TODO: Figure out how to abstract this
def variable_name
- identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
+ @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym
end
# TODO: Figure out how to abstract this
def counter_name
- "#{variable_name}_counter".to_sym
+ @counter_name ||= "#{variable_name}_counter".to_sym
end
# TODO: kill hax
@@ -90,7 +93,8 @@ module ActionView
def build_method_name(locals)
# TODO: is locals.keys.hash reliably the same?
- "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
+ @method_names[locals.keys.hash] ||=
+ "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_")
end
end
end
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index 81944ff546..9f12e5e0a8 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -15,7 +15,9 @@ module ActionView #:nodoc:
def render(*) self end
def mime_type() @content_type end
-
+
+ def formats() [mime_type] end
+
def partial?() false end
end
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 3f3951509a..e51744d095 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -9,14 +9,14 @@ module ActionView
end
attr_internal :rendered
- alias_method :_render_template_without_template_tracking, :_render_template
- def _render_template(template, local_assigns = {})
+ alias_method :_render_template_without_template_tracking, :_render_single_template
+ def _render_single_template(template, local_assigns, &block)
if template.respond_to?(:identifier) && template.present?
@_rendered[:partials][template] += 1 if template.partial?
@_rendered[:template] ||= []
@_rendered[:template] << template
end
- _render_template_without_template_tracking(template, local_assigns)
+ _render_template_without_template_tracking(template, local_assigns, &block)
end
end
diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb
index 56ec6a6a31..9438a4dfc9 100644
--- a/actionpack/test/abstract_controller/abstract_controller_test.rb
+++ b/actionpack/test/abstract_controller/abstract_controller_test.rb
@@ -27,7 +27,7 @@ module AbstractController
# Test Render mixin
# ====
class RenderingController < AbstractController::Base
- include Renderer
+ include ::AbstractController::RenderingController
def _prefix() end
@@ -65,8 +65,8 @@ module AbstractController
self.response_body = render_to_string :_template_name => "naked_render.erb"
end
end
-
- class TestRenderer < ActiveSupport::TestCase
+
+ class TestRenderingController < ActiveSupport::TestCase
test "rendering templates works" do
result = Me2.new.process(:index)
assert_equal "Hello from index.erb", result.response_body
@@ -139,10 +139,10 @@ module AbstractController
private
def self.layout(formats)
begin
- view_paths.find_by_parts(name.underscore, {:formats => formats}, "layouts")
+ view_paths.find(name.underscore, {:formats => formats}, "layouts")
rescue ActionView::MissingTemplate
begin
- view_paths.find_by_parts("application", {:formats => formats}, "layouts")
+ view_paths.find("application", {:formats => formats}, "layouts")
rescue ActionView::MissingTemplate
end
end
diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb
index 0a2535f834..e9a60c0307 100644
--- a/actionpack/test/abstract_controller/helper_test.rb
+++ b/actionpack/test/abstract_controller/helper_test.rb
@@ -4,7 +4,7 @@ module AbstractController
module Testing
class ControllerWithHelpers < AbstractController::Base
- include Renderer
+ include AbstractController::RenderingController
include Helpers
def render(string)
@@ -40,4 +40,4 @@ module AbstractController
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb
index b28df7743f..42f73faa61 100644
--- a/actionpack/test/abstract_controller/layouts_test.rb
+++ b/actionpack/test/abstract_controller/layouts_test.rb
@@ -6,7 +6,7 @@ module AbstractControllerTests
# Base controller for these tests
class Base < AbstractController::Base
- include AbstractController::Renderer
+ include AbstractController::RenderingController
include AbstractController::Layouts
self.view_paths = [ActionView::FixtureResolver.new(
diff --git a/actionpack/test/abstract_controller/test_helper.rb b/actionpack/test/abstract_controller/test_helper.rb
index 915054f7fb..ba4302d914 100644
--- a/actionpack/test/abstract_controller/test_helper.rb
+++ b/actionpack/test/abstract_controller/test_helper.rb
@@ -6,7 +6,7 @@ require 'rubygems'
require 'test/unit'
require 'active_support'
require 'active_support/test_case'
-require 'action_controller/abstract'
+require 'abstract_controller'
require 'action_view'
require 'action_view/base'
require 'action_dispatch'
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 6e71b85645..b062a71442 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -13,7 +13,6 @@ require 'test/unit'
require 'active_support'
require 'active_support/test_case'
-require 'action_controller/abstract'
require 'action_controller'
require 'fixture_template'
require 'action_controller/testing/process'
@@ -78,7 +77,7 @@ module ActionController
def assert_template(options = {}, message = nil)
validate_request!
- hax = @controller._action_view.instance_variable_get(:@_rendered)
+ hax = @controller.view_context.instance_variable_get(:@_rendered)
case options
when NilClass, String
diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb
index 2036d1eeb5..37f1f6dff8 100644
--- a/actionpack/test/activerecord/polymorphic_routes_test.rb
+++ b/actionpack/test/activerecord/polymorphic_routes_test.rb
@@ -45,6 +45,12 @@ class PolymorphicRoutesTest < ActionController::TestCase
assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(@project)
end
end
+
+ def test_with_class
+ with_test_routes do
+ assert_equal "http://example.com/projects", polymorphic_url(@project.class)
+ end
+ end
def test_with_new_record
with_test_routes do
@@ -52,6 +58,13 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_destroyed_record
+ with_test_routes do
+ @project.destroy
+ assert_equal "http://example.com/projects", polymorphic_url(@project)
+ end
+ end
+
def test_with_record_and_action
with_test_routes do
assert_equal "http://example.com/projects/new", polymorphic_url(@project, :action => 'new')
@@ -129,6 +142,27 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_nested_destroyed
+ with_test_routes do
+ @project.save
+ @task.destroy
+ assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task])
+ end
+ end
+
+ def test_with_nested_class
+ with_test_routes do
+ @project.save
+ assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task.class])
+ end
+ end
+
+ def test_class_with_array_and_namespace
+ with_admin_test_routes do
+ assert_equal "http://example.com/admin/projects", polymorphic_url([:admin, @project.class])
+ end
+ end
+
def test_new_with_array_and_namespace
with_admin_test_routes do
assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], :action => 'new')
@@ -252,11 +286,24 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_with_irregular_plural_class
+ with_test_routes do
+ assert_equal "http://example.com/taxes", polymorphic_url(@tax.class)
+ end
+ end
+
def test_with_irregular_plural_new_record
with_test_routes do
assert_equal "http://example.com/taxes", polymorphic_url(@tax)
end
end
+
+ def test_with_irregular_plural_destroyed_record
+ with_test_routes do
+ @tax.destroy
+ assert_equal "http://example.com/taxes", polymorphic_url(@tax)
+ end
+ end
def test_with_irregular_plural_record_and_action
with_test_routes do
@@ -298,6 +345,12 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
+ def test_class_with_irregular_plural_array_and_namespace
+ with_admin_test_routes do
+ assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax.class])
+ end
+ end
+
def test_unsaved_with_irregular_plural_array_and_namespace
with_admin_test_routes do
assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax])
@@ -395,4 +448,4 @@ class PolymorphicRoutesTest < ActionController::TestCase
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb
index ad17d1288b..2e77d2f8ad 100644
--- a/actionpack/test/controller/assert_select_test.rb
+++ b/actionpack/test/controller/assert_select_test.rb
@@ -257,6 +257,13 @@ class AssertSelectTest < ActionController::TestCase
end
assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"}
end
+
+ def test_assert_select_rjs_for_redirect_to
+ render_rjs do |page|
+ page.redirect_to '/'
+ end
+ assert_select_rjs :redirect, '/'
+ end
def test_elect_with_xml_namespace_attributes
render_html %Q{<link xlink:href="http://nowhere.com"></link>}
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index c286976315..68529cc8f7 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -625,6 +625,21 @@ class FragmentCachingTest < ActionController::TestCase
assert !fragment_computed
assert_equal 'generated till now -> fragment content', buffer
end
+
+ def test_fragment_for_logging
+ fragment_computed = false
+
+ @controller.class.expects(:benchmark).with('Cached fragment exists?: views/expensive')
+ @controller.class.expects(:benchmark).with('Cached fragment miss: views/expensive')
+ @controller.class.expects(:benchmark).with('Cached fragment hit: views/expensive').never
+
+ buffer = 'generated till now -> '
+ @controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
+
+ assert fragment_computed
+ assert_equal 'generated till now -> ', buffer
+ end
+
end
class FunctionalCachingController < ActionController::Base
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index fbc94a0df7..23688ca584 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -4,6 +4,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
class DummyController < ActionController::Base
before_filter :authenticate, :only => :index
before_filter :authenticate_with_request, :only => :display
+ before_filter :authenticate_long_credentials, :only => :show
def index
render :text => "Hello Secret"
@@ -12,6 +13,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
def display
render :text => 'Definitely Maybe'
end
+
+ def show
+ render :text => 'Only for loooooong credentials'
+ end
private
@@ -28,6 +33,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
request_http_basic_authentication("SuperSecret")
end
end
+
+ def authenticate_long_credentials
+ authenticate_or_request_with_http_basic do |username, password|
+ username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890'
+ end
+ end
end
AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION']
@@ -42,6 +53,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_response :success
assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}"
end
+ test "successful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890')
+ get :show
+
+ assert_response :success
+ assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials"
+ end
end
AUTH_HEADERS.each do |header|
@@ -52,6 +70,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert_response :unauthorized
assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}"
end
+ test "unsuccessful authentication with #{header.downcase} and long credentials" do
+ @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld')
+ get :show
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials"
+ end
end
test "authentication request without credential" do
diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb
index 58f3b88075..7e9a2625f1 100644
--- a/actionpack/test/controller/http_digest_authentication_test.rb
+++ b/actionpack/test/controller/http_digest_authentication_test.rb
@@ -136,7 +136,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert_equal 'Definitely Maybe', @response.body
end
- test "authentication request with request-uri that doesn't match credentials digest-uri" do
+ test "authentication request with request-uri that doesn't match credentials digest-uri" do
@request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
@request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri"
get :display
@@ -145,10 +145,33 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
assert_equal "Authentication Failed", @response.body
end
- test "authentication request with absolute uri" do
- @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display",
+ test "authentication request with absolute request uri (as in webrick)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please')
+ @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in credentials (as in IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
:username => 'pretty', :password => 'please')
- @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display"
+
+ get :display
+
+ assert_response :success
+ assert assigns(:logged_in)
+ assert_equal 'Definitely Maybe', @response.body
+ end
+
+ test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest",
+ :username => 'pretty', :password => 'please')
+ @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest"
+
get :display
assert_response :success
@@ -202,11 +225,11 @@ class HttpDigestAuthenticationTest < ActionController::TestCase
credentials = decode_credentials(@response.headers['WWW-Authenticate'])
credentials.merge!(options)
- credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}")
+ credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s)
ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1])
end
def decode_credentials(header)
ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate'])
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/logging_test.rb b/actionpack/test/controller/logging_test.rb
index a7ed1b8665..98ffbc3813 100644
--- a/actionpack/test/controller/logging_test.rb
+++ b/actionpack/test/controller/logging_test.rb
@@ -17,9 +17,10 @@ class LoggingTest < ActionController::TestCase
@level = Logger::DEBUG
end
- def method_missing(method, *args)
+ def method_missing(method, *args, &blk)
@logged ||= []
@logged << args.first
+ @logged << blk.call if block_given?
end
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 117f4ea4f0..8319b5c573 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'controller/fake_models'
class RespondToController < ActionController::Base
layout :set_layout
@@ -471,22 +472,10 @@ class RespondToControllerTest < ActionController::TestCase
end
end
-class RespondResource
- undef_method :to_json
-
- def to_xml
- "XML"
- end
-
- def to_js
- "JS"
- end
-end
-
class RespondWithController < ActionController::Base
respond_to :html, :json
respond_to :xml, :except => :using_defaults
- respond_to :js, :only => :using_defaults
+ respond_to :js, :only => [ :using_defaults, :using_resource ]
def using_defaults
respond_to do |format|
@@ -498,20 +487,27 @@ class RespondWithController < ActionController::Base
respond_to(:js, :xml)
end
+ def default_overwritten
+ respond_to do |format|
+ format.html { render :text => "HTML" }
+ end
+ end
+
def using_resource
- respond_with(RespondResource.new)
+ respond_with(Customer.new("david", 13))
end
- def using_resource_with_options
- respond_with(RespondResource.new, :status => :unprocessable_entity) do |format|
- format.js
- end
+ def using_resource_with_parent
+ respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)])
end
- def default_overwritten
- respond_to do |format|
- format.html { render :text => "HTML" }
- end
+ def using_resource_with_status_and_location
+ respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created)
+ end
+
+ def using_resource_with_responder
+ responder = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" }
+ respond_with(Customer.new("david", 13), :responder => responder)
end
protected
@@ -527,7 +523,7 @@ class InheritedRespondWithController < RespondWithController
respond_to :xml, :json
def index
- respond_with(RespondResource.new) do |format|
+ respond_with(Customer.new("david", 13)) do |format|
format.json { render :text => "JSON" }
end
end
@@ -540,6 +536,11 @@ class RespondWithControllerTest < ActionController::TestCase
super
ActionController::Base.use_accept_header = true
@request.host = "www.example.com"
+
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :customers
+ map.resources :quiz_stores, :has_many => :customers
+ end
end
def teardown
@@ -576,11 +577,17 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "<p>Hello world!</p>\n", @response.body
end
+ def test_default_overwritten
+ get :default_overwritten
+ assert_equal "text/html", @response.content_type
+ assert_equal "HTML", @response.body
+ end
+
def test_using_resource
- @request.accept = "text/html"
+ @request.accept = "text/javascript"
get :using_resource
- assert_equal "text/html", @response.content_type
- assert_equal "Hello world!", @response.body
+ assert_equal "text/javascript", @response.content_type
+ assert_equal '$("body").visualEffect("highlight");', @response.body
@request.accept = "application/xml"
get :using_resource
@@ -593,24 +600,114 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
- def test_using_resource_with_options
+ def test_using_resource_for_post_with_html
+ post :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "New world!\n", @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_post_with_xml
@request.accept = "application/xml"
- get :using_resource_with_options
+
+ post :using_resource
assert_equal "application/xml", @response.content_type
- assert_equal 422, @response.status
+ assert_equal 201, @response.status
assert_equal "XML", @response.body
+ assert_equal "http://www.example.com/customers/13", @response.location
- @request.accept = "text/javascript"
- get :using_resource_with_options
- assert_equal "text/javascript", @response.content_type
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
- assert_equal "JS", @response.body
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
end
- def test_default_overwritten
- get :default_overwritten
+ def test_using_resource_for_put_with_html
+ put :using_resource
assert_equal "text/html", @response.content_type
- assert_equal "HTML", @response.body
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers/13", @response.location
+ assert @response.redirect?
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "Edit world!\n", @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_put_with_xml
+ @request.accept = "application/xml"
+
+ put :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal " ", @response.body
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_delete_with_html
+ Customer.any_instance.stubs(:destroyed?).returns(true)
+ delete :using_resource
+ assert_equal "text/html", @response.content_type
+ assert_equal 302, @response.status
+ assert_equal "http://www.example.com/customers", @response.location
+ end
+
+ def test_using_resource_for_delete_with_xml
+ Customer.any_instance.stubs(:destroyed?).returns(true)
+ @request.accept = "application/xml"
+ delete :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal " ", @response.body
+ end
+
+ def test_using_resource_with_parent_for_get
+ @request.accept = "application/xml"
+ get :using_resource_with_parent
+ assert_equal "application/xml", @response.content_type
+ assert_equal 200, @response.status
+ assert_equal "XML", @response.body
+ end
+
+ def test_using_resource_with_parent_for_post
+ @request.accept = "application/xml"
+
+ post :using_resource_with_parent
+ assert_equal "application/xml", @response.content_type
+ assert_equal 201, @response.status
+ assert_equal "XML", @response.body
+ assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location
+
+ errors = { :name => :invalid }
+ Customer.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
end
def test_clear_respond_to
@@ -628,6 +725,29 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "XML", @response.body
end
+ def test_no_double_render_is_raised
+ @request.accept = "text/html"
+ assert_raise ActionView::MissingTemplate do
+ get :using_resource
+ end
+ end
+
+ def test_using_resource_with_status_and_location
+ @request.accept = "text/html"
+ post :using_resource_with_status_and_location
+ assert @response.redirect?
+ assert_equal "http://test.host/", @response.location
+
+ @request.accept = "application/xml"
+ get :using_resource_with_status_and_location
+ assert_equal 201, @response.status
+ end
+
+ def test_using_resource_with_responder
+ get :using_resource_with_responder
+ assert_equal "Resource name is david", @response.body
+ end
+
def test_not_acceptable
@request.accept = "application/xml"
get :using_defaults
@@ -642,14 +762,12 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal 406, @response.status
@request.accept = "text/javascript"
- get :using_resource
+ get :default_overwritten
assert_equal 406, @response.status
end
end
class AbstractPostController < ActionController::Base
- respond_to :html, :iphone
-
self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/"
end
@@ -658,7 +776,7 @@ class PostController < AbstractPostController
around_filter :with_iphone
def index
- respond_to # It will use formats declared above
+ respond_to(:html, :iphone)
end
protected
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index 44e49ed3f8..6b6d154faa 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -54,24 +54,6 @@ class RecordIdentifierTest < Test::Unit::TestCase
assert_equal "edit_#{@singular}_1", dom_id(@record, :edit)
end
- def test_partial_path
- expected = "#{@plural}/#{@singular}"
- assert_equal expected, partial_path(@record)
- assert_equal expected, partial_path(Comment)
- end
-
- def test_partial_path_with_namespaced_controller_path
- expected = "admin/#{@plural}/#{@singular}"
- assert_equal expected, partial_path(@record, "admin/posts")
- assert_equal expected, partial_path(@klass, "admin/posts")
- end
-
- def test_partial_path_with_not_namespaced_controller_path
- expected = "#{@plural}/#{@singular}"
- assert_equal expected, partial_path(@record, "posts")
- assert_equal expected, partial_path(@klass, "posts")
- end
-
def test_dom_class
assert_equal @singular, dom_class(@record)
end
@@ -101,42 +83,3 @@ class RecordIdentifierTest < Test::Unit::TestCase
RecordIdentifier.send(method, *args)
end
end
-
-class NestedRecordIdentifierTest < RecordIdentifierTest
- def setup
- @klass = Comment::Nested
- @record = @klass.new
- @singular = 'comment_nested'
- @plural = 'comment_nesteds'
- end
-
- def test_partial_path
- expected = "comment/nesteds/nested"
- assert_equal expected, partial_path(@record)
- assert_equal expected, partial_path(Comment::Nested)
- end
-
- def test_partial_path_with_namespaced_controller_path
- expected = "admin/comment/nesteds/nested"
- assert_equal expected, partial_path(@record, "admin/posts")
- assert_equal expected, partial_path(@klass, "admin/posts")
- end
-
- def test_partial_path_with_deeper_namespaced_controller_path
- expected = "deeper/admin/comment/nesteds/nested"
- assert_equal expected, partial_path(@record, "deeper/admin/posts")
- assert_equal expected, partial_path(@klass, "deeper/admin/posts")
- end
-
- def test_partial_path_with_even_deeper_namespaced_controller_path
- expected = "even/more/deeper/admin/comment/nesteds/nested"
- assert_equal expected, partial_path(@record, "even/more/deeper/admin/posts")
- assert_equal expected, partial_path(@klass, "even/more/deeper/admin/posts")
- end
-
- def test_partial_path_with_not_namespaced_controller_path
- expected = "comment/nesteds/nested"
- assert_equal expected, partial_path(@record, "posts")
- assert_equal expected, partial_path(@klass, "posts")
- end
-end
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index b3321303c0..7755af592d 100644
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -34,6 +34,10 @@ class RedirectController < ActionController::Base
redirect_to({:action => "hello_world"}, {:status => 301})
end
+ def redirect_with_protocol
+ redirect_to :action => "hello_world", :protocol => "https"
+ end
+
def url_redirect_with_status
redirect_to("http://www.example.com", :status => :moved_permanently)
end
@@ -132,6 +136,12 @@ class RedirectTest < ActionController::TestCase
assert_equal "http://test.host/redirect/hello_world", redirect_to_url
end
+ def test_redirect_with_protocol
+ get :redirect_with_protocol
+ assert_response 302
+ assert_equal "https://test.host/redirect/hello_world", redirect_to_url
+ end
+
def test_url_redirect_with_status
get :url_redirect_with_status
assert_response 301
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index acb0c895e0..0c0599679c 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -17,8 +17,9 @@ class MockLogger
@logged = []
end
- def method_missing(method, *args)
+ def method_missing(method, *args, &blk)
@logged << args.first
+ @logged << blk.call if block_given?
end
end
@@ -457,6 +458,10 @@ class TestController < ActionController::Base
head :location => "/foo"
end
+ def head_with_location_object
+ head :location => Customer.new("david", 1)
+ end
+
def head_with_symbolic_status
head :status => params[:status].intern
end
@@ -617,6 +622,7 @@ class TestController < ActionController::Base
end
private
+
def determine_layout
case action_name
when "hello_world", "layout_test", "rendering_without_layout",
@@ -1083,6 +1089,18 @@ class RenderTest < ActionController::TestCase
assert_response :ok
end
+ def test_head_with_location_object
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :customers
+ map.connect ':controller/:action/:id'
+ end
+
+ get :head_with_location_object
+ assert @response.body.blank?
+ assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
+ assert_response :ok
+ end
+
def test_head_with_custom_header
get :head_with_custom_header
assert @response.body.blank?
@@ -1219,7 +1237,6 @@ class RenderTest < ActionController::TestCase
def test_partial_collection_with_spacer
get :partial_collection_with_spacer
assert_equal "Hello: davidonly partialHello: mary", @response.body
- assert_template :partial => 'test/_partial_only'
assert_template :partial => '_customer'
end
@@ -1331,7 +1348,7 @@ class EtagRenderTest < ActionController::TestCase
def test_render_200_should_set_etag
get :render_hello_world_from_variable
assert_equal etag_for("hello david"), @response.headers['ETag']
- assert_equal "private, max-age=0, must-revalidate", @response.headers['Cache-Control']
+ assert_equal "max-age=0, private, must-revalidate", @response.headers['Cache-Control']
end
def test_render_against_etag_request_should_304_when_match
diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb
index 052b4f0b52..139f55d8bd 100644
--- a/actionpack/test/controller/render_xml_test.rb
+++ b/actionpack/test/controller/render_xml_test.rb
@@ -11,7 +11,7 @@ class TestController < ActionController::Base
def render_with_object_location
customer = Customer.new("Some guy", 1)
- render :xml => "<customer/>", :location => customer_url(customer), :status => :created
+ render :xml => "<customer/>", :location => customer, :status => :created
end
def render_with_to_xml
@@ -78,4 +78,4 @@ class RenderTest < ActionController::TestCase
get :implicit_content_type, :format => 'atom'
assert_equal Mime::ATOM, @response.content_type
end
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index fb83dba395..5f9ae6292c 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,3 +1,4 @@
+# encoding: utf-8
require 'abstract_unit'
require 'controller/fake_controllers'
require 'active_support/dependencies'
@@ -1179,7 +1180,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase
assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo"))
token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian
- token.force_encoding("UTF-8") if token.respond_to?(:force_encoding)
escaped_token = CGI::escape(token)
assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token)
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index ae32ee5649..0afebac68c 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -1,9 +1,10 @@
+# encoding: utf-8
require 'abstract_unit'
module TestFileUtils
def file_name() File.basename(__FILE__) end
def file_path() File.expand_path(__FILE__) end
- def file_data() File.open(file_path, 'rb') { |f| f.read } end
+ def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
end
class SendFileController < ActionController::Base
@@ -22,6 +23,10 @@ class SendFileController < ActionController::Base
def data
send_data(file_data, options)
end
+
+ def multibyte_text_data
+ send_data("Кирилица\n祝您好運", options)
+ end
end
class SendFileTest < ActionController::TestCase
@@ -55,6 +60,7 @@ class SendFileTest < ActionController::TestCase
require 'stringio'
output = StringIO.new
output.binmode
+ output.string.force_encoding(file_data.encoding) if output.string.respond_to?(:force_encoding)
assert_nothing_raised { response.body_parts.each { |part| output << part.to_s } }
assert_equal file_data, output.string
end
@@ -123,7 +129,7 @@ class SendFileTest < ActionController::TestCase
# test overriding Cache-Control: no-cache header to fix IE open/save dialog
@controller.headers = { 'Cache-Control' => 'no-cache' }
@controller.send(:send_file_headers!, options)
- h = @controller.headers
+ @controller.response.prepare!
assert_equal 'private', h['Cache-Control']
end
@@ -163,4 +169,11 @@ class SendFileTest < ActionController::TestCase
assert_equal 200, @response.status
end
end
+
+ def test_send_data_content_length_header
+ @controller.headers = {}
+ @controller.options = { :type => :text, :filename => 'file_with_utf8_text' }
+ process('multibyte_text_data')
+ assert_equal '29', @controller.headers['Content-Length']
+ end
end
diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
index 9e008a9ae8..301080842e 100644
--- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb
@@ -120,8 +120,6 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
fixture = FIXTURE_PATH + "/mona_lisa.jpg"
params = { :uploaded_data => fixture_file_upload(fixture, "image/jpg") }
post '/read', params
- expected_length = 'File: '.length + File.size(fixture)
- assert_equal expected_length, response.content_length
end
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index 8ebf9aa186..f3500fca34 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -72,6 +72,34 @@ class RequestTest < ActiveSupport::TestCase
assert_equal '9.9.9.9', request.remote_ip
end
+ test "remote ip with user specified trusted proxies" do
+ ActionController::Base.trusted_proxies = /^67\.205\.106\.73$/i
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '3.4.5.6'
+ assert_equal '67.205.106.74', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73'
+ assert_equal 'unknown', request.remote_ip
+
+ request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73'
+ assert_equal '3.4.5.6', request.remote_ip
+
+ ActionController::Base.trusted_proxies = nil
+ end
+
test "domains" do
request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org'
assert_equal "rubyonrails.org", request.domain
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 2ddc6cb2b5..256ed06a45 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -13,10 +13,9 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
- "Cache-Control" => "private, max-age=0, must-revalidate",
+ "Cache-Control" => "max-age=0, private, must-revalidate",
"ETag" => '"65a8e27d8879283831b664bd8b7f0ad4"',
- "Set-Cookie" => "",
- "Content-Length" => "13"
+ "Set-Cookie" => ""
}, headers)
parts = []
@@ -32,10 +31,9 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8",
- "Cache-Control" => "private, max-age=0, must-revalidate",
+ "Cache-Control" => "max-age=0, private, must-revalidate",
"ETag" => '"ebb5e89e8a94e9dd22abf5d915d112b2"',
- "Set-Cookie" => "",
- "Content-Length" => "8"
+ "Set-Cookie" => ""
}, headers)
end
diff --git a/actionpack/test/fixtures/respond_with/edit.html.erb b/actionpack/test/fixtures/respond_with/edit.html.erb
new file mode 100644
index 0000000000..ae82dfa4fc
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/edit.html.erb
@@ -0,0 +1 @@
+Edit world!
diff --git a/actionpack/test/fixtures/respond_with/new.html.erb b/actionpack/test/fixtures/respond_with/new.html.erb
new file mode 100644
index 0000000000..96c8f1b88b
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/new.html.erb
@@ -0,0 +1 @@
+New world!
diff --git a/actionpack/test/fixtures/respond_with/using_resource.html.erb b/actionpack/test/fixtures/respond_with/using_resource.html.erb
deleted file mode 100644
index 6769dd60bd..0000000000
--- a/actionpack/test/fixtures/respond_with/using_resource.html.erb
+++ /dev/null
@@ -1 +0,0 @@
-Hello world! \ No newline at end of file
diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.rjs b/actionpack/test/fixtures/respond_with/using_resource.js.rjs
new file mode 100644
index 0000000000..737c175a4e
--- /dev/null
+++ b/actionpack/test/fixtures/respond_with/using_resource.js.rjs
@@ -0,0 +1 @@
+page[:body].visual_effect :highlight
diff --git a/actionpack/test/fixtures/test/greeting.xml.erb b/actionpack/test/fixtures/test/greeting.xml.erb
new file mode 100644
index 0000000000..62fb0293f0
--- /dev/null
+++ b/actionpack/test/fixtures/test/greeting.xml.erb
@@ -0,0 +1 @@
+<p>This is grand!</p>
diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb
index c6726432ec..0faf8f3f9a 100644
--- a/actionpack/test/lib/controller/fake_models.rb
+++ b/actionpack/test/lib/controller/fake_models.rb
@@ -4,9 +4,27 @@ class Customer < Struct.new(:name, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
+ undef_method :to_json
+
def to_param
id.to_s
end
+
+ def to_xml
+ "XML"
+ end
+
+ def to_js
+ "JS"
+ end
+
+ def errors
+ []
+ end
+
+ def destroyed?
+ false
+ end
end
class BadCustomer < Customer
@@ -24,4 +42,8 @@ module Quiz
id.to_s
end
end
+
+ class Store < Question
+ end
end
+
diff --git a/actionpack/test/new_base/base_test.rb b/actionpack/test/new_base/base_test.rb
index d9d552f9e5..1b2e917ced 100644
--- a/actionpack/test/new_base/base_test.rb
+++ b/actionpack/test/new_base/base_test.rb
@@ -34,7 +34,6 @@ module Dispatching
assert_body "success"
assert_status 200
assert_content_type "text/html; charset=utf-8"
- assert_header "Content-Length", "7"
end
# :api: plugin
@@ -42,7 +41,6 @@ module Dispatching
get "/dispatching/simple/modify_response_body"
assert_body "success"
- assert_header "Content-Length", "7" # setting the body manually sets the content length
end
# :api: plugin
@@ -50,7 +48,6 @@ module Dispatching
get "/dispatching/simple/modify_response_body_twice"
assert_body "success!"
- assert_header "Content-Length", "8"
end
test "controller path" do
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 921bfeb93a..28f9d48671 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -83,7 +83,10 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag(:all)) => %(<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/application.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="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:all, :recursive => true)) => %(<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/application.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="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults, "bank")) => %(<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/bank.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
- %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<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/application.js" type="text/javascript"></script>)
+ %(javascript_include_tag("bank", :defaults)) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>\n<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/application.js" type="text/javascript"></script>),
+
+ %(javascript_include_tag("http://example.com/all")) => %(<script src="http://example.com/all" type="text/javascript"></script>),
+ %(javascript_include_tag("http://example.com/all.js")) => %(<script src="http://example.com/all.js" type="text/javascript"></script>),
}
StylePathToTag = {
@@ -111,7 +114,8 @@ class AssetTagHelperTest < ActionView::TestCase
%(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("random.styles", "/elsewhere/file")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/elsewhere/file.css" media="screen" rel="stylesheet" type="text/css" />),
- %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />)
+ %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />),
}
ImagePathToTag = {
@@ -158,8 +162,8 @@ class AssetTagHelperTest < ActionView::TestCase
VideoLinkToTag = {
%(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg" />),
- %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="true" controls="true" src="/videos/rss.m4v" />),
- %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="true" src="/videos/rss.m4v" />),
+ %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v" />),
+ %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v" />),
%(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160" />),
%(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320" />),
%(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg" />),
@@ -168,7 +172,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi" />),
%(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov" />),
%(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="multiple.ogg" /><source src="multiple.avi" /></video>),
- %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="true" height="120" width="160"><source src="multiple.ogg" /><source src="multiple.avi" /></video>)
+ %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="multiple.ogg" /><source src="multiple.avi" /></video>)
}
AudioPathToTag = {
@@ -187,7 +191,7 @@ class AssetTagHelperTest < ActionView::TestCase
AudioLinkToTag = {
%(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav" />),
- %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="true" controls="true" src="/audios/rss.wav" />),
+ %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav" />),
%(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov" />),
}
diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb
index 3acaecd142..6a5fb0acff 100644
--- a/actionpack/test/template/atom_feed_helper_test.rb
+++ b/actionpack/test/template/atom_feed_helper_test.rb
@@ -157,6 +157,26 @@ class ScrollsController < ActionController::Base
end
end
EOT
+ FEEDS["provide_builder"] = <<-'EOT'
+ # we pass in the new_xml to the helper so it doesn't
+ # call anything on the original builder
+ new_xml = Builder::XmlMarkup.new(:target=>'')
+ atom_feed(:xml => new_xml) do |feed|
+ feed.title("My great blog!")
+ feed.updated((@scrolls.first.created_at))
+
+ for scroll in @scrolls
+ feed.entry(scroll) do |entry|
+ entry.title(scroll.title)
+ entry.content(scroll.body, :type => 'html')
+
+ entry.author do |author|
+ author.name("DHH")
+ end
+ end
+ end
+ end
+ EOT
def index
@scrolls = [
Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)),
@@ -202,6 +222,15 @@ class AtomFeedTest < ActionController::TestCase
end
end
+ def test_providing_builder_to_atom_feed
+ with_restful_routing(:scrolls) do
+ get :index, :id=>"provide_builder"
+ # because we pass in the non-default builder, the content generated by the
+ # helper should go 'nowhere'. Leaving the response body blank.
+ assert @response.body.blank?
+ end
+ end
+
def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record
with_restful_routing(:scrolls) do
get :index, :id => "entry_options"
diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb
index bac67c1a7d..defe85107e 100644
--- a/actionpack/test/template/body_parts_test.rb
+++ b/actionpack/test/template/body_parts_test.rb
@@ -4,7 +4,7 @@ class BodyPartsTest < ActionController::TestCase
RENDERINGS = [Object.new, Object.new, Object.new]
class TestController < ActionController::Base
- def performed?() true end
+ def response_body() "" end
def index
RENDERINGS.each do |rendering|
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 99160dd8b1..2b1d80b1bf 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -307,6 +307,16 @@ class FormHelperTest < ActionView::TestCase
)
end
+ def test_radio_button_with_booleans
+ assert_dom_equal('<input id="post_secret_true" name="post[secret]" type="radio" value="true" />',
+ radio_button("post", "secret", true)
+ )
+
+ assert_dom_equal('<input id="post_secret_false" name="post[secret]" type="radio" value="false" />',
+ radio_button("post", "secret", false)
+ )
+ end
+
def test_text_area
assert_dom_equal(
'<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>',
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 79004264fd..d64b9492e2 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -136,6 +136,18 @@ class FormTagHelperTest < ActionView::TestCase
assert_match VALID_HTML_ID, input_elem['id']
end
+ def test_select_tag_with_include_blank
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => true
+ expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
+ def test_select_tag_with_include_blank_with_string
+ actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :include_blank => "string"
+ expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>)
+ assert_dom_equal expected, actual
+ end
+
def test_text_area_tag_size_string
actual = text_area_tag "body", "hello world", "size" => "20x40"
expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>)
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 57b740032e..85a97d570c 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -88,6 +88,7 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("111.00", number_with_precision(111, :precision => 2))
assert_equal("111.235", number_with_precision("111.2346"))
assert_equal("31.83", number_with_precision("31.825", :precision => 2))
+ assert_equal("3268", number_with_precision((32.675 * 100.00), :precision => 0))
assert_equal("112", number_with_precision(111.50, :precision => 0))
assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0))
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index ef88cae5b8..2aa3d5b5fa 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -71,6 +71,19 @@ class TagHelperTest < ActionView::TestCase
assert_equal '<p><b>Hello</b></p>', output_buffer
end
+ def test_content_tag_with_escaped_array_class
+ str = content_tag('p', "limelight", :class => ["song", "play>"])
+ assert_equal "<p class=\"song play&gt;\">limelight</p>", str
+
+ str = content_tag('p', "limelight", :class => ["song", "play"])
+ assert_equal "<p class=\"song play\">limelight</p>", str
+ end
+
+ def test_content_tag_with_unescaped_array_class
+ str = content_tag('p', "limelight", {:class => ["song", "play>"]}, false)
+ assert_equal "<p class=\"song play>\">limelight</p>", str
+ end
+
def test_cdata_section
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index 706b5085f4..b7823b9394 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'testing_sandbox'
+require 'redcloth'
class TextHelperTest < ActionView::TestCase
tests ActionView::Helpers::TextHelper
@@ -528,4 +529,20 @@ class TextHelperTest < ActionView::TestCase
assert_equal("red", cycle("red", "blue"))
assert_equal(%w{Specialized Fuji Giant}, @cycles)
end
+
+ def test_textilize
+ assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!"))
+ end
+
+ def test_textilize_with_blank
+ assert_equal("", textilize(""))
+ end
+
+ def test_textilize_with_options
+ assert_equal("<p>This is worded &lt;strong&gt;strongly&lt;/strong&gt;</p>", textilize("This is worded <strong>strongly</strong>", :filter_html))
+ end
+
+ def test_textilize_with_hard_breaks
+ assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True."))
+ end
end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 2de19597b1..9bb4cf8b54 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -26,6 +26,7 @@ $:.unshift(activesupport_path) if File.directory?(activesupport_path)
require 'active_support'
module ActiveModel
+ autoload :AttributeMethods, 'active_model/attribute_methods'
autoload :Conversion, 'active_model/conversion'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
autoload :Errors, 'active_model/errors'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
new file mode 100644
index 0000000000..de80559036
--- /dev/null
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -0,0 +1,267 @@
+module ActiveModel
+ class MissingAttributeError < NoMethodError
+ end
+
+ module AttributeMethods
+ extend ActiveSupport::Concern
+
+ # Declare and check for suffixed attribute methods.
+ module ClassMethods
+ # Defines an "attribute" method (like +inheritance_column+ or
+ # +table_name+). A new (class) method will be created with the
+ # given name. If a value is specified, the new method will
+ # return that value (as a string). Otherwise, the given block
+ # will be used to compute the value of the method.
+ #
+ # The original method will be aliased, with the new name being
+ # prefixed with "original_". This allows the new method to
+ # access the original value.
+ #
+ # Example:
+ #
+ # class A < ActiveRecord::Base
+ # define_attr_method :primary_key, "sysid"
+ # define_attr_method( :inheritance_column ) do
+ # original_inheritance_column + "_id"
+ # end
+ # end
+ def define_attr_method(name, value=nil, &block)
+ sing = metaclass
+ sing.send :alias_method, "original_#{name}", name
+ if block_given?
+ sing.send :define_method, name, &block
+ else
+ # use eval instead of a block to work around a memory leak in dev
+ # mode in fcgi
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
+ end
+ end
+
+ # Declares a method available for all attributes with the given prefix.
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
+ #
+ # #{prefix}#{attr}(*args, &block)
+ #
+ # to
+ #
+ # #{prefix}attribute(#{attr}, *args, &block)
+ #
+ # An <tt>#{prefix}attribute</tt> instance method must exist and accept at least
+ # the +attr+ argument.
+ #
+ # For example:
+ #
+ # class Person < ActiveRecord::Base
+ # attribute_method_prefix 'clear_'
+ #
+ # private
+ # def clear_attribute(attr)
+ # ...
+ # end
+ # end
+ #
+ # person = Person.find(1)
+ # person.name # => 'Gem'
+ # person.clear_name
+ # person.name # => ''
+ def attribute_method_prefix(*prefixes)
+ attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix })
+ undefine_attribute_methods
+ end
+
+ # Declares a method available for all attributes with the given suffix.
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
+ #
+ # #{attr}#{suffix}(*args, &block)
+ #
+ # to
+ #
+ # attribute#{suffix}(#{attr}, *args, &block)
+ #
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
+ # the +attr+ argument.
+ #
+ # For example:
+ #
+ # class Person < ActiveRecord::Base
+ # attribute_method_suffix '_short?'
+ #
+ # private
+ # def attribute_short?(attr)
+ # ...
+ # end
+ # end
+ #
+ # person = Person.find(1)
+ # person.name # => 'Gem'
+ # person.name_short? # => true
+ def attribute_method_suffix(*suffixes)
+ attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix })
+ undefine_attribute_methods
+ end
+
+ # Declares a method available for all attributes with the given prefix
+ # and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
+ # the method.
+ #
+ # #{prefix}#{attr}#{suffix}(*args, &block)
+ #
+ # to
+ #
+ # #{prefix}attribute#{suffix}(#{attr}, *args, &block)
+ #
+ # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
+ # accept at least the +attr+ argument.
+ #
+ # For example:
+ #
+ # class Person < ActiveRecord::Base
+ # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
+ #
+ # private
+ # def reset_attribute_to_default!(attr)
+ # ...
+ # end
+ # end
+ #
+ # person = Person.find(1)
+ # person.name # => 'Gem'
+ # person.reset_name_to_default!
+ # person.name # => 'Gemma'
+ def attribute_method_affix(*affixes)
+ attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] })
+ undefine_attribute_methods
+ end
+
+ def define_attribute_methods(attr_names)
+ return if attribute_methods_generated?
+ attr_names.each do |name|
+ attribute_method_matchers.each do |method|
+ method_name = "#{method.prefix}#{name}#{method.suffix}"
+ unless instance_method_already_implemented?(method_name)
+ generate_method = "define_method_#{method.prefix}attribute#{method.suffix}"
+
+ if respond_to?(generate_method)
+ send(generate_method, name)
+ else
+ generated_attribute_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__)
+ end
+ end
+ end
+ end
+ end
+
+ def undefine_attribute_methods
+ generated_attribute_methods.module_eval do
+ instance_methods.each { |m| undef_method(m) }
+ end
+ @attribute_methods_generated = nil
+ end
+
+ def generated_attribute_methods #:nodoc:
+ @generated_attribute_methods ||= begin
+ @attribute_methods_generated = true
+ mod = Module.new
+ include mod
+ mod
+ end
+ end
+
+ def attribute_methods_generated?
+ @attribute_methods_generated ? true : false
+ end
+
+ protected
+ def instance_method_already_implemented?(method_name)
+ method_defined?(method_name)
+ end
+
+ private
+ class AttributeMethodMatcher
+ attr_reader :prefix, :suffix
+
+ AttributeMethodMatch = Struct.new(:prefix, :base, :suffix)
+
+ def initialize(options = {})
+ options.symbolize_keys!
+ @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
+ @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
+ end
+
+ def match(method_name)
+ if matchdata = @regex.match(method_name)
+ AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3])
+ else
+ nil
+ end
+ end
+ end
+
+ def attribute_method_matchers #:nodoc:
+ @@attribute_method_matchers ||= []
+ end
+ end
+
+ # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
+ # were first-class methods. So a Person class with a name attribute can use Person#name and
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
+ # the completed attribute is not +nil+ or 0.
+ #
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
+ # table with a +master_id+ foreign key can instantiate master through Client#master.
+ def method_missing(method_id, *args, &block)
+ method_name = method_id.to_s
+ if match = match_attribute_method?(method_name)
+ guard_private_attribute_method!(method_name, args)
+ return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block)
+ end
+ super
+ end
+
+ # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
+ # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
+ # which will all return +true+.
+ alias :respond_to_without_attributes? :respond_to?
+ def respond_to?(method, include_private_methods = false)
+ if super
+ return true
+ elsif !include_private_methods && super(method, true)
+ # If we're here then we haven't found among non-private methods
+ # but found among all methods. Which means that given method is private.
+ return false
+ elsif match_attribute_method?(method.to_s)
+ return true
+ end
+ super
+ end
+
+ protected
+ def attribute_method?(attr_name)
+ attributes.include?(attr_name)
+ end
+
+ private
+ # Returns a struct representing the matching attribute method.
+ # The struct's attributes are prefix, base and suffix.
+ def match_attribute_method?(method_name)
+ self.class.send(:attribute_method_matchers).each do |method|
+ if (match = method.match(method_name)) && attribute_method?(match.base)
+ return match
+ end
+ end
+ nil
+ end
+
+ # prevent method_missing from calling private methods with #send
+ def guard_private_attribute_method!(method_name, args)
+ if self.class.private_method_defined?(method_name)
+ raise NoMethodError.new("Attempt to call private method", method_name, args)
+ end
+ end
+
+ def missing_attribute(attr_name, stack)
+ raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 1f2d20f6e2..b31ab0b837 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -68,7 +68,7 @@ module ActiveModel
# Will add an error message to each of the attributes in +attributes+ that is empty.
def add_on_empty(attributes, custom_message = nil)
[attributes].flatten.each do |attribute|
- value = @base.send(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
end
@@ -77,7 +77,7 @@ module ActiveModel
# 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)
[attributes].flatten.each do |attribute|
- value = @base.send(attribute)
+ value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, :default => custom_message) if value.blank?
end
end
@@ -146,7 +146,7 @@ module ActiveModel
defaults = defaults.compact.flatten << :"messages.#{message}"
key = defaults.shift
- value = @base.send(attribute)
+ value = @base.send(:read_attribute_for_validation, attribute)
options = { :default => defaults,
:model => @base.class.name.humanize,
diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb
index 1172d31ea3..527794b34d 100644
--- a/activemodel/lib/active_model/state_machine.rb
+++ b/activemodel/lib/active_model/state_machine.rb
@@ -5,12 +5,9 @@ module ActiveModel
autoload :State, 'active_model/state_machine/state'
autoload :StateTransition, 'active_model/state_machine/state_transition'
- class InvalidTransition < Exception
- end
+ extend ActiveSupport::Concern
- def self.included(base)
- require 'active_model/state_machine/machine'
- base.extend ClassMethods
+ class InvalidTransition < Exception
end
module ClassMethods
diff --git a/activemodel/lib/active_model/state_machine/event.rb b/activemodel/lib/active_model/state_machine/event.rb
index 3eb656b6d6..30e9601dc2 100644
--- a/activemodel/lib/active_model/state_machine/event.rb
+++ b/activemodel/lib/active_model/state_machine/event.rb
@@ -1,5 +1,3 @@
-require 'active_model/state_machine/state_transition'
-
module ActiveModel
module StateMachine
class Event
@@ -53,12 +51,12 @@ module ActiveModel
self
end
- private
- def transitions(trans_opts)
- Array(trans_opts[:from]).each do |s|
- @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+ private
+ def transitions(trans_opts)
+ Array(trans_opts[:from]).each do |s|
+ @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+ end
end
- end
end
end
end
diff --git a/activemodel/lib/active_model/state_machine/machine.rb b/activemodel/lib/active_model/state_machine/machine.rb
index a5ede021b1..777531213e 100644
--- a/activemodel/lib/active_model/state_machine/machine.rb
+++ b/activemodel/lib/active_model/state_machine/machine.rb
@@ -1,6 +1,3 @@
-require 'active_model/state_machine/state'
-require 'active_model/state_machine/event'
-
module ActiveModel
module StateMachine
class Machine
@@ -57,22 +54,22 @@ module ActiveModel
"@#{@name}_current_state"
end
- private
- def state(name, options = {})
- @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
- end
+ private
+ def state(name, options = {})
+ @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
+ end
- def event(name, options = {}, &block)
- (@events[name] ||= Event.new(self, name)).update(options, &block)
- end
+ def event(name, options = {}, &block)
+ (@events[name] ||= Event.new(self, name)).update(options, &block)
+ end
- def event_fired_callback
- @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
- end
+ def event_fired_callback
+ @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
+ end
- def event_failed_callback
- @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
- end
+ def event_failed_callback
+ @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
+ end
end
end
end
diff --git a/activemodel/lib/active_model/state_machine/state_transition.rb b/activemodel/lib/active_model/state_machine/state_transition.rb
index f9df998ea4..b0c5504de7 100644
--- a/activemodel/lib/active_model/state_machine/state_transition.rb
+++ b/activemodel/lib/active_model/state_machine/state_transition.rb
@@ -18,7 +18,7 @@ module ActiveModel
true
end
end
-
+
def execute(obj, *args)
case @on_transition
when Symbol, String
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 54a869396d..7d49e60790 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -66,7 +66,7 @@ module ActiveModel
# Declare the validation.
send(validation_method(options[:on]), options) do |record|
attrs.each do |attr|
- value = record.send(attr)
+ value = record.send(:read_attribute_for_validation, attr)
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
yield record, attr, value
end
@@ -95,6 +95,28 @@ module ActiveModel
def invalid?
!valid?
end
+
+ protected
+ # Hook method defining how an attribute value should be retieved. 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 e.g.
+ # class MyClass
+ # include ActiveModel::Validations
+ #
+ # def initialize(data = {})
+ # @data = data
+ # end
+ #
+ # private
+ #
+ # def read_attribute_for_validation(key)
+ # @data[key]
+ # end
+ # end
+ #
+ def read_attribute_for_validation(key)
+ send(key)
+ end
end
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index db0439d447..e91841bd1c 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -66,10 +66,14 @@ module ActiveModel
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
- if value.nil? or value.size < option_value.begin
- record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
- elsif value.size > option_value.end
- record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
+
+ min, max = option_value.begin, option_value.end
+ max = max - 1 if option_value.exclude_end?
+
+ if value.nil? || value.size < min
+ record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => min)
+ elsif value.size > max
+ record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => max)
end
end
when :is, :minimum, :maximum
@@ -80,9 +84,14 @@ module ActiveModel
validates_each(attrs, options) do |record, attr, value|
value = options[:tokenizer].call(value) if value.kind_of?(String)
- unless !value.nil? and value.size.method(validity_checks[option])[option_value]
- record.errors.add(attr, key, :default => custom_message, :count => option_value)
+
+ valid_value = if option == :maximum
+ value.nil? || value.size.send(validity_checks[option], option_value)
+ else
+ value && value.size.send(validity_checks[option], option_value)
end
+
+ record.errors.add(attr, key, :default => custom_message, :count => option_value) unless valid_value
end
end
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 4a2f72feab..2c97b762f1 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -52,6 +52,12 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"]
end
+ def test_validates_length_of_using_maximum_should_allow_nil
+ Topic.validates_length_of :title, :maximum => 10
+ t = Topic.create
+ assert t.valid?
+ end
+
def test_optionally_validates_length_of_using_minimum
Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
@@ -75,9 +81,6 @@ class LengthValidationTest < ActiveModel::TestCase
t.title = ""
assert t.valid?
-
- t.title = nil
- assert !t.valid?
end
def test_optionally_validates_length_of_using_maximum
@@ -109,6 +112,20 @@ class LengthValidationTest < ActiveModel::TestCase
assert t.valid?
end
+ def test_validates_length_of_using_within_with_exclusive_range
+ Topic.validates_length_of(:title, :within => 4...10)
+
+ t = Topic.new("title" => "9 chars!!")
+ assert t.valid?
+
+ t.title = "Now I'm 10"
+ assert !t.valid?
+ assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title]
+
+ t.title = "Four"
+ assert t.valid?
+ end
+
def test_optionally_validates_length_of_using_within
Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb
index aa5bdf1e62..bb6fb91774 100644
--- a/activemodel/test/cases/validations/presence_validation_test.rb
+++ b/activemodel/test/cases/validations/presence_validation_test.rb
@@ -54,4 +54,18 @@ class PresenceValidationTest < ActiveModel::TestCase
assert p.valid?
end
end
+
+ def test_validates_presence_of_for_ruby_class_with_custom_reader
+ repair_validations(Person) do
+ CustomReader.validates_presence_of :karma
+
+ p = CustomReader.new
+ assert p.invalid?
+
+ assert_equal ["can't be blank"], p.errors[:karma]
+
+ p[:karma] = "Cold"
+ assert p.valid?
+ end
+ end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 8c89494247..0b340e68bf 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -5,6 +5,7 @@ require 'cases/tests_database'
require 'models/topic'
require 'models/reply'
require 'models/developer'
+require 'models/custom_reader'
class ValidationsTest < ActiveModel::TestCase
include ActiveModel::TestsDatabase
@@ -97,6 +98,19 @@ class ValidationsTest < ActiveModel::TestCase
assert_equal %w(gotcha gotcha), t.errors[:title]
assert_equal %w(gotcha gotcha), t.errors[:content]
end
+
+ def test_validates_each_custom_reader
+ hits = 0
+ CustomReader.validates_each(:title, :content, [:title, :content]) do |record, attr|
+ record.errors.add attr, 'gotcha'
+ hits += 1
+ end
+ t = CustomReader.new("title" => "valid", "content" => "whatever")
+ assert !t.valid?
+ assert_equal 4, hits
+ assert_equal %w(gotcha gotcha), t.errors[:title]
+ assert_equal %w(gotcha gotcha), t.errors[:content]
+ end
def test_validate_block
Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
diff --git a/activemodel/test/models/custom_reader.rb b/activemodel/test/models/custom_reader.rb
new file mode 100644
index 0000000000..7ac70e6167
--- /dev/null
+++ b/activemodel/test/models/custom_reader.rb
@@ -0,0 +1,17 @@
+class CustomReader
+ include ActiveModel::Validations
+
+ def initialize(data = {})
+ @data = data
+ end
+
+ def []=(key, value)
+ @data[key] = value
+ end
+
+ private
+
+ def read_attribute_for_validation(key)
+ @data[key]
+ end
+end \ No newline at end of file
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 659de99873..9adc6b887f 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,9 @@
*Edge*
+* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing]
+
+* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper]
+
* Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha]
# employees.company_name references companies.name
Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name'
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 0d33b9d516..09dbc5ad6d 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -64,8 +64,8 @@ end
namespace :mysql do
desc 'Build the MySQL test databases'
task :build_databases do
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
- %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
+ %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
+ %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER})
end
desc 'Drop the MySQL test databases'
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 63eb5c3eeb..68b0251982 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -52,7 +52,6 @@ module ActiveRecord
autoload :Batches, 'active_record/batches'
autoload :Calculations, 'active_record/calculations'
autoload :Callbacks, 'active_record/callbacks'
- autoload :Dirty, 'active_record/dirty'
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
autoload :Migration, 'active_record/migration'
@@ -66,11 +65,22 @@ module ActiveRecord
autoload :SchemaDumper, 'active_record/schema_dumper'
autoload :Serialization, 'active_record/serialization'
autoload :SessionStore, 'active_record/session_store'
+ autoload :StateMachine, 'active_record/state_machine'
autoload :TestCase, 'active_record/test_case'
autoload :Timestamp, 'active_record/timestamp'
autoload :Transactions, 'active_record/transactions'
autoload :Validations, 'active_record/validations'
+ module AttributeMethods
+ autoload :BeforeTypeCast, 'active_record/attribute_methods/before_type_cast'
+ autoload :Dirty, 'active_record/attribute_methods/dirty'
+ autoload :PrimaryKey, 'active_record/attribute_methods/primary_key'
+ autoload :Query, 'active_record/attribute_methods/query'
+ autoload :Read, 'active_record/attribute_methods/read'
+ autoload :TimeZoneConversion, 'active_record/attribute_methods/time_zone_conversion'
+ autoload :Write, 'active_record/attribute_methods/write'
+ end
+
module Locking
autoload :Optimistic, 'active_record/locking/optimistic'
autoload :Pessimistic, 'active_record/locking/pessimistic'
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 66aa9332c8..7f299b2aa5 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1367,7 +1367,7 @@ module ActiveRecord
define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
ids = (new_value || []).reject { |nid| nid.blank? }
- send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
+ send("#{reflection.name}=", reflection.klass.find(ids))
end
end
end
@@ -1912,7 +1912,7 @@ module ActiveRecord
descendant
end.flatten.compact
- remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
+ remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index e21ef90391..f4507c979c 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -8,6 +8,8 @@ module ActiveRecord
alias_method :new, :build
def create!(attrs = nil)
+ ensure_owner_is_not_new
+
transaction do
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!)
object
@@ -15,6 +17,8 @@ module ActiveRecord
end
def create(attrs = nil)
+ ensure_owner_is_not_new
+
transaction do
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association)
object
@@ -50,7 +54,7 @@ module ActiveRecord
options[:select] = construct_select(options[:select])
options[:from] ||= construct_from
options[:joins] = construct_joins(options[:joins])
- options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include]
end
def insert_record(record, force = true, validate = true)
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index ecd2d57a5a..ab7ad34b9e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -3,100 +3,13 @@ require 'active_support/core_ext/enumerable'
module ActiveRecord
module AttributeMethods #:nodoc:
extend ActiveSupport::Concern
+ include ActiveModel::AttributeMethods
- DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
- ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
-
- included do
- attribute_method_suffix(*DEFAULT_SUFFIXES)
-
- cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
-
- cattr_accessor :time_zone_aware_attributes, :instance_writer => false
- self.time_zone_aware_attributes = false
-
- class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
- self.skip_time_zone_conversion_for_attributes = []
- end
-
- # Declare and check for suffixed attribute methods.
module ClassMethods
- # Declares a method available for all attributes with the given suffix.
- # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method
- #
- # #{attr}#{suffix}(*args, &block)
- #
- # to
- #
- # attribute#{suffix}(#{attr}, *args, &block)
- #
- # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
- # the +attr+ argument.
- #
- # For example:
- #
- # class Person < ActiveRecord::Base
- # attribute_method_suffix '_changed?'
- #
- # private
- # def attribute_changed?(attr)
- # ...
- # end
- # end
- #
- # person = Person.find(1)
- # person.name_changed? # => false
- # person.name = 'Hubert'
- # person.name_changed? # => true
- def attribute_method_suffix(*suffixes)
- attribute_method_suffixes.concat suffixes
- rebuild_attribute_method_regexp
- end
-
- # Returns MatchData if method_name is an attribute method.
- def match_attribute_method?(method_name)
- rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
- @@attribute_method_regexp.match(method_name)
- end
-
-
- # Contains the names of the generated attribute methods.
- def generated_methods #:nodoc:
- @generated_methods ||= Set.new
- end
-
- def generated_methods?
- !generated_methods.empty?
- end
-
# Generates all the attribute related methods for columns in the database
# accessors, mutators and query methods.
def define_attribute_methods
- return if generated_methods?
- columns_hash.each do |name, column|
- unless instance_method_already_implemented?(name)
- if self.serialized_attributes[name]
- define_read_method_for_serialized_attribute(name)
- elsif create_time_zone_conversion_attribute?(name, column)
- define_read_method_for_time_zone_conversion(name)
- else
- define_read_method(name.to_sym, name, column)
- end
- end
-
- unless instance_method_already_implemented?("#{name}=")
- if create_time_zone_conversion_attribute?(name, column)
- define_write_method_for_time_zone_conversion(name)
- else
- define_write_method(name.to_sym)
- end
- end
-
- unless instance_method_already_implemented?("#{name}?")
- define_question_method(name)
- end
- end
+ super(columns_hash.keys)
end
# Checks whether the method is defined in the model or any of its subclasses
@@ -104,296 +17,35 @@ module ActiveRecord
# method is defined by Active Record though.
def instance_method_already_implemented?(method_name)
method_name = method_name.to_s
- return true if method_name =~ /^id(=$|\?$|$)/
@_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set
@@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set
raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
@_defined_class_methods.include?(method_name)
end
-
- alias :define_read_methods :define_attribute_methods
-
- # +cache_attributes+ allows you to declare which converted attribute values should
- # be cached. Usually caching only pays off for attributes with expensive conversion
- # methods, like time related columns (e.g. +created_at+, +updated_at+).
- def cache_attributes(*attribute_names)
- attribute_names.each {|attr| cached_attributes << attr.to_s}
- end
-
- # Returns the attributes which are cached. By default time related columns
- # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
- def cached_attributes
- @cached_attributes ||=
- columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
- end
-
- # Returns +true+ if the provided attribute is being cached.
- def cache_attribute?(attr_name)
- cached_attributes.include?(attr_name)
- end
-
- private
-
- # Suffixes a, ?, c become regexp /(a|\?|c)$/
- def rebuild_attribute_method_regexp
- suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
- @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
- end
-
- # Default to =, ?, _before_type_cast
- def attribute_method_suffixes
- @@attribute_method_suffixes ||= []
- end
-
- def create_time_zone_conversion_attribute?(name, column)
- time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
- end
-
- # Define an attribute reader method. Cope with nil column.
- def define_read_method(symbol, attr_name, column)
- cast_code = column.type_cast_code('v') if column
- access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
-
- unless attr_name.to_s == self.primary_key.to_s
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
- end
-
- if cache_attribute?(attr_name)
- access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
- end
- evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
- end
-
- # Define read method for serialized attribute.
- def define_read_method_for_serialized_attribute(attr_name)
- evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
- end
-
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
- def define_read_method_for_time_zone_conversion(attr_name)
- method_body = <<-EOV
- def #{attr_name}(reload = false)
- cached = @attributes_cache['#{attr_name}']
- return cached if cached && !reload
- time = read_attribute('#{attr_name}')
- @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
- end
- EOV
- evaluate_attribute_method attr_name, method_body
- end
-
- # Defines a predicate method <tt>attr_name?</tt>.
- def define_question_method(attr_name)
- evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
- end
-
- def define_write_method(attr_name)
- evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
- end
-
- # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
- # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
- def define_write_method_for_time_zone_conversion(attr_name)
- method_body = <<-EOV
- def #{attr_name}=(time)
- unless time.acts_like?(:time)
- time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
- end
- time = time.in_time_zone rescue nil if time
- write_attribute(:#{attr_name}, time)
- end
- EOV
- evaluate_attribute_method attr_name, method_body, "#{attr_name}="
- end
-
- # Evaluate the definition for an attribute related method
- def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
-
- unless method_name.to_s == primary_key.to_s
- generated_methods << method_name
- end
-
- begin
- class_eval(method_definition, __FILE__, __LINE__)
- rescue SyntaxError => err
- generated_methods.delete(attr_name)
- if logger
- logger.warn "Exception occurred during reader method compilation."
- logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
- logger.warn err.message
- end
- end
- end
- end # ClassMethods
-
+ end
- # Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
- # were first-class methods. So a Person class with a name attribute can use Person#name and
- # Person#name= and never directly use the attributes hash -- except for multiple assigns with
- # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
- # the completed attribute is not +nil+ or 0.
- #
- # It's also possible to instantiate related objects, so a Client class belonging to the clients
- # table with a +master_id+ foreign key can instantiate master through Client#master.
def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
-
# If we haven't generated any methods yet, generate them, then
# see if we've created the method we're looking for.
- if !self.class.generated_methods?
+ if !self.class.attribute_methods_generated?
self.class.define_attribute_methods
+ method_name = method_id.to_s
guard_private_attribute_method!(method_name, args)
- if self.class.generated_methods.include?(method_name)
+ if self.class.generated_attribute_methods.instance_methods.include?(method_name)
return self.send(method_id, *args, &block)
end
end
-
- guard_private_attribute_method!(method_name, args)
- if self.class.primary_key.to_s == method_name
- id
- elsif md = self.class.match_attribute_method?(method_name)
- attribute_name, method_type = md.pre_match, md.to_s
- if @attributes.include?(attribute_name)
- __send__("attribute#{method_type}", attribute_name, *args, &block)
- else
- super
- end
- elsif @attributes.include?(method_name)
- read_attribute(method_name)
- else
- super
- end
- end
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- def read_attribute(attr_name)
- attr_name = attr_name.to_s
- if !(value = @attributes[attr_name]).nil?
- if column = column_for_attribute(attr_name)
- if unserializable_attribute?(attr_name, column)
- unserialize_attribute(attr_name)
- else
- column.type_cast(value)
- end
- else
- value
- end
- else
- nil
- end
- end
-
- def read_attribute_before_type_cast(attr_name)
- @attributes[attr_name]
- end
-
- # Returns true if the attribute is of a text column and marked for serialization.
- def unserializable_attribute?(attr_name, column)
- column.text? && self.class.serialized_attributes[attr_name]
- end
-
- # Returns the unserialized object of the attribute.
- def unserialize_attribute(attr_name)
- unserialized_object = object_from_yaml(@attributes[attr_name])
-
- if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
- @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
- else
- raise SerializationTypeMismatch,
- "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
- end
- end
-
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
- # columns are turned into +nil+.
- def write_attribute(attr_name, value)
- attr_name = attr_name.to_s
- @attributes_cache.delete(attr_name)
- if (column = column_for_attribute(attr_name)) && column.number?
- @attributes[attr_name] = convert_number_column_value(value)
- else
- @attributes[attr_name] = value
- end
+ super
end
-
- def query_attribute(attr_name)
- unless value = read_attribute(attr_name)
- false
- else
- column = self.class.columns_hash[attr_name]
- if column.nil?
- if Numeric === value || value !~ /[^0-9]/
- !value.to_i.zero?
- else
- return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
- !value.blank?
- end
- elsif column.number?
- !value.zero?
- else
- !value.blank?
- end
- end
- end
-
- # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
- # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+.
- alias :respond_to_without_attributes? :respond_to?
- def respond_to?(method, include_private_methods = false)
- method_name = method.to_s
- if super
- return true
- elsif !include_private_methods && super(method, true)
- # If we're here than we haven't found among non-private methods
- # but found among all methods. Which means that given method is private.
- return false
- elsif !self.class.generated_methods?
- self.class.define_attribute_methods
- if self.class.generated_methods.include?(method_name)
- return true
- end
- end
-
- if @attributes.nil?
- return super
- elsif @attributes.include?(method_name)
- return true
- elsif md = self.class.match_attribute_method?(method_name)
- return true if @attributes.include?(md.pre_match)
- end
+ def respond_to?(*args)
+ self.class.define_attribute_methods
super
end
- private
- # prevent method_missing from calling private methods with #send
- def guard_private_attribute_method!(method_name, args)
- if self.class.private_method_defined?(method_name)
- raise NoMethodError.new("Attempt to call private method", method_name, args)
- end
- end
-
- def missing_attribute(attr_name, stack)
- raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
- end
-
- # Handle *? for method_missing.
- def attribute?(attribute_name)
- query_attribute(attribute_name)
- end
-
- # Handle *= for method_missing.
- def attribute=(attribute_name, value)
- write_attribute(attribute_name, value)
- end
-
- # Handle *_before_type_cast for method_missing.
- def attribute_before_type_cast(attribute_name)
- read_attribute_before_type_cast(attribute_name)
+ protected
+ def attribute_method?(attr_name)
+ attr_name == 'id' || attributes.include?(attr_name)
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
new file mode 100644
index 0000000000..a4e144f233
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb
@@ -0,0 +1,33 @@
+module ActiveRecord
+ module AttributeMethods
+ module BeforeTypeCast
+ extend ActiveSupport::Concern
+
+ included do
+ attribute_method_suffix "_before_type_cast"
+ end
+
+ def read_attribute_before_type_cast(attr_name)
+ @attributes[attr_name]
+ end
+
+ # Returns a hash of attributes before typecasting and deserialization.
+ def attributes_before_type_cast
+ self.attribute_names.inject({}) do |attrs, name|
+ attrs[name] = read_attribute_before_type_cast(name)
+ attrs
+ end
+ end
+
+ private
+ # Handle *_before_type_cast for method_missing.
+ def attribute_before_type_cast(attribute_name)
+ if attribute_name == 'id'
+ read_attribute_before_type_cast(self.class.primary_key)
+ else
+ read_attribute_before_type_cast(attribute_name)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
new file mode 100644
index 0000000000..911c908c8b
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -0,0 +1,204 @@
+module ActiveRecord
+ module AttributeMethods
+ # Track unsaved attribute changes.
+ #
+ # A newly instantiated object is unchanged:
+ # person = Person.find_by_name('Uncle Bob')
+ # person.changed? # => false
+ #
+ # Change the name:
+ # person.name = 'Bob'
+ # person.changed? # => true
+ # person.name_changed? # => true
+ # person.name_was # => 'Uncle Bob'
+ # person.name_change # => ['Uncle Bob', 'Bob']
+ # person.name = 'Bill'
+ # person.name_change # => ['Uncle Bob', 'Bill']
+ #
+ # Save the changes:
+ # person.save
+ # person.changed? # => false
+ # person.name_changed? # => false
+ #
+ # Assigning the same value leaves the attribute unchanged:
+ # person.name = 'Bill'
+ # person.name_changed? # => false
+ # person.name_change # => nil
+ #
+ # Which attributes have changed?
+ # person.name = 'Bob'
+ # person.changed # => ['name']
+ # person.changes # => { 'name' => ['Bill', 'Bob'] }
+ #
+ # Resetting an attribute returns it to its original state:
+ # person.reset_name! # => 'Bill'
+ # person.changed? # => false
+ # person.name_changed? # => false
+ # person.name # => 'Bill'
+ #
+ # Before modifying an attribute in-place:
+ # person.name_will_change!
+ # person.name << 'y'
+ # person.name_change # => ['Bill', 'Billy']
+ module Dirty
+ extend ActiveSupport::Concern
+
+ DIRTY_AFFIXES = [
+ { :suffix => '_changed?' },
+ { :suffix => '_change' },
+ { :suffix => '_will_change!' },
+ { :suffix => '_was' },
+ { :prefix => 'reset_', :suffix => '!' }
+ ]
+
+ included do
+ attribute_method_affix *DIRTY_AFFIXES
+
+ alias_method_chain :save, :dirty
+ alias_method_chain :save!, :dirty
+ alias_method_chain :update, :dirty
+ alias_method_chain :reload, :dirty
+
+ superclass_delegating_accessor :partial_updates
+ self.partial_updates = true
+ end
+
+ # Do any attributes have unsaved changes?
+ # person.changed? # => false
+ # person.name = 'bob'
+ # person.changed? # => true
+ def changed?
+ !changed_attributes.empty?
+ end
+
+ # List of attributes with unsaved changes.
+ # person.changed # => []
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ def changed
+ changed_attributes.keys
+ end
+
+ # Map of changed attrs => [original value, new value].
+ # person.changes # => {}
+ # person.name = 'bob'
+ # person.changes # => { 'name' => ['bill', 'bob'] }
+ def changes
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
+ end
+
+ # Attempts to +save+ the record and clears changed attributes if successful.
+ def save_with_dirty(*args) #:nodoc:
+ if status = save_without_dirty(*args)
+ changed_attributes.clear
+ end
+ status
+ end
+
+ # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
+ def save_with_dirty!(*args) #:nodoc:
+ status = save_without_dirty!(*args)
+ changed_attributes.clear
+ status
+ end
+
+ # <tt>reload</tt> the record and clears changed attributes.
+ def reload_with_dirty(*args) #:nodoc:
+ record = reload_without_dirty(*args)
+ changed_attributes.clear
+ record
+ end
+
+ private
+ # Map of change <tt>attr => original value</tt>.
+ def changed_attributes
+ @changed_attributes ||= {}
+ end
+
+ # Handle <tt>*_changed?</tt> for +method_missing+.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
+
+ # Handle <tt>*_change</tt> for +method_missing+.
+ def attribute_change(attr)
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ end
+
+ # Handle <tt>*_was</tt> for +method_missing+.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ end
+
+ # Handle <tt>reset_*!</tt> for +method_missing+.
+ def reset_attribute!(attr)
+ self[attr] = changed_attributes[attr] if attribute_changed?(attr)
+ end
+
+ # Handle <tt>*_will_change!</tt> for +method_missing+.
+ def attribute_will_change!(attr)
+ changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
+ end
+
+ # Wrap write_attribute to remember original attribute value.
+ def write_attribute(attr, value)
+ attr = attr.to_s
+
+ # The attribute already has an unsaved change.
+ if changed_attributes.include?(attr)
+ old = changed_attributes[attr]
+ changed_attributes.delete(attr) unless field_changed?(attr, old, value)
+ else
+ old = clone_attribute_value(:read_attribute, attr)
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
+ end
+
+ # Carry on.
+ super(attr, value)
+ end
+
+ def update_with_dirty
+ if partial_updates?
+ # Serialized attributes should always be written in case they've been
+ # changed in place.
+ update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
+ else
+ update_without_dirty
+ end
+ end
+
+ def field_changed?(attr, old, value)
+ if column = column_for_attribute(attr)
+ if column.number? && column.null && (old.nil? || old == 0) && value.blank?
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
+ value = nil
+ else
+ value = column.type_cast(value)
+ end
+ end
+
+ old != value
+ end
+
+ module ClassMethods
+ def self.extended(base)
+ class << base
+ alias_method_chain :alias_attribute, :dirty
+ end
+ end
+
+ def alias_attribute_with_dirty(new_name, old_name)
+ alias_attribute_without_dirty(new_name, old_name)
+ DIRTY_AFFIXES.each do |affixes|
+ module_eval <<-STR, __FILE__, __LINE__+1
+ def #{affixes[:prefix]}#{new_name}#{affixes[:suffix]}; self.#{affixes[:prefix]}#{old_name}#{affixes[:suffix]}; end # def reset_subject!; self.reset_title!; end
+ STR
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
new file mode 100644
index 0000000000..365fdeb55a
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -0,0 +1,44 @@
+module ActiveRecord
+ module AttributeMethods
+ module PrimaryKey
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
+ # primary_key_prefix_type setting, though.
+ def primary_key
+ reset_primary_key
+ end
+
+ def reset_primary_key #:nodoc:
+ key = get_primary_key(base_class.name)
+ set_primary_key(key)
+ key
+ end
+
+ def get_primary_key(base_name) #:nodoc:
+ key = 'id'
+ case primary_key_prefix_type
+ when :table_name
+ key = base_name.to_s.foreign_key(false)
+ when :table_name_with_underscore
+ key = base_name.to_s.foreign_key
+ end
+ key
+ end
+
+ # Sets the name of the primary key column to use to the given value,
+ # or (if the value is nil or false) to the value returned by the given
+ # block.
+ #
+ # class Project < ActiveRecord::Base
+ # set_primary_key "sysid"
+ # end
+ def set_primary_key(value = nil, &block)
+ define_attr_method :primary_key, value, &block
+ end
+ alias :primary_key= :set_primary_key
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb
new file mode 100644
index 0000000000..a949d80120
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/query.rb
@@ -0,0 +1,37 @@
+module ActiveRecord
+ module AttributeMethods
+ module Query
+ extend ActiveSupport::Concern
+
+ included do
+ attribute_method_suffix "?"
+ end
+
+ def query_attribute(attr_name)
+ unless value = read_attribute(attr_name)
+ false
+ else
+ column = self.class.columns_hash[attr_name]
+ if column.nil?
+ if Numeric === value || value !~ /[^0-9]/
+ !value.to_i.zero?
+ else
+ return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value)
+ !value.blank?
+ end
+ elsif column.number?
+ !value.zero?
+ else
+ !value.blank?
+ end
+ end
+ end
+
+ private
+ # Handle *? for method_missing.
+ def attribute?(attribute_name)
+ query_attribute(attribute_name)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
new file mode 100644
index 0000000000..3da3d9d8cc
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -0,0 +1,116 @@
+module ActiveRecord
+ module AttributeMethods
+ module Read
+ extend ActiveSupport::Concern
+
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+
+ included do
+ attribute_method_suffix ""
+
+ cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
+ self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+
+ # Undefine id so it can be used as an attribute name
+ undef_method(:id) if method_defined?(:id)
+ end
+
+ module ClassMethods
+ # +cache_attributes+ allows you to declare which converted attribute values should
+ # be cached. Usually caching only pays off for attributes with expensive conversion
+ # methods, like time related columns (e.g. +created_at+, +updated_at+).
+ def cache_attributes(*attribute_names)
+ attribute_names.each {|attr| cached_attributes << attr.to_s}
+ end
+
+ # Returns the attributes which are cached. By default time related columns
+ # with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
+ def cached_attributes
+ @cached_attributes ||=
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set
+ end
+
+ # Returns +true+ if the provided attribute is being cached.
+ def cache_attribute?(attr_name)
+ cached_attributes.include?(attr_name)
+ end
+
+ protected
+ def define_method_attribute(attr_name)
+ if self.serialized_attributes[attr_name]
+ define_read_method_for_serialized_attribute(attr_name)
+ else
+ define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
+ end
+
+ if attr_name == primary_key && attr_name != "id"
+ define_read_method(:id, attr_name, columns_hash[attr_name])
+ end
+ end
+
+ private
+ # Define read method for serialized attribute.
+ def define_read_method_for_serialized_attribute(attr_name)
+ generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__)
+ end
+
+ # Define an attribute reader method. Cope with nil column.
+ def define_read_method(symbol, attr_name, column)
+ cast_code = column.type_cast_code('v') if column
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
+
+ unless attr_name.to_s == self.primary_key.to_s
+ access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
+ end
+
+ if cache_attribute?(attr_name)
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
+ end
+ generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__)
+ end
+ end
+
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ def read_attribute(attr_name)
+ attr_name = attr_name.to_s
+ attr_name = self.class.primary_key if attr_name == 'id'
+ if !(value = @attributes[attr_name]).nil?
+ if column = column_for_attribute(attr_name)
+ if unserializable_attribute?(attr_name, column)
+ unserialize_attribute(attr_name)
+ else
+ column.type_cast(value)
+ end
+ else
+ value
+ end
+ else
+ nil
+ end
+ end
+
+ # Returns true if the attribute is of a text column and marked for serialization.
+ def unserializable_attribute?(attr_name, column)
+ column.text? && self.class.serialized_attributes[attr_name]
+ end
+
+ # Returns the unserialized object of the attribute.
+ def unserialize_attribute(attr_name)
+ unserialized_object = object_from_yaml(@attributes[attr_name])
+
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
+ else
+ raise SerializationTypeMismatch,
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
+ end
+ end
+
+ private
+ def attribute(attribute_name)
+ read_attribute(attribute_name)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
new file mode 100644
index 0000000000..a8e3e28a7a
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -0,0 +1,60 @@
+module ActiveRecord
+ module AttributeMethods
+ module TimeZoneConversion
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor :time_zone_aware_attributes, :instance_writer => false
+ self.time_zone_aware_attributes = false
+
+ class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ self.skip_time_zone_conversion_for_attributes = []
+ end
+
+ module ClassMethods
+ protected
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
+ def define_method_attribute(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body = <<-EOV
+ def #{attr_name}(reload = false)
+ cached = @attributes_cache['#{attr_name}']
+ return cached if cached && !reload
+ time = read_attribute('#{attr_name}')
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ else
+ super
+ end
+ end
+
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
+ def define_method_attribute=(attr_name)
+ if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name])
+ method_body = <<-EOV
+ def #{attr_name}=(time)
+ unless time.acts_like?(:time)
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
+ end
+ time = time.in_time_zone rescue nil if time
+ write_attribute(:#{attr_name}, time)
+ end
+ EOV
+ generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
+ else
+ super
+ end
+ end
+
+ private
+ def create_time_zone_conversion_attribute?(name, column)
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
new file mode 100644
index 0000000000..e31acac050
--- /dev/null
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -0,0 +1,37 @@
+module ActiveRecord
+ module AttributeMethods
+ module Write
+ extend ActiveSupport::Concern
+
+ included do
+ attribute_method_suffix "="
+ end
+
+ module ClassMethods
+ protected
+ def define_method_attribute=(attr_name)
+ generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
+ end
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
+ # columns are turned into +nil+.
+ def write_attribute(attr_name, value)
+ attr_name = attr_name.to_s
+ attr_name = self.class.primary_key if attr_name == 'id'
+ @attributes_cache.delete(attr_name)
+ if (column = column_for_attribute(attr_name)) && column.number?
+ @attributes[attr_name] = convert_number_column_value(value)
+ else
+ @attributes[attr_name] = value
+ end
+ end
+
+ private
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ write_attribute(attribute_name, value)
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 5a36ff5ba2..531a698f77 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -148,11 +148,6 @@ module ActiveRecord #:nodoc:
class DangerousAttributeError < ActiveRecordError
end
- # Raised when you've tried to access a column which wasn't loaded by your finder.
- # Typically this is because <tt>:select</tt> has been specified.
- class MissingAttributeError < NoMethodError
- end
-
# Raised when unknown attributes are supplied via mass assignment.
class UnknownAttributeError < NoMethodError
end
@@ -1225,29 +1220,6 @@ module ActiveRecord #:nodoc:
name
end
- # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
- # primary_key_prefix_type setting, though.
- def primary_key
- reset_primary_key
- end
-
- def reset_primary_key #:nodoc:
- key = get_primary_key(base_class.name)
- set_primary_key(key)
- key
- end
-
- def get_primary_key(base_name) #:nodoc:
- key = 'id'
- case primary_key_prefix_type
- when :table_name
- key = base_name.to_s.foreign_key(false)
- when :table_name_with_underscore
- key = base_name.to_s.foreign_key
- end
- key
- end
-
# Defines the column name for use with single table inheritance
# -- can be set in subclasses like so: self.inheritance_column = "type_id"
def inheritance_column
@@ -1277,18 +1249,6 @@ module ActiveRecord #:nodoc:
end
alias :table_name= :set_table_name
- # Sets the name of the primary key column to use to the given value,
- # or (if the value is nil or false) to the value returned by the given
- # block.
- #
- # class Project < ActiveRecord::Base
- # set_primary_key "sysid"
- # end
- def set_primary_key(value = nil, &block)
- define_attr_method :primary_key, value, &block
- end
- alias :primary_key= :set_primary_key
-
# Sets the name of the inheritance column to use to the given value,
# or (if the value # is nil or false) to the value returned by the
# given block.
@@ -1401,8 +1361,8 @@ module ActiveRecord #:nodoc:
# end
# end
def reset_column_information
- generated_methods.each { |name| undef_method(name) }
- @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
+ undefine_attribute_methods
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
end
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
@@ -2077,36 +2037,6 @@ module ActiveRecord #:nodoc:
end
end
- # Defines an "attribute" method (like +inheritance_column+ or
- # +table_name+). A new (class) method will be created with the
- # given name. If a value is specified, the new method will
- # return that value (as a string). Otherwise, the given block
- # will be used to compute the value of the method.
- #
- # The original method will be aliased, with the new name being
- # prefixed with "original_". This allows the new method to
- # access the original value.
- #
- # Example:
- #
- # class A < ActiveRecord::Base
- # define_attr_method :primary_key, "sysid"
- # define_attr_method( :inheritance_column ) do
- # original_inheritance_column + "_id"
- # end
- # end
- def define_attr_method(name, value=nil, &block)
- sing = metaclass
- sing.send :alias_method, "original_#{name}", name
- if block_given?
- sing.send :define_method, name, &block
- else
- # use eval instead of a block to work around a memory leak in dev
- # mode in fcgi
- sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
- end
- end
-
protected
# Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
# method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
@@ -2508,18 +2438,6 @@ module ActiveRecord #:nodoc:
result
end
- # A model instance's primary key is always available as model.id
- # whether you name it the default 'id' or set it to something else.
- def id
- attr_name = self.class.primary_key
- column = column_for_attribute(attr_name)
-
- self.class.send(:define_read_method, :id, attr_name, column)
- # now that the method exists, call it
- self.send attr_name.to_sym
-
- end
-
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -2565,24 +2483,20 @@ module ActiveRecord #:nodoc:
end
end
- def id_before_type_cast #:nodoc:
- read_attribute_before_type_cast(self.class.primary_key)
- end
-
def quoted_id #:nodoc:
quote_value(id, column_for_attribute(self.class.primary_key))
end
- # Sets the primary ID.
- def id=(value)
- write_attribute(self.class.primary_key, value)
- end
-
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
def new_record?
@new_record || false
end
+ # Returns true if this object has been destroyed, otherwise returns false.
+ def destroyed?
+ @destroyed || false
+ end
+
# :call-seq:
# save(perform_validation = true)
#
@@ -2633,6 +2547,7 @@ module ActiveRecord #:nodoc:
# options, use <tt>#destroy</tt>.
def delete
self.class.delete(id) unless new_record?
+ @destroyed = true
freeze
end
@@ -2647,6 +2562,7 @@ module ActiveRecord #:nodoc:
)
end
+ @destroyed = true
freeze
end
@@ -2822,14 +2738,6 @@ module ActiveRecord #:nodoc:
end
end
- # Returns a hash of attributes before typecasting and deserialization.
- def attributes_before_type_cast
- self.attribute_names.inject({}) do |attrs, name|
- attrs[name] = read_attribute_before_type_cast(name)
- attrs
- end
- end
-
# Returns an <tt>#inspect</tt>-like string for the value of the
# attribute +attr_name+. String attributes are elided after 50
# characters, and Date and Time attributes are returned in the
@@ -3212,7 +3120,10 @@ module ActiveRecord #:nodoc:
include Validations
include Locking::Optimistic, Locking::Pessimistic
include AttributeMethods
- include Dirty
+ include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query
+ include AttributeMethods::PrimaryKey
+ include AttributeMethods::TimeZoneConversion
+ include AttributeMethods::Dirty
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
include ActiveModel::Conversion
diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb
index 727f4c1dc6..4a88c43dff 100644
--- a/activerecord/lib/active_record/calculations.rb
+++ b/activerecord/lib/active_record/calculations.rb
@@ -197,6 +197,8 @@ module ActiveRecord
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
if options[:from]
sql << " FROM #{options[:from]} "
+ elsif scope && scope[:from]
+ sql << " FROM #{scope[:from]} "
else
sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
sql << " FROM #{connection.quote_table_name(table_name)} "
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 720fba29e9..8649f96498 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -60,7 +60,12 @@ module ActiveRecord
end
def quoted_date(value)
- value.to_s(:db)
+ if value.acts_like?(:time)
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
+ else
+ value
+ end.to_s(:db)
end
def quoted_string_prefix
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 24c734cddb..f346e3ebc8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -277,7 +277,6 @@ module ActiveRecord
add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
column_sql
end
- alias to_s :to_sql
private
@@ -508,7 +507,7 @@ module ActiveRecord
# concatenated together. This string can then be prepended and appended to
# to generate the final SQL to create the table.
def to_sql
- @columns * ', '
+ @columns.map(&:to_sql) * ', '
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 83cb9cff15..2b882a1f25 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -587,6 +587,10 @@ module ActiveRecord
@connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
end
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
+
@connection.real_connect(*@connection_options)
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 5e5e30776a..c0f5046bff 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -27,7 +27,6 @@ module ActiveRecord
private
def parse_sqlite_config!(config)
- config[:database] ||= config[:dbfile]
# Require database.
unless config[:database]
raise ArgumentError, "No database file specified. Missing argument: database"
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
deleted file mode 100644
index 178767e0c3..0000000000
--- a/activerecord/lib/active_record/dirty.rb
+++ /dev/null
@@ -1,186 +0,0 @@
-module ActiveRecord
- # Track unsaved attribute changes.
- #
- # A newly instantiated object is unchanged:
- # person = Person.find_by_name('uncle bob')
- # person.changed? # => false
- #
- # Change the name:
- # person.name = 'Bob'
- # person.changed? # => true
- # person.name_changed? # => true
- # person.name_was # => 'uncle bob'
- # person.name_change # => ['uncle bob', 'Bob']
- # person.name = 'Bill'
- # person.name_change # => ['uncle bob', 'Bill']
- #
- # Save the changes:
- # person.save
- # person.changed? # => false
- # person.name_changed? # => false
- #
- # Assigning the same value leaves the attribute unchanged:
- # person.name = 'Bill'
- # person.name_changed? # => false
- # person.name_change # => nil
- #
- # Which attributes have changed?
- # person.name = 'bob'
- # person.changed # => ['name']
- # person.changes # => { 'name' => ['Bill', 'bob'] }
- #
- # Before modifying an attribute in-place:
- # person.name_will_change!
- # person.name << 'by'
- # person.name_change # => ['uncle bob', 'uncle bobby']
- module Dirty
- extend ActiveSupport::Concern
-
- DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was']
-
- included do
- attribute_method_suffix *DIRTY_SUFFIXES
-
- alias_method_chain :write_attribute, :dirty
- alias_method_chain :save, :dirty
- alias_method_chain :save!, :dirty
- alias_method_chain :update, :dirty
- alias_method_chain :reload, :dirty
-
- superclass_delegating_accessor :partial_updates
- self.partial_updates = true
- end
-
- # Do any attributes have unsaved changes?
- # person.changed? # => false
- # person.name = 'bob'
- # person.changed? # => true
- def changed?
- !changed_attributes.empty?
- end
-
- # List of attributes with unsaved changes.
- # person.changed # => []
- # person.name = 'bob'
- # person.changed # => ['name']
- def changed
- changed_attributes.keys
- end
-
- # Map of changed attrs => [original value, new value].
- # person.changes # => {}
- # person.name = 'bob'
- # person.changes # => { 'name' => ['bill', 'bob'] }
- def changes
- changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
- end
-
- # Attempts to +save+ the record and clears changed attributes if successful.
- def save_with_dirty(*args) #:nodoc:
- if status = save_without_dirty(*args)
- changed_attributes.clear
- end
- status
- end
-
- # Attempts to <tt>save!</tt> the record and clears changed attributes if successful.
- def save_with_dirty!(*args) #:nodoc:
- status = save_without_dirty!(*args)
- changed_attributes.clear
- status
- end
-
- # <tt>reload</tt> the record and clears changed attributes.
- def reload_with_dirty(*args) #:nodoc:
- record = reload_without_dirty(*args)
- changed_attributes.clear
- record
- end
-
- private
- # Map of change <tt>attr => original value</tt>.
- def changed_attributes
- @changed_attributes ||= {}
- end
-
- # Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
- end
-
- # Handle <tt>*_change</tt> for +method_missing+.
- def attribute_change(attr)
- [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
- end
-
- # Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
- end
-
- # Handle <tt>*_will_change!</tt> for +method_missing+.
- def attribute_will_change!(attr)
- changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
- end
-
- # Wrap write_attribute to remember original attribute value.
- def write_attribute_with_dirty(attr, value)
- attr = attr.to_s
-
- # The attribute already has an unsaved change.
- if changed_attributes.include?(attr)
- old = changed_attributes[attr]
- changed_attributes.delete(attr) unless field_changed?(attr, old, value)
- else
- old = clone_attribute_value(:read_attribute, attr)
- changed_attributes[attr] = old if field_changed?(attr, old, value)
- end
-
- # Carry on.
- write_attribute_without_dirty(attr, value)
- end
-
- def update_with_dirty
- if partial_updates?
- # Serialized attributes should always be written in case they've been
- # changed in place.
- update_without_dirty(changed | self.class.serialized_attributes.keys)
- else
- update_without_dirty
- end
- end
-
- def field_changed?(attr, old, value)
- if column = column_for_attribute(attr)
- if column.number? && column.null && (old.nil? || old == 0) && value.blank?
- # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
- # Hence we don't record it as a change if the value changes from nil to ''.
- # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
- # be typecast back to 0 (''.to_i => 0)
- value = nil
- else
- value = column.type_cast(value)
- end
- end
-
- old != value
- end
-
- module ClassMethods
- def self.extended(base)
- class << base
- alias_method_chain :alias_attribute, :dirty
- end
- end
-
- def alias_attribute_with_dirty(new_name, old_name)
- alias_attribute_without_dirty(new_name, old_name)
- DIRTY_SUFFIXES.each do |suffix|
- module_eval <<-STR, __FILE__, __LINE__+1
- def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
- STR
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 3963baa6b8..adb3a3f75e 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -388,13 +388,11 @@ module ActiveRecord
end
def rollback(migrations_path, steps=1)
- migrator = self.new(:down, migrations_path)
- start_index = migrator.migrations.index(migrator.current_migration)
-
- return unless start_index
-
- finish = migrator.migrations[start_index + steps]
- down(migrations_path, finish ? finish.version : 0)
+ move(:down, migrations_path, steps)
+ end
+
+ def forward(migrations_path, steps=1)
+ move(:up, migrations_path, steps)
end
def up(migrations_path, target_version = nil)
@@ -430,6 +428,19 @@ module ActiveRecord
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
end
+
+ private
+
+ def move(direction, migrations_path, steps)
+ migrator = self.new(direction, migrations_path)
+ start_index = migrator.migrations.index(migrator.current_migration)
+
+ if start_index
+ finish = migrator.migrations[start_index + steps]
+ version = finish ? finish.version : 0
+ send(direction, migrations_path, version)
+ end
+ end
end
def initialize(direction, migrations_path, target_version = nil)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 5d88012e4f..c8e1b4f53a 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -84,7 +84,6 @@ HEADER
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
- pk ||= 'id'
tbl.print " create_table #{table.inspect}"
if columns.detect { |c| c.name == pk }
diff --git a/activerecord/lib/active_record/state_machine.rb b/activerecord/lib/active_record/state_machine.rb
new file mode 100644
index 0000000000..aebd03344a
--- /dev/null
+++ b/activerecord/lib/active_record/state_machine.rb
@@ -0,0 +1,24 @@
+module ActiveRecord
+ module StateMachine #:nodoc:
+ extend ActiveSupport::Concern
+ include ActiveModel::StateMachine
+
+ included do
+ before_validation :set_initial_state
+ validates_presence_of :state
+ end
+
+ protected
+ def write_state(state_machine, state)
+ update_attributes! :state => state.to_s
+ end
+
+ def read_state(state_machine)
+ self.state.to_sym
+ end
+
+ def set_initial_state
+ self.state ||= self.class.state_machine.initial_state.to_s
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 80530194ff..88136597e3 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -112,23 +112,14 @@ class AdapterTest < ActiveRecord::TestCase
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
sql_inject = "1 select * from schema"
- assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject)
- if current_adapter?(:MysqlAdapter)
- assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
- else
- assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
- end
+ assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
end
def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
sql_inject = "1, 7 procedure help()"
- if current_adapter?(:MysqlAdapter)
- assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
- assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7)
- else
- assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
- assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
- end
+ assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
end
def test_uniqueness_violations_are_translated_to_specific_exception
@@ -141,7 +132,13 @@ class AdapterTest < ActiveRecord::TestCase
def test_foreign_key_violations_are_translated_to_specific_exception
unless @connection.adapter_name == 'SQLite'
assert_raises(ActiveRecord::InvalidForeignKey) do
- @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if @connection.prefetch_primary_key?
+ id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id"))
+ @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)"
+ else
+ @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)"
+ end
end
end
end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index ab6f752243..784c484178 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -293,7 +293,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_new_record_with_foreign_key_but_no_object
c = Client.new("firm_id" => 1)
- assert_equal Firm.find(:first), c.firm_with_basic_id
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
+ assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
end
def test_forgetting_the_load_when_foreign_key_enters_late
@@ -301,7 +302,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil c.firm_with_basic_id
c.firm_id = 1
- assert_equal Firm.find(:first), c.firm_with_basic_id
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
+ assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
end
def test_field_name_same_as_foreign_key
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 4cf49be668..811ebfbe3f 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -813,7 +813,12 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_include_has_many_using_primary_key
expected = Firm.find(1).clients_using_primary_key.sort_by &:name
- firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ # Oracle adapter truncates alias to 30 characters
+ if current_adapter?(:OracleAdapter)
+ firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name'
+ else
+ firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name'
+ end
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 14b96caaae..11a159686e 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -284,12 +284,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
end
def test_creation_respects_hash_condition
- post = categories(:general).post_with_conditions.build(:body => '')
+ # in Oracle '' is saved as null therefore need to save ' ' in not null column
+ post = categories(:general).post_with_conditions.build(:body => ' ')
assert post.save
assert_equal 'Yet Another Testing Title', post.title
- another_post = categories(:general).post_with_conditions.create(:body => '')
+ # in Oracle '' is saved as null therefore need to save ' ' in not null column
+ another_post = categories(:general).post_with_conditions.create(:body => ' ')
assert !another_post.new_record?
assert_equal 'Yet Another Testing Title', another_post.title
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 15919e2289..a3d92c3bdb 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -24,28 +24,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).clients_of_firm.each {|f| }
end
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
def test_counting_with_counter_sql
- assert_equal 2, Firm.find(:first).clients.count
+ assert_equal 2, Firm.find(:first, :order => "id").clients.count
end
def test_counting
- assert_equal 2, Firm.find(:first).plain_clients.count
+ assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count
end
def test_counting_with_empty_hash_conditions
- assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {})
+ assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {})
end
def test_counting_with_single_conditions
- assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"])
+ assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"])
end
def test_counting_with_single_hash
- assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"})
+ assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"})
end
def test_counting_with_column_name_and_hash
- assert_equal 2, Firm.find(:first).plain_clients.count(:name)
+ assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name)
end
def test_counting_with_association_limit
@@ -55,12 +56,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_finding
- assert_equal 2, Firm.find(:first).clients.length
+ assert_equal 2, Firm.find(:first, :order => "id").clients.length
end
def test_find_with_blank_conditions
[[], {}, nil, ""].each do |blank|
- assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size
+ assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size
end
end
@@ -115,52 +116,53 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_triple_equality
- assert !(Array === Firm.find(:first).clients)
- assert Firm.find(:first).clients === Array
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
+ assert !(Array === Firm.find(:first, :order => "id").clients)
+ assert Firm.find(:first, :order => "id").clients === Array
end
def test_finding_default_orders
- assert_equal "Summit", Firm.find(:first).clients.first.name
+ assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name
end
def test_finding_with_different_class_name_and_order
- assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name
+ assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name
end
def test_finding_with_foreign_key
- assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name
+ assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name
end
def test_finding_with_condition
- assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name
+ assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name
end
def test_finding_with_condition_hash
- assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name
+ assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name
end
def test_finding_using_primary_key
- assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name
+ assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name
end
def test_finding_using_sql
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
first_client = firm.clients_using_sql.first
assert_not_nil first_client
assert_equal "Microsoft", first_client.name
assert_equal 1, firm.clients_using_sql.size
- assert_equal 1, Firm.find(:first).clients_using_sql.size
+ assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size
end
def test_counting_using_sql
- assert_equal 1, Firm.find(:first).clients_using_counter_sql.size
- assert Firm.find(:first).clients_using_counter_sql.any?
- assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size
- assert !Firm.find(:first).clients_using_zero_counter_sql.any?
+ assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size
+ assert Firm.find(:first, :order => "id").clients_using_counter_sql.any?
+ assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size
+ assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any?
end
def test_counting_non_existant_items_using_sql
- assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size
+ assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size
end
def test_counting_using_finder_sql
@@ -183,7 +185,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_ids
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find }
@@ -203,7 +205,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_string_ids_when_using_finder_sql
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
client = firm.clients_using_finder_sql.find("2")
assert_kind_of Client, client
@@ -219,7 +221,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_all
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
end
@@ -264,24 +266,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_find_all_sanitized
- firm = Firm.find(:first)
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
+ firm = Firm.find(:first, :order => "id")
summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
end
def test_find_first
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
client2 = Client.find(2)
- assert_equal firm.clients.first, firm.clients.find(:first)
- assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'")
+ assert_equal firm.clients.first, firm.clients.find(:first, :order => "id")
+ assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id")
end
def test_find_first_sanitized
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
client2 = Client.find(2)
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'])
- assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
+ assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id")
+ assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id")
end
def test_find_in_collection
@@ -341,7 +344,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_create_with_bang_on_has_many_raises_when_record_not_saved
assert_raise(ActiveRecord::RecordInvalid) do
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
firm.plain_clients.create!
end
end
@@ -731,7 +734,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_destroy_dependent_when_deleted_from_association
- firm = Firm.find(:first)
+ # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
+ firm = Firm.find(:first, :order => "id")
assert_equal 2, firm.clients.size
client = firm.clients.first
@@ -798,7 +802,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_less
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
firm.clients = [companies(:first_client)]
assert firm.save, "Could not save firm"
firm.reload
@@ -812,7 +816,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_replace_with_new
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
firm.save
firm.reload
@@ -1104,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
end
def test_creating_using_primary_key
- firm = Firm.find(:first)
+ firm = Firm.find(:first, :order => "id")
client = firm.clients_using_primary_key.create!(:name => 'test')
assert_equal firm.name, client.firm_name
end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 8529ff0285..799ab52025 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -169,6 +169,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert_equal peeps + 1, posts(:thinking).people.count
end
+ def test_create_on_new_record
+ p = Post.new
+
+ assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") }
+ assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") }
+ end
+
def test_clear_associations
assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
@@ -292,4 +299,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys
assert_equal 1, owners(:blackbeard).toys.count
end
+
+ def test_find_on_has_many_association_collection_with_include_and_conditions
+ post_with_no_comments = people(:michael).posts_with_no_comments.first
+ assert_equal post_with_no_comments, posts(:authorless)
+ end
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index b1060d01af..9da7fc2639 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -14,7 +14,9 @@ require 'models/citation'
class AssociationsJoinModelTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books
+ fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
+ # Reload edges table from fixtures as otherwise repeated test was failing
+ :edges
def test_has_many
assert authors(:david).categories.include?(categories(:general))
@@ -343,14 +345,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
def test_has_many_polymorphic_with_source_type
- assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts
+ # added sort by ID as otherwise Oracle select sometimes returned rows in different order
+ assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id)
end
def test_eager_has_many_polymorphic_with_source_type
tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
desired = posts(:welcome, :thinking)
assert_no_queries do
- assert_equal desired, tag_with_include.tagged_posts
+ # added sort by ID as otherwise test using JRuby was failing as array elements were in different order
+ assert_equal desired.sort_by(&:id), tag_with_include.tagged_posts.sort_by(&:id)
end
assert_equal 5, tag_with_include.taggings.length
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index 183be1e2f9..055590da0a 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -4,40 +4,43 @@ require 'models/minimalistic'
class AttributeMethodsTest < ActiveRecord::TestCase
fixtures :topics
+
def setup
- @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup
+ @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup
@target = Class.new(ActiveRecord::Base)
@target.table_name = 'topics'
end
def teardown
- ActiveRecord::Base.send(:attribute_method_suffixes).clear
- ActiveRecord::Base.attribute_method_suffix *@old_suffixes
+ ActiveRecord::Base.send(:attribute_method_matchers).clear
+ ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
- def test_match_attribute_method_query_returns_match_data
- assert_not_nil md = @target.match_attribute_method?('title=')
- assert_equal 'title', md.pre_match
- assert_equal ['='], md.captures
-
- %w(_hello_world ist! _maybe?).each do |suffix|
- @target.class_eval "def attribute#{suffix}(*args) args end"
- @target.attribute_method_suffix suffix
-
- assert_not_nil md = @target.match_attribute_method?("title#{suffix}")
- assert_equal 'title', md.pre_match
- assert_equal [suffix], md.captures
- end
- end
-
- def test_declared_attribute_method_affects_respond_to_and_method_missing
+ def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing
topic = @target.new(:title => 'Budget')
assert topic.respond_to?('title')
assert_equal 'Budget', topic.title
assert !topic.respond_to?('title_hello_world')
assert_raise(NoMethodError) { topic.title_hello_world }
+ end
- %w(_hello_world _it! _candidate= able?).each do |suffix|
+ def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing
+ topic = @target.new(:title => 'Budget')
+ %w(default_ title_).each do |prefix|
+ @target.class_eval "def #{prefix}attribute(*args) args end"
+ @target.attribute_method_prefix prefix
+
+ meth = "#{prefix}title"
+ assert topic.respond_to?(meth)
+ assert_equal ['title'], topic.send(meth)
+ assert_equal ['title', 'a'], topic.send(meth, 'a')
+ assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
+ end
+ end
+
+ def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing
+ topic = @target.new(:title => 'Budget')
+ %w(_default _title_default _it! _candidate= able?).each do |suffix|
@target.class_eval "def attribute#{suffix}(*args) args end"
@target.attribute_method_suffix suffix
@@ -49,6 +52,20 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing
+ topic = @target.new(:title => 'Budget')
+ [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix|
+ @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end"
+ @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix })
+
+ meth = "#{prefix}title#{suffix}"
+ assert topic.respond_to?(meth)
+ assert_equal ['title'], topic.send(meth)
+ assert_equal ['title', 'a'], topic.send(meth, 'a')
+ assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
+ end
+ end
+
def test_should_unserialize_attributes_for_frozen_records
myobj = {:value1 => :value2}
topic = Topic.create("content" => myobj)
@@ -58,13 +75,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_typecast_attribute_from_select_to_false
topic = Topic.create(:title => 'Budget')
- topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
+ # Oracle does not support boolean expressions in SELECT
+ if current_adapter?(:OracleAdapter)
+ topic = Topic.find(:first, :select => "topics.*, 0 as is_test")
+ else
+ topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test")
+ end
assert !topic.is_test?
end
def test_typecast_attribute_from_select_to_true
topic = Topic.create(:title => 'Budget')
- topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
+ # Oracle does not support boolean expressions in SELECT
+ if current_adapter?(:OracleAdapter)
+ topic = Topic.find(:first, :select => "topics.*, 1 as is_test")
+ else
+ topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test")
+ end
assert topic.is_test?
end
@@ -74,10 +101,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
- def test_primary_key_implemented
- assert Class.new(ActiveRecord::Base).instance_method_already_implemented?('id')
- end
-
def test_defined_kernel_methods_implemented_in_model
%w(test name display y).each do |method|
klass = Class.new ActiveRecord::Base
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index ddca5e962d..271086af8e 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -154,7 +154,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_save_fails_for_invalid_belongs_to
- assert log = AuditLog.create(:developer_id => 0, :message => "")
+ # Oracle saves empty string as NULL therefore :message changed to one space
+ assert log = AuditLog.create(:developer_id => 0, :message => " ")
log.developer = Developer.new
assert !log.developer.valid?
@@ -164,7 +165,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
end
def test_save_succeeds_for_invalid_belongs_to_with_validate_false
- assert log = AuditLog.create(:developer_id => 0, :message=> "")
+ # Oracle saves empty string as NULL therefore :message changed to one space
+ assert log = AuditLog.create(:developer_id => 0, :message=> " ")
log.unvalidated_developer = Developer.new
assert !log.unvalidated_developer.valid?
@@ -666,7 +668,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
@pirate.catchphrase = ''
@pirate.ship.name = ''
@pirate.save(false)
- assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
+ # Oracle saves empty string as NULL
+ if current_adapter?(:OracleAdapter)
+ assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name]
+ else
+ assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name]
+ end
end
def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth
@@ -678,7 +685,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
@pirate.save(false)
values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)]
- assert_equal ['', '', '', ''], values
+ # Oracle saves empty string as NULL
+ if current_adapter?(:OracleAdapter)
+ assert_equal [nil, nil, nil, nil], values
+ else
+ assert_equal ['', '', '', ''], values
+ end
end
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
@@ -756,7 +768,12 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
@ship.pirate.catchphrase = ''
@ship.name = ''
@ship.save(false)
- assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
+ # Oracle saves empty string as NULL
+ if current_adapter?(:OracleAdapter)
+ assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase]
+ else
+ assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase]
+ end
end
def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that
@@ -837,11 +854,20 @@ module AutosaveAssociationOnACollectionAssociationTests
@pirate.send(@association_name).each { |child| child.name = '' }
assert @pirate.save(false)
- assert_equal ['', '', ''], [
- @pirate.reload.catchphrase,
- @pirate.send(@association_name).first.name,
- @pirate.send(@association_name).last.name
- ]
+ # Oracle saves empty string as NULL
+ if current_adapter?(:OracleAdapter)
+ assert_equal [nil, nil, nil], [
+ @pirate.reload.catchphrase,
+ @pirate.send(@association_name).first.name,
+ @pirate.send(@association_name).last.name
+ ]
+ else
+ assert_equal ['', '', ''], [
+ @pirate.reload.catchphrase,
+ @pirate.send(@association_name).first.name,
+ @pirate.send(@association_name).last.name
+ ]
+ end
end
def test_should_validation_the_associated_models_on_create
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index e47f898485..16364141df 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -422,11 +422,6 @@ class BasicsTest < ActiveRecord::TestCase
end
- def test_reader_for_invalid_column_names
- Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
- assert !Topic.generated_methods.include?("mumub-jumbo")
- end
-
def test_non_attribute_access_and_assignment
topic = Topic.new
assert !topic.respond_to?("mumbo")
@@ -469,6 +464,60 @@ class BasicsTest < ActiveRecord::TestCase
end
end
+ def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :utc do
+ time = Time.local(2000)
+ topic = Topic.create('written_on' => time)
+ saved_time = Topic.find(topic.id).written_on
+ assert_equal time, saved_time
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a
+ assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
+ end
+ end
+ end
+
+ def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :utc do
+ Time.use_zone 'Central Time (US & Canada)' do
+ time = Time.zone.local(2000)
+ topic = Topic.create('written_on' => time)
+ saved_time = Topic.find(topic.id).written_on
+ assert_equal time, saved_time
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
+ assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a
+ end
+ end
+ end
+ end
+
+ def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local
+ with_env_tz 'America/New_York' do
+ time = Time.utc(2000)
+ topic = Topic.create('written_on' => time)
+ saved_time = Topic.find(topic.id).written_on
+ assert_equal time, saved_time
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a
+ assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a
+ end
+ end
+
+ def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :local do
+ Time.use_zone 'Central Time (US & Canada)' do
+ time = Time.zone.local(2000)
+ topic = Topic.create('written_on' => time)
+ saved_time = Topic.find(topic.id).written_on
+ assert_equal time, saved_time
+ assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a
+ assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a
+ end
+ end
+ end
+ end
+
def test_custom_mutator
topic = Topic.find(1)
# This mutator is protected in the class definition
@@ -1222,6 +1271,23 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal Topic.find(1).new_record?, false
end
+ def test_destroyed_returns_boolean
+ developer = Developer.new
+ assert_equal developer.destroyed?, false
+ developer.destroy
+ assert_equal developer.destroyed?, true
+
+ developer = Developer.first
+ assert_equal developer.destroyed?, false
+ developer.destroy
+ assert_equal developer.destroyed?, true
+
+ developer = Developer.last
+ assert_equal developer.destroyed?, false
+ developer.delete
+ assert_equal developer.destroyed?, true
+ end
+
def test_clone
topic = Topic.find(1)
cloned_topic = nil
@@ -2120,4 +2186,19 @@ class BasicsTest < ActiveRecord::TestCase
def test_dup
assert !Minimalistic.new.freeze.dup.frozen?
end
+
+ protected
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
+ ensure
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ end
+
+ def with_active_record_default_timezone(zone)
+ old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
+ yield
+ ensure
+ ActiveRecord::Base.default_timezone = old_zone
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 75f52dfa4a..c2e02763f6 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -2,6 +2,8 @@ require "cases/helper"
require 'models/company'
require 'models/topic'
require 'models/edge'
+require 'models/club'
+require 'models/organization'
Company.has_many :accounts
@@ -223,6 +225,10 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 15, companies(:rails_core).companies.sum(:id)
end
+ def test_should_sum_scoped_field_with_from
+ assert_equal Club.count, Organization.clubs.count
+ end
+
def test_should_sum_scoped_field_with_conditions
assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
end
@@ -298,7 +304,12 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_should_sum_expression
- assert_equal '636', Account.sum("2 * credit_limit")
+ # Oracle adapter returns floating point value 636.0 after SUM
+ if current_adapter?(:OracleAdapter)
+ assert_equal 636, Account.sum("2 * credit_limit")
+ else
+ assert_equal '636', Account.sum("2 * credit_limit")
+ end
end
def test_count_with_from_option
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index 6274d5250f..c689e97d83 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -6,7 +6,14 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
end
def test_insert_should_return_the_inserted_id
- id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
+ # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method
+ if current_adapter?(:OracleAdapter)
+ sequence_name = "accounts_seq"
+ id_value = @connection.next_sequence_value(sequence_name)
+ id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name)
+ else
+ id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)")
+ end
assert_not_nil id
end
end
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index ac95bac4ad..74571d923a 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -62,6 +62,16 @@ class DirtyTest < ActiveRecord::TestCase
assert_equal parrot.name_change, parrot.title_change
end
+ def test_reset_attribute!
+ pirate = Pirate.create!(:catchphrase => 'Yar!')
+ pirate.catchphrase = 'Ahoy!'
+
+ pirate.reset_catchphrase!
+ assert_equal "Yar!", pirate.catchphrase
+ assert_equal Hash.new, pirate.changes
+ assert !pirate.catchphrase_changed?
+ end
+
def test_nullable_number_not_marked_as_changed_if_new_value_is_blank
pirate = Pirate.new
@@ -288,6 +298,16 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present
+ with_partial_updates(Topic) do
+ Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
+ topic = Topic.first(:select => 'id, author_name')
+ topic.update_attribute :author_name, 'John'
+ topic = Topic.first
+ assert_not_nil topic.content
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index d8f5695a0f..7b6bf597a8 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -156,10 +156,8 @@ class FinderTest < ActiveRecord::TestCase
end
def test_find_all_with_limit
- entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
-
- assert_equal(2, entrants.size)
- assert_equal(entrants(:first).name, entrants.first.name)
+ assert_equal(2, Entrant.find(:all, :limit => 2).size)
+ assert_equal(0, Entrant.find(:all, :limit => 0).size)
end
def test_find_all_with_prepared_limit_and_offset
@@ -168,22 +166,23 @@ class FinderTest < ActiveRecord::TestCase
assert_equal(2, entrants.size)
assert_equal(entrants(:second).name, entrants.first.name)
+ assert_equal 3, Entrant.count
entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
assert_equal(1, entrants.size)
assert_equal(entrants(:third).name, entrants.first.name)
end
- def test_find_all_with_limit_and_offset_and_multiple_orderings
- developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
- assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
- end
+ def test_find_all_with_limit_and_offset_and_multiple_order_clauses
+ first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
+ second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
+ last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
- def test_find_with_limit_and_condition
- developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
- assert_equal(1, developers.size)
- assert_equal("fixture_3", developers.first.name)
+ assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
end
+
def test_find_with_group
developers = Developer.find(:all, :group => "salary", :select => "salary")
assert_equal 4, developers.size
@@ -251,7 +250,7 @@ class FinderTest < ActiveRecord::TestCase
def test_find_only_some_columns
topic = Topic.find(1, :select => "author_name")
- assert_raise(ActiveRecord::MissingAttributeError) {topic.title}
+ assert_raise(ActiveModel::MissingAttributeError) {topic.title}
assert_equal "David", topic.author_name
assert !topic.attribute_present?("title")
#assert !topic.respond_to?("title")
@@ -423,6 +422,42 @@ class FinderTest < ActiveRecord::TestCase
assert_equal customers(:david), found_customer
end
+ def test_condition_utc_time_interpolation_with_default_timezone_local
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :local do
+ topic = Topic.first
+ assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc])
+ end
+ end
+ end
+
+ def test_hash_condition_utc_time_interpolation_with_default_timezone_local
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :local do
+ topic = Topic.first
+ assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc})
+ end
+ end
+ end
+
+ def test_condition_local_time_interpolation_with_default_timezone_utc
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :utc do
+ topic = Topic.first
+ assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal])
+ end
+ end
+ end
+
+ def test_hash_condition_local_time_interpolation_with_default_timezone_utc
+ with_env_tz 'America/New_York' do
+ with_active_record_default_timezone :utc do
+ topic = Topic.first
+ assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal})
+ end
+ end
+ end
+
def test_bind_variables
assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
@@ -942,40 +977,6 @@ class FinderTest < ActiveRecord::TestCase
assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
end
- def test_find_all_with_limit
- first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5
- assert_equal 5, first_five_developers.length
- assert_equal 'David', first_five_developers.first.name
- assert_equal 'fixture_5', first_five_developers.last.name
-
- no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
- assert_equal 0, no_developers.length
- end
-
- def test_find_all_with_limit_and_offset
- first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
- second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
- last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
-
- assert_equal 3, first_three_developers.length
- assert_equal 3, second_three_developers.length
- assert_equal 2, last_two_developers.length
-
- assert_equal 'David', first_three_developers.first.name
- assert_equal 'fixture_4', second_three_developers.first.name
- assert_equal 'fixture_9', last_two_developers.first.name
- end
-
- def test_find_all_with_limit_and_offset_and_multiple_order_clauses
- first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
- second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
- last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
-
- assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
- assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
- end
-
def test_find_all_with_join
developers_on_project_one = Developer.find(
:all,
@@ -991,7 +992,7 @@ class FinderTest < ActiveRecord::TestCase
def test_joins_dont_clobber_id
first = Firm.find(
:first,
- :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id',
+ :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id',
:conditions => 'companies.id = 1'
)
assert_equal 1, first.id
@@ -1087,4 +1088,18 @@ class FinderTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
end
end
+
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
+ ensure
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ end
+
+ def with_active_record_default_timezone(zone)
+ old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone
+ yield
+ ensure
+ ActiveRecord::Base.default_timezone = old_zone
+ end
end
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index b07d4f3521..eb3f03c91d 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -185,7 +185,7 @@ class FixturesTest < ActiveRecord::TestCase
def test_binary_in_fixtures
assert_equal 1, @binaries.size
- data = File.read(ASSETS_ROOT + "/flowers.jpg")
+ data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read }
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
data.freeze
assert_equal data, @flowers.data
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 167d3abad9..5cd11e9799 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -137,7 +137,8 @@ class InheritanceTest < ActiveRecord::TestCase
def test_update_all_within_inheritance
Client.update_all "name = 'I am a client'"
assert_equal "I am a client", Client.find(:all).first.name
- assert_equal "37signals", Firm.find(:all).first.name
+ # Order by added as otherwise Oracle tests were failing because of different order of results
+ assert_equal "37signals", Firm.find(:all, :order => "id").first.name
end
def test_alt_update_all_within_inheritance
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index e2bb17c37f..99af7d2986 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -11,13 +11,23 @@ class InvalidDateTest < Test::Unit::TestCase
valid_dates.each do |date_src|
topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
- assert_equal(topic.last_read, Date.new(*date_src))
+ # Oracle DATE columns are datetime columns and Oracle adapter returns Time value
+ if current_adapter?(:OracleAdapter)
+ assert_equal(topic.last_read.to_date, Date.new(*date_src))
+ else
+ assert_equal(topic.last_read, Date.new(*date_src))
+ end
end
invalid_dates.each do |date_src|
assert_nothing_raised do
topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s})
- assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ # Oracle DATE columns are datetime columns and Oracle adapter returns Time value
+ if current_adapter?(:OracleAdapter)
+ assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ else
+ assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ end
end
end
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index d8246f49b8..35f7bc5443 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -379,7 +379,8 @@ class NestedScopingTest < ActiveRecord::TestCase
poor_jamis = developers(:poor_jamis)
Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do
- assert_sql /ORDER BY id asc / do
+ # Oracle adapter does not generated space after asc therefore trailing space removed from regex
+ assert_sql /ORDER BY id asc/ do
assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
end
end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 215b5a427a..f0f21615e0 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -446,18 +446,22 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal Date, bob.favorite_day.class
end
- # Test DateTime column and defaults, including timezone.
- # FIXME: moment of truth may be Time on 64-bit platforms.
- if bob.moment_of_truth.is_a?(DateTime)
-
- with_env_tz 'US/Eastern' do
- assert_equal DateTime.local_offset, bob.moment_of_truth.offset
- assert_not_equal 0, bob.moment_of_truth.offset
- assert_not_equal "Z", bob.moment_of_truth.zone
- # US/Eastern is -5 hours from GMT
- assert_equal Rational(-5, 24), bob.moment_of_truth.offset
- assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM
- assert_equal DateTime::ITALY, bob.moment_of_truth.start
+ # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column
+ # therefore no timezone change is done afterwards when default timezone is changed
+ unless current_adapter?(:OracleAdapter)
+ # Test DateTime column and defaults, including timezone.
+ # FIXME: moment of truth may be Time on 64-bit platforms.
+ if bob.moment_of_truth.is_a?(DateTime)
+
+ with_env_tz 'US/Eastern' do
+ assert_equal DateTime.local_offset, bob.moment_of_truth.offset
+ assert_not_equal 0, bob.moment_of_truth.offset
+ assert_not_equal "Z", bob.moment_of_truth.zone
+ # US/Eastern is -5 hours from GMT
+ assert_equal Rational(-5, 24), bob.moment_of_truth.offset
+ assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_equal DateTime::ITALY, bob.moment_of_truth.start
+ end
end
end
@@ -571,7 +575,7 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Base.connection.create_table(:hats) do |table|
table.column :hat_name, :string, :default => nil
end
- exception = if current_adapter?(:PostgreSQLAdapter)
+ exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
ActiveRecord::StatementInvalid
else
ActiveRecord::ActiveRecordError
@@ -625,7 +629,13 @@ if ActiveRecord::Base.connection.supports_migrations?
table.column :hat_size, :integer
table.column :hat_style, :string, :limit => 100
end
- ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
+ # Oracle index names should be 30 or less characters
+ if current_adapter?(:OracleAdapter)
+ ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true,
+ :name => 'index_hats_on_hat_style_size'
+ else
+ ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
+ end
assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
ensure
@@ -727,19 +737,20 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_change_column
Person.connection.add_column 'people', 'age', :integer
- old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
+ label = "test_change_column Columns"
+ old_columns = Person.connection.columns(Person.table_name, label)
assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
assert_nothing_raised { Person.connection.change_column "people", "age", :string }
- new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
+ new_columns = Person.connection.columns(Person.table_name, label)
assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
assert new_columns.find { |c| c.name == 'age' and c.type == :string }
- old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
+ old_columns = Topic.connection.columns(Topic.table_name, label)
assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false }
- new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
+ new_columns = Topic.connection.columns(Topic.table_name, label)
assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
@@ -783,7 +794,12 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 }
- assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" }
+ else
+ assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
+ end
ensure
Person.connection.drop_table :testings rescue nil
end
@@ -799,7 +815,12 @@ if ActiveRecord::Base.connection.supports_migrations?
person_klass.reset_column_information
assert_equal 99, person_klass.columns_hash["wealth"].default
assert_equal false, person_klass.columns_hash["wealth"].null
- assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+ # Oracle needs primary key value from sequence
+ if current_adapter?(:OracleAdapter)
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")}
+ else
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+ end
# change column default to see that column doesn't lose its not null definition
person_klass.connection.change_column_default "testings", "wealth", 100
@@ -1054,7 +1075,12 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def test_migrator_db_has_no_schema_migrations_table
- ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
+ # Oracle adapter raises error if semicolon is present as last character
+ if current_adapter?(:OracleAdapter)
+ ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations")
+ else
+ ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
+ end
assert_nothing_raised do
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
end
@@ -1110,6 +1136,17 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal(0, ActiveRecord::Migrator.current_version)
end
+ def test_migrator_forward
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2)
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+ end
+
def test_schema_migrations_table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
ActiveRecord::Base.table_name_suffix = "_suffix"
@@ -1412,6 +1449,8 @@ if ActiveRecord::Base.connection.supports_migrations?
def string_column
if current_adapter?(:PostgreSQLAdapter)
"character varying(255)"
+ elsif current_adapter?(:OracleAdapter)
+ 'VARCHAR2(255)'
else
'varchar(255)'
end
@@ -1420,6 +1459,8 @@ if ActiveRecord::Base.connection.supports_migrations?
def integer_column
if current_adapter?(:MysqlAdapter)
'int(11)'
+ elsif current_adapter?(:OracleAdapter)
+ 'NUMBER(38)'
else
'integer'
end
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 283333fc04..4f559bcaa5 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -4,6 +4,23 @@ require 'models/company_in_module'
class ModulesTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :projects, :developers
+ def setup
+ # need to make sure Object::Firm and Object::Client are not defined,
+ # so that constantize will not be able to cheat when having to load namespaced classes
+ @undefined_consts = {}
+
+ [:Firm, :Client].each do |const|
+ @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const)
+ end
+ end
+
+ def teardown
+ # reinstate the constants that we undefined in the setup
+ @undefined_consts.each do |constant, value|
+ Object.send :const_set, constant, value unless value.nil?
+ end
+ end
+
def test_module_spanning_associations
firm = MyApplication::Business::Firm.find(:first)
assert !firm.clients.empty?, "Firm should have clients"
@@ -36,4 +53,29 @@ class ModulesTest < ActiveRecord::TestCase
assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass'
assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model'
end
+
+ def test_assign_ids
+ firm = MyApplication::Business::Firm.first
+
+ assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ firm.client_ids = [MyApplication::Business::Client.first.id]
+ end
+ end
+
+ # need to add an eager loading condition to force the eager loading model into
+ # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640
+ def test_eager_loading_in_modules
+ clients = []
+
+ assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do
+ clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL')
+ clients << MyApplication::Business::Client.find(3, :include => {:firm => :account})
+ end
+
+ clients.each do |client|
+ assert_no_queries do
+ assert_not_nil(client.firm.account)
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index f4fdc9a39d..2a729f0678 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -154,7 +154,8 @@ class NamedScopeTest < ActiveRecord::TestCase
assert !authors(:david).posts.ranked_by_comments.limit(5).empty?
assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5)
- assert_equal authors(:david).posts.ranked_by_comments.limit(5), authors(:david).posts.top(5)
+ # Oracle sometimes sorts differently if WHERE condition is changed
+ assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id)
assert_equal Post.ranked_by_comments.limit(5), Post.top(5)
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index f31275163d..d033c1e760 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -4,6 +4,8 @@ require "models/ship"
require "models/bird"
require "models/parrot"
require "models/treasure"
+require "models/man"
+require "models/interest"
require 'active_support/hash_with_indifferent_access'
module AssertRaiseWithMessage
@@ -470,6 +472,41 @@ module NestedAttributesOnACollectionAssociationTests
assert Pirate.reflect_on_association(@association_name).options[:autosave]
end
+ def test_validate_presence_of_parent__works_with_inverse_of
+ Man.accepts_nested_attributes_for(:interests)
+ assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of]
+ assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of]
+
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:man)
+ assert_difference 'Man.count' do
+ assert_difference 'Interest.count', 2 do
+ man = Man.create!(:name => 'John',
+ :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
+ assert_equal 2, man.interests.count
+ end
+ end
+ end
+ end
+
+ def test_validate_presence_of_parent__fails_without_inverse_of
+ Man.accepts_nested_attributes_for(:interests)
+ Man.reflect_on_association(:interests).options.delete(:inverse_of)
+ Interest.reflect_on_association(:man).options.delete(:inverse_of)
+
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:man)
+ assert_no_difference ['Man.count', 'Interest.count'] do
+ man = Man.create(:name => 'John',
+ :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}])
+ assert !man.errors[:interests_man].empty?
+ end
+ end
+ # restore :inverse_of
+ Man.reflect_on_association(:interests).options[:inverse_of] = :man
+ Interest.reflect_on_association(:man).options[:inverse_of] = :interests
+ end
+
private
def association_setter
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index f90a66d1dc..2af6a56b6a 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -50,7 +50,12 @@ class QueryCacheTest < ActiveRecord::TestCase
def test_cache_does_not_wrap_string_results_in_arrays
Task.cache do
- assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ # Oracle adapter returns count() as Fixnum or Float
+ if current_adapter?(:OracleAdapter)
+ assert Task.connection.select_value("SELECT count(*) AS count_all FROM tasks").is_a?(Numeric)
+ else
+ assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ end
end
end
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 9612b0beb6..e6a77f626b 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -114,6 +114,11 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_6.*:limit => 6}, output
assert_match %r{c_int_7.*:limit => 7}, output
assert_match %r{c_int_8.*:limit => 8}, output
+ elsif current_adapter?(:OracleAdapter)
+ assert_match %r{c_int_5.*:limit => 5}, output
+ assert_match %r{c_int_6.*:limit => 6}, output
+ assert_match %r{c_int_7.*:limit => 7}, output
+ assert_match %r{c_int_8.*:limit => 8}, output
else
assert_match %r{c_int_5.*:limit => 8}, output
assert_match %r{c_int_6.*:limit => 8}, output
@@ -193,6 +198,19 @@ class SchemaDumperTest < ActiveRecord::TestCase
def test_schema_dump_keeps_large_precision_integer_columns_as_decimal
output = standard_dump
- assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output
+ # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers
+ if current_adapter?(:OracleAdapter)
+ assert_match %r{t.integer\s+"atoms_in_universe",\s+:precision => 38,\s+:scale => 0}, output
+ else
+ assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output
+ end
+ end
+
+ def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added
+ output = standard_dump
+ match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n})
+ assert_not_nil(match, "goofy_string_id table not found")
+ assert_match %r(:id => false), match[1], "no table id not preserved"
+ assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved"
end
end
diff --git a/activerecord/test/cases/state_machine_test.rb b/activerecord/test/cases/state_machine_test.rb
new file mode 100644
index 0000000000..5d13668bab
--- /dev/null
+++ b/activerecord/test/cases/state_machine_test.rb
@@ -0,0 +1,42 @@
+require 'cases/helper'
+require 'models/traffic_light'
+
+class StateMachineTest < ActiveRecord::TestCase
+ def setup
+ @light = TrafficLight.create!
+ end
+
+ test "states initial state" do
+ assert @light.off?
+ assert_equal :off, @light.current_state
+ end
+
+ test "transition to a valid state" do
+ @light.reset
+ assert @light.red?
+ assert_equal :red, @light.current_state
+
+ @light.green_on
+ assert @light.green?
+ assert_equal :green, @light.current_state
+ end
+
+ test "transition does not persist state" do
+ @light.reset
+ assert_equal :red, @light.current_state
+ @light.reload
+ assert_equal "off", @light.state
+ end
+
+ test "transition does persists state" do
+ @light.reset!
+ assert_equal :red, @light.current_state
+ @light.reload
+ assert_equal "red", @light.state
+ end
+
+ test "transition to an invalid state" do
+ assert_raise(ActiveModel::StateMachine::InvalidTransition) { @light.yellow_on }
+ assert_equal :off, @light.current_state
+ end
+end
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index b1203c12ed..278a7a6a06 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -3,6 +3,9 @@ require "cases/helper"
require 'models/topic'
require 'models/reply'
require 'models/owner'
+require 'models/pet'
+require 'models/man'
+require 'models/interest'
class AssociationValidationTest < ActiveRecord::TestCase
fixtures :topics, :owners
@@ -98,4 +101,24 @@ class AssociationValidationTest < ActiveRecord::TestCase
end
end
end
+
+ def test_validates_presence_of_belongs_to_association__parent_is_new_record
+ repair_validations(Interest) do
+ # Note that Interest and Man have the :inverse_of option set
+ Interest.validates_presence_of(:man)
+ man = Man.new(:name => 'John')
+ interest = man.interests.build(:topic => 'Airplanes')
+ assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated"
+ end
+ end
+
+ def test_validates_presence_of_belongs_to_association__existing_parent
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:man)
+ man = Man.create!(:name => 'John')
+ interest = man.interests.build(:topic => 'Airplanes')
+ assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated"
+ end
+ end
+
end
diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb
index 0954b27f87..c8183dc0fb 100644
--- a/activerecord/test/connections/native_oracle/connection.rb
+++ b/activerecord/test/connections/native_oracle/connection.rb
@@ -1,27 +1,68 @@
+# gem "rsim-activerecord-oracle_enhanced-adapter"
+# gem "activerecord-oracle_enhanced-adapter", ">=1.2.1"
+# uses local copy of oracle_enhanced adapter
+$:.unshift("../../oracle-enhanced/lib")
+require 'active_record/connection_adapters/oracle_enhanced_adapter'
+# gem "activerecord-jdbc-adapter"
+# require 'active_record/connection_adapters/jdbc_adapter'
+
+# otherwise failed with silence_warnings method missing exception
+require 'active_support/core_ext/kernel/reporting'
+
print "Using Oracle\n"
require_dependency 'models/course'
require 'logger'
-ActiveRecord::Base.logger = Logger.new STDOUT
-ActiveRecord::Base.logger.level = Logger::WARN
+# ActiveRecord::Base.logger = Logger.new STDOUT
+# ActiveRecord::Base.logger.level = Logger::WARN
+ActiveRecord::Base.logger = Logger.new("debug.log")
# Set these to your database connection strings
-db = ENV['ARUNIT_DB'] || 'activerecord_unittest'
+db = ENV['ARUNIT_DB_NAME'] = 'orcl'
ActiveRecord::Base.configurations = {
'arunit' => {
- :adapter => 'oracle',
+ :adapter => 'oracle_enhanced',
+ :database => db,
+ :host => "localhost", # used just by JRuby to construct JDBC connect string
+ # :adapter => "jdbc",
+ # :driver => "oracle.jdbc.driver.OracleDriver",
+ # :url => "jdbc:oracle:thin:@localhost:1521:#{db}",
:username => 'arunit',
:password => 'arunit',
- :database => db,
+ :emulate_oracle_adapter => true
},
'arunit2' => {
- :adapter => 'oracle',
+ :adapter => 'oracle_enhanced',
+ :database => db,
+ :host => "localhost", # used just by JRuby to construct JDBC connect string
+ # :adapter => "jdbc",
+ # :driver => "oracle.jdbc.driver.OracleDriver",
+ # :url => "jdbc:oracle:thin:@localhost:1521:#{db}",
:username => 'arunit2',
:password => 'arunit2',
- :database => db
+ :emulate_oracle_adapter => true
}
}
ActiveRecord::Base.establish_connection 'arunit'
Course.establish_connection 'arunit2'
+
+# ActiveRecord::Base.connection.execute %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
+# ActiveRecord::Base.connection.execute %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
+
+# for assert_queries test helper
+ActiveRecord::Base.connection.class.class_eval do
+ IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im]
+
+ def select_with_query_record(sql, name = nil, return_column_names = false)
+ $queries_executed ||= []
+ $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r }
+ select_without_query_record(sql, name, return_column_names)
+ end
+
+ alias_method_chain :select, :query_record
+end
+
+# For JRuby Set default $KCODE to UTF8
+$KCODE = "UTF8" if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 22168468a6..1c05e523e0 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -73,12 +73,16 @@ class Firm < Company
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
- has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account"
+ # added order by id as in fixtures there are two accounts for Rails Core
+ # Oracle tests were failing because of that as the second fixture was selected
+ has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account", :order => "id"
has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete
end
class DependentFirm < Company
- has_one :account, :foreign_key => "firm_id", :dependent => :nullify
+ # added order by id as in fixtures there are two accounts for Rails Core
+ # Oracle tests were failing because of that as the second fixture was selected
+ has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id"
has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify
end
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 8b84c2fb5e..cdda7a44d4 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -13,7 +13,7 @@ module MyApplication
has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}'
- has_one :account, :dependent => :destroy
+ has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy
end
class Client < Company
diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb
index d79d5037c8..c85726169e 100644
--- a/activerecord/test/models/organization.rb
+++ b/activerecord/test/models/organization.rb
@@ -1,4 +1,6 @@
class Organization < ActiveRecord::Base
has_many :member_details
has_many :members, :through => :member_details
+
+ named_scope :clubs, { :from => 'clubs' }
end \ No newline at end of file
diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb
index 3502943f3a..1b9d8107f8 100644
--- a/activerecord/test/models/subject.rb
+++ b/activerecord/test/models/subject.rb
@@ -1,4 +1,12 @@
-# used for OracleSynonymTest, see test/synonym_test_oci.rb
+# used for OracleSynonymTest, see test/synonym_test_oracle.rb
#
class Subject < ActiveRecord::Base
+ protected
+ # added initialization of author_email_address in the same way as in Topic class
+ # as otherwise synonym test was failing
+ def after_initialize
+ if self.new_record?
+ self.author_email_address = 'test@test.com'
+ end
+ end
end
diff --git a/activerecord/test/models/traffic_light.rb b/activerecord/test/models/traffic_light.rb
new file mode 100644
index 0000000000..f8cfddbef9
--- /dev/null
+++ b/activerecord/test/models/traffic_light.rb
@@ -0,0 +1,27 @@
+class TrafficLight < ActiveRecord::Base
+ include ActiveRecord::StateMachine
+
+ state_machine do
+ state :off
+
+ state :red
+ state :green
+ state :yellow
+
+ event :red_on do
+ transitions :to => :red, :from => [:yellow]
+ end
+
+ event :green_on do
+ transitions :to => :green, :from => [:red]
+ end
+
+ event :yellow_on do
+ transitions :to => :yellow, :from => [:green]
+ end
+
+ event :reset do
+ transitions :to => :red, :from => [:off]
+ end
+ end
+end
diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb
index 2d87f34625..3314687445 100644
--- a/activerecord/test/schema/oracle_specific_schema.rb
+++ b/activerecord/test/schema/oracle_specific_schema.rb
@@ -2,6 +2,10 @@ ActiveRecord::Schema.define do
execute "drop table test_oracle_defaults" rescue nil
execute "drop sequence test_oracle_defaults_seq" rescue nil
+ execute "drop sequence companies_nonstd_seq" rescue nil
+ execute "drop synonym subjects" rescue nil
+ execute "drop table defaults" rescue nil
+ execute "drop sequence defaults_seq" rescue nil
execute <<-SQL
create table test_oracle_defaults (
@@ -16,4 +20,27 @@ create table test_oracle_defaults (
create sequence test_oracle_defaults_seq minvalue 10000
SQL
+ execute "create sequence companies_nonstd_seq minvalue 10000"
+
+ execute "create synonym subjects for topics"
+
+ execute <<-SQL
+ CREATE TABLE defaults (
+ id integer not null,
+ modified_date date default sysdate,
+ modified_date_function date default sysdate,
+ fixed_date date default to_date('2004-01-01', 'YYYY-MM-DD'),
+ modified_time date default sysdate,
+ modified_time_function date default sysdate,
+ fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'),
+ char1 varchar2(1) default 'Y',
+ char2 varchar2(50) default 'a varchar field',
+ char3 clob default 'a text field',
+ positive_integer integer default 1,
+ negative_integer integer default -1,
+ decimal_number number(3,2) default 2.78
+ )
+ SQL
+ execute "create sequence defaults_seq minvalue 10000"
+
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 2b7d3856b7..5f60d5e137 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -104,7 +104,13 @@ ActiveRecord::Schema.define do
create_table :comments, :force => true do |t|
t.integer :post_id, :null => false
- t.text :body, :null => false
+ # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
+ # Oracle SELECT WHERE clause which causes many unit test failures
+ if current_adapter?(:OracleAdapter)
+ t.string :body, :null => false, :limit => 4000
+ else
+ t.text :body, :null => false
+ end
t.string :type
end
@@ -279,7 +285,12 @@ ActiveRecord::Schema.define do
t.decimal :my_house_population, :precision => 2, :scale => 0
t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78
t.float :temperature
- t.decimal :atoms_in_universe, :precision => 55, :scale => 0
+ # Oracle supports precision up to 38
+ if current_adapter?(:OracleAdapter)
+ t.decimal :atoms_in_universe, :precision => 38, :scale => 0
+ else
+ t.decimal :atoms_in_universe, :precision => 55, :scale => 0
+ end
end
create_table :orders, :force => true do |t|
@@ -350,7 +361,13 @@ ActiveRecord::Schema.define do
create_table :posts, :force => true do |t|
t.integer :author_id
t.string :title, :null => false
- t.text :body, :null => false
+ # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
+ # Oracle SELECT WHERE clause which causes many unit test failures
+ if current_adapter?(:OracleAdapter)
+ t.string :body, :null => false, :limit => 4000
+ else
+ t.text :body, :null => false
+ end
t.string :type
t.integer :comments_count, :default => 0
t.integer :taggings_count, :default => 0
@@ -423,7 +440,13 @@ ActiveRecord::Schema.define do
t.datetime :written_on
t.time :bonus_time
t.date :last_read
- t.text :content
+ # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in
+ # Oracle SELECT WHERE clause which causes many unit test failures
+ if current_adapter?(:OracleAdapter)
+ t.string :content, :limit => 4000
+ else
+ t.text :content
+ end
t.boolean :approved, :default => true
t.integer :replies_count, :default => 0
t.integer :parent_id
@@ -448,6 +471,13 @@ ActiveRecord::Schema.define do
t.integer :pet_id, :integer
end
+ create_table :traffic_lights, :force => true do |t|
+ t.string :location
+ t.string :state
+ t.datetime :created_at
+ t.datetime :updated_at
+ end
+
create_table :treasures, :force => true do |t|
t.column :name, :string
t.column :looter_id, :integer
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 6572934893..1f9ecea6c5 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,3 +1,8 @@
+*Edge*
+
+* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
+
+
*2.3.2 [Final] (March 15, 2009)*
* Nothing new, just included in 2.3.2
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index bc82139dac..9db35881b8 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -257,6 +257,22 @@ module ActiveResource
end
end
+ # Gets the \proxy variable if a proxy is required
+ def proxy
+ # Not using superclass_delegating_reader. See +site+ for explanation
+ if defined?(@proxy)
+ @proxy
+ elsif superclass != Object && superclass.proxy
+ superclass.proxy.dup.freeze
+ end
+ end
+
+ # Sets the URI of the http proxy to the value in the +proxy+ argument.
+ def proxy=(proxy)
+ @connection = nil
+ @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
+ end
+
# Gets the \user for REST HTTP authentication.
def user
# Not using superclass_delegating_reader. See +site+ for explanation
@@ -332,6 +348,7 @@ module ActiveResource
def connection(refresh = false)
if defined?(@connection) || superclass == Object
@connection = Connection.new(site, format) if refresh || @connection.nil?
+ @connection.proxy = proxy if proxy
@connection.user = user if user
@connection.password = password if password
@connection.timeout = timeout if timeout
@@ -622,6 +639,11 @@ module ActiveResource
site.is_a?(URI) ? site.dup : URI.parse(site)
end
+ # Accepts a URI and creates the proxy URI from that.
+ def create_proxy_uri_from(proxy)
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
+ end
+
# contains a set of the current prefix parameters.
def prefix_parameters
@prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index fb3fde59d6..ef57c1f8b2 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -16,7 +16,7 @@ module ActiveResource
:delete => 'Accept'
}
- attr_reader :site, :user, :password, :timeout
+ attr_reader :site, :user, :password, :timeout, :proxy
attr_accessor :format
class << self
@@ -41,6 +41,11 @@ module ActiveResource
@password = URI.decode(@site.password) if @site.password
end
+ # Set the proxy for remote service.
+ def proxy=(proxy)
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
+ end
+
# Sets the user for remote service.
def user=(user)
@user = user
@@ -132,10 +137,27 @@ module ActiveResource
# Creates new Net::HTTP instance for communication with the
# remote service and resources.
def http
- http = Net::HTTP.new(@site.host, @site.port)
- http.use_ssl = @site.is_a?(URI::HTTPS)
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
- http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
+ configure_http(new_http)
+ end
+
+ def new_http
+ if @proxy
+ Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
+ else
+ Net::HTTP.new(@site.host, @site.port)
+ end
+ end
+
+ def configure_http(http)
+ http.use_ssl = @site.is_a?(URI::HTTPS)
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
+
+ # Net::HTTP timeouts default to 60 seconds.
+ if @timeout
+ http.open_timeout = @timeout
+ http.read_timeout = @timeout
+ end
+
http
end
diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb
index 82d3b2ae96..e68d562d97 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/base_test.rb
@@ -3,6 +3,7 @@ require "fixtures/person"
require "fixtures/customer"
require "fixtures/street_address"
require "fixtures/beast"
+require "fixtures/proxy"
require 'active_support/core_ext/hash/conversions'
class BaseTest < Test::Unit::TestCase
@@ -125,6 +126,28 @@ class BaseTest < Test::Unit::TestCase
assert_nil actor.site
end
+ def test_proxy_accessor_accepts_uri_or_string_argument
+ proxy = URI.parse('http://localhost')
+
+ assert_nothing_raised { Person.proxy = 'http://localhost' }
+ assert_equal proxy, Person.proxy
+
+ assert_nothing_raised { Person.proxy = proxy }
+ assert_equal proxy, Person.proxy
+ end
+
+ def test_should_use_proxy_prefix_and_credentials
+ assert_equal 'http://user:password@proxy.local:3000', ProxyResource.proxy.to_s
+ end
+
+ def test_proxy_variable_can_be_reset
+ actor = Class.new(ActiveResource::Base)
+ assert_nil actor.site
+ actor.proxy = 'http://localhost:31337'
+ actor.proxy = nil
+ assert_nil actor.site
+ end
+
def test_should_accept_setting_user
Forum.user = 'david'
assert_equal('david', Forum.user)
@@ -221,6 +244,47 @@ class BaseTest < Test::Unit::TestCase
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
end
+ def test_proxy_reader_uses_superclass_site_until_written
+ # Superclass is Object so returns nil.
+ assert_nil ActiveResource::Base.proxy
+ assert_nil Class.new(ActiveResource::Base).proxy
+
+ # Subclass uses superclass proxy.
+ actor = Class.new(Person)
+ assert_equal Person.proxy, actor.proxy
+
+ # Subclass returns frozen superclass copy.
+ assert !Person.proxy.frozen?
+ assert actor.proxy.frozen?
+
+ # Changing subclass proxy doesn't change superclass site.
+ actor.proxy = 'http://localhost:31337'
+ assert_not_equal Person.proxy, actor.proxy
+
+ # Changed subclass proxy is not frozen.
+ assert !actor.proxy.frozen?
+
+ # Changing superclass proxy doesn't overwrite subclass site.
+ Person.proxy = 'http://somewhere.else'
+ assert_not_equal Person.proxy, actor.proxy
+
+ # Changing superclass proxy after subclassing changes subclass site.
+ jester = Class.new(actor)
+ actor.proxy = 'http://nomad'
+ assert_equal actor.proxy, jester.proxy
+ assert jester.proxy.frozen?
+
+ # Subclasses are always equal to superclass proxy when not overridden
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+
+ fruit.proxy = 'http://market'
+ assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
+
+ fruit.proxy = 'http://supermarket'
+ assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
+ end
+
def test_user_reader_uses_superclass_user_until_written
# Superclass is Object so returns nil.
assert_nil ActiveResource::Base.user
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 831fbc4003..12e8058b0c 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -101,6 +101,16 @@ class ConnectionTest < Test::Unit::TestCase
assert_equal site, @conn.site
end
+ def test_proxy_accessor_accepts_uri_or_string_argument
+ proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242")
+
+ assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" }
+ assert_equal proxy, @conn.proxy
+
+ assert_nothing_raised { @conn.proxy = proxy }
+ assert_equal proxy, @conn.proxy
+ end
+
def test_timeout_accessor
@conn.timeout = 5
assert_equal 5, @conn.timeout
@@ -175,6 +185,17 @@ class ConnectionTest < Test::Unit::TestCase
assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') }
end
+ def test_setting_timeout
+ http = Net::HTTP.new('')
+
+ [10, 20].each do |timeout|
+ @conn.timeout = timeout
+ @conn.send(:configure_http, http)
+ assert_equal timeout, http.open_timeout
+ assert_equal timeout, http.read_timeout
+ end
+ end
+
def test_accept_http_header
@http = mock('new Net::HTTP')
@conn.expects(:http).returns(@http)
diff --git a/activeresource/test/fixtures/proxy.rb b/activeresource/test/fixtures/proxy.rb
new file mode 100644
index 0000000000..bb8e015df0
--- /dev/null
+++ b/activeresource/test/fixtures/proxy.rb
@@ -0,0 +1,4 @@
+class ProxyResource < ActiveResource::Base
+ self.site = "http://localhost"
+ self.proxy = "http://user:password@proxy.local:3000"
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 434a32b29b..15a303cf04 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -55,12 +55,10 @@ module Enumerable
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
#
def sum(identity = 0, &block)
- return identity unless size > 0
-
if block_given?
- map(&block).sum
+ map(&block).sum(identity)
else
- inject { |sum, element| sum + element }
+ inject { |sum, element| sum + element } || identity
end
end
@@ -113,3 +111,13 @@ module Enumerable
!any?(&block)
end unless [].respond_to?(:none?)
end
+
+class Range #:nodoc:
+ # Optimize range sum to use arithmetic progression if a block is not given and
+ # we have a range of numeric values.
+ def sum(identity = 0)
+ return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer))
+ actual_last = exclude_end? ? (last - 1) : last
+ (actual_last - first + 1) * (actual_last + first) / 2
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index f36a21818f..cc6287b100 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -20,7 +20,8 @@ class MissingSourceFile < LoadError #:nodoc:
REGEXPS = [
[/^no such file to load -- (.+)$/i, 1],
[/^Missing \w+ (file\s*)?([^\s]+.rb)$/i, 2],
- [/^Missing API definition file in (.+)$/i, 1]
+ [/^Missing API definition file in (.+)$/i, 1],
+ [/win32/, 0]
] unless defined?(REGEXPS)
end
diff --git a/activesupport/lib/active_support/core_ext/string/bytesize.rb b/activesupport/lib/active_support/core_ext/string/bytesize.rb
new file mode 100644
index 0000000000..ed051b921e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/string/bytesize.rb
@@ -0,0 +1,5 @@
+unless '1.9'.respond_to?(:bytesize)
+ class String
+ alias :bytesize :size
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb
index d459c03d39..d9159b690a 100644
--- a/activesupport/lib/active_support/core_ext/string/interpolation.rb
+++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb
@@ -6,6 +6,7 @@
=end
if RUBY_VERSION < '1.9'
+ require 'active_support/core_ext/string/bytesize'
# KeyError is raised by String#% when the string contains a named placeholder
# that is not contained in the given arguments hash. Ruby 1.9 includes and
@@ -24,8 +25,6 @@ if RUBY_VERSION < '1.9'
# the meaning of the msgids using "named argument" instead of %s/%d style.
class String
- # For older ruby versions, such as ruby-1.8.5
- alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
alias :interpolate_without_ruby_19_syntax :% # :nodoc:
INTERPOLATION_PATTERN = Regexp.union(
@@ -90,4 +89,4 @@ if RUBY_VERSION < '1.9'
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index b73c3b2c9b..4f3b869f50 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -158,7 +158,7 @@ class Time
alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Time representing the end of this week (Sunday, 23:59:59)
+ # Returns a new Time representing the end of this week, (end of Sunday)
def end_of_week
days_to_sunday = wday!=0 ? 7-wday : 0
(self + days_to_sunday.days).end_of_day
@@ -178,9 +178,9 @@ class Time
alias :at_midnight :beginning_of_day
alias :at_beginning_of_day :beginning_of_day
- # Returns a new Time representing the end of the day (23:59:59)
+ # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9)
def end_of_day
- change(:hour => 23, :min => 59, :sec => 59)
+ change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
end
# Returns a new Time representing the start of the month (1st of the month, 0:00)
@@ -190,11 +190,11 @@ class Time
end
alias :at_beginning_of_month :beginning_of_month
- # Returns a new Time representing the end of the month (last day of the month, 0:00)
+ # Returns a new Time representing the end of the month (end of the last day of the month)
def end_of_month
#self - ((self.mday-1).days + self.seconds_since_midnight)
last_day = ::Time.days_in_month(month, year)
- change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 0)
+ change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
end
alias :at_end_of_month :end_of_month
@@ -204,7 +204,7 @@ class Time
end
alias :at_beginning_of_quarter :beginning_of_quarter
- # Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59)
+ # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december)
def end_of_quarter
beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month
end
@@ -216,9 +216,9 @@ class Time
end
alias :at_beginning_of_year :beginning_of_year
- # Returns a new Time representing the end of the year (31st of december, 23:59:59)
+ # Returns a new Time representing the end of the year (end of the 31st of december)
def end_of_year
- change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59)
+ change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999)
end
alias :at_end_of_year :end_of_year
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index b9eb539aa7..deb29a82b8 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -11,15 +11,15 @@ module ActiveSupport
method_names.each do |method_name|
target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1)
- def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block)
- ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn(
- ::ActiveSupport::Deprecation.deprecated_method_warning( # ::ActiveSupport::Deprecation.deprecated_method_warning(
- :#{method_name}, # :generate_secret,
- #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"),
- caller # caller
- ) # )
- #{target}_without_deprecation#{punctuation}(*args, &block) # generate_secret_without_deprecation(*args, &block)
- end # end
+ def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block)
+ ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn(
+ ::ActiveSupport::Deprecation.deprecated_method_warning( # ::ActiveSupport::Deprecation.deprecated_method_warning(
+ :#{method_name}, # :generate_secret,
+ #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"),
+ caller # caller
+ ) # )
+ send(:#{target}_without_deprecation#{punctuation}, *args, &block) # send(:generate_secret_without_deprecation, *args, &block)
+ end # end
end_eval
end
end
diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb
index 667016f45d..92dd31cfbc 100644
--- a/activesupport/lib/active_support/json/backends/yaml.rb
+++ b/activesupport/lib/active_support/json/backends/yaml.rb
@@ -34,11 +34,9 @@ module ActiveSupport
pos = scanner.pos
elsif quoting == char
if json[pos..scanner.pos-2] =~ DATE_REGEX
- # found a date, track the exact positions of the quotes so we can remove them later.
- # oh, and increment them for each current mark, each one is an extra padded space that bumps
- # the position in the final YAML output
- total_marks = marks.size
- times << pos+total_marks << scanner.pos+total_marks
+ # found a date, track the exact positions of the quotes so we can
+ # overwrite them with spaces later.
+ times << pos << scanner.pos
end
quoting = false
end
@@ -64,7 +62,12 @@ module ActiveSupport
output = []
left_pos.each_with_index do |left, i|
scanner.pos = left.succ
- output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
+ chunk = scanner.peek(right_pos[i] - scanner.pos + 1)
+ # overwrite the quotes found around the dates with spaces
+ while times.size > 0 && times[0] <= right_pos[i]
+ chunk[times.shift - scanner.pos - 1] = ' '
+ end
+ chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do
ustr = $1
if ustr.start_with?('u')
[ustr[1..-1].to_i(16)].pack("U")
@@ -74,10 +77,10 @@ module ActiveSupport
ustr
end
end
+ output << chunk
end
output = output * " "
- times.each { |i| output[i-1] = ' ' }
output.gsub!(/\\\//, '/')
output
end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 7fd551eaf3..8a7bae5fc6 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -196,7 +196,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase
end
def test_end_of_day
- assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day
+ assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day
end
def test_xmlschema
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index 885393815b..66507d4652 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/core_ext/array'
+require 'active_support/core_ext/symbol'
require 'active_support/core_ext/enumerable'
Payment = Struct.new(:price)
@@ -60,6 +61,14 @@ class EnumerableTests < Test::Unit::TestCase
assert_equal Payment.new(0), [].sum(Payment.new(0))
end
+ def test_enumerable_sums
+ assert_equal 20, (1..4).sum { |i| i * 2 }
+ assert_equal 10, (1..4).sum
+ assert_equal 6, (1...4).sum
+ assert_equal 'abc', ('a'..'c').sum
+ assert_raises(NoMethodError) { 1..2.5.sum }
+ end
+
def test_each_with_object
result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result)
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index a23d3f6fef..1005a7e7ad 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -345,3 +345,10 @@ class TestGetTextString < Test::Unit::TestCase
assert_raises(ArgumentError) { "%{name} %f" % [1.0, 2.0] }
end
end
+
+class StringBytesizeTest < Test::Unit::TestCase
+ def test_bytesize
+ assert_respond_to 'foo', :bytesize
+ assert_equal 3, 'foo'.bytesize
+ end
+end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index 1c2d0fbce4..f6003bc083 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -85,45 +85,45 @@ class TimeExtCalculationsTest < Test::Unit::TestCase
end
def test_end_of_day
- assert_equal Time.local(2007,8,12,23,59,59), Time.local(2007,8,12,10,10,10).end_of_day
+ assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day
with_env_tz 'US/Eastern' do
- assert_equal Time.local(2007,4,2,23,59,59), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
- assert_equal Time.local(2007,10,29,23,59,59), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST'
end
with_env_tz 'NZ' do
- assert_equal Time.local(2006,3,19,23,59,59), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
- assert_equal Time.local(2006,10,1,23,59,59), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
+ assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
+ assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
end
end
def test_end_of_week
- assert_equal Time.local(2008,1,6,23,59,59), Time.local(2007,12,31,10,10,10).end_of_week
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,27,0,0,0).end_of_week #monday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,30,0,0,0).end_of_week #thursday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,31,0,0,0).end_of_week #friday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,01,0,0,0).end_of_week #saturday
- assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,02,0,0,0).end_of_week #sunday
+ assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday
+ assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday
end
def test_end_of_month
- assert_equal Time.local(2005,3,31,23,59,59), Time.local(2005,3,20,10,10,10).end_of_month
- assert_equal Time.local(2005,2,28,23,59,59), Time.local(2005,2,20,10,10,10).end_of_month
- assert_equal Time.local(2005,4,30,23,59,59), Time.local(2005,4,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month
+ assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month
end
def test_end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,2,15,10,10,10).end_of_quarter
- assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,3,31,0,0,0).end_of_quarter
- assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,21,10,10,10).end_of_quarter
- assert_equal Time.local(2007,6,30,23,59,59), Time.local(2007,4,1,0,0,0).end_of_quarter
- assert_equal Time.local(2008,6,30,23,59,59), Time.local(2008,5,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter
+ assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter
+ assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter
+ assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter
end
def test_end_of_year
- assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,2,22,10,10,10).end_of_year
- assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,31,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year
+ assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year
end
def test_beginning_of_year
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 73a1f9959c..a3ae39d071 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -25,6 +25,9 @@ class Deprecatee
def e; end
deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one"
+ def f=(v); end
+ deprecate :f=
+
module B
C = 1
end
@@ -133,6 +136,7 @@ class DeprecationTest < ActiveSupport::TestCase
def test_deprecation_without_explanation
assert_deprecated { @dtc.a }
assert_deprecated { @dtc.b }
+ assert_deprecated { @dtc.f = :foo }
end
def test_deprecation_with_alternate_method
diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb
index 09fd0d09ba..4129a4fab6 100644
--- a/activesupport/test/json/decoding_test.rb
+++ b/activesupport/test/json/decoding_test.rb
@@ -33,7 +33,13 @@ class TestJSONDecoding < ActiveSupport::TestCase
%q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"},
%q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"},
%q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"},
- %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]}
+ %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]},
+ # test combination of dates and escaped or unicode encoded data in arrays
+ %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) =>
+ [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}],
+ %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) =>
+ [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'},
+ {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}]
}
# load the default JSON backend
diff --git a/railties/lib/commands/server.rb b/railties/lib/commands/server.rb
index 01dd33fa8c..823916b1dc 100644
--- a/railties/lib/commands/server.rb
+++ b/railties/lib/commands/server.rb
@@ -3,13 +3,6 @@ require 'action_controller'
require 'fileutils'
require 'optparse'
-# TODO: Push Thin adapter upstream so we don't need worry about requiring it
-begin
- require_library_or_gem 'thin'
-rescue Exception
- # Thin not available
-end
-
options = {
:Port => 3000,
:Host => "0.0.0.0",
diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb
index 64ec808ee4..c97c61507a 100644
--- a/railties/lib/generators.rb
+++ b/railties/lib/generators.rb
@@ -11,7 +11,7 @@ end
$:.unshift(File.dirname(__FILE__))
-require 'vendor/thor-0.11.3/lib/thor'
+require 'vendor/thor-0.11.5/lib/thor'
require 'generators/base'
require 'generators/named_base'
@@ -83,6 +83,34 @@ module Rails
@@options ||= DEFAULT_OPTIONS.dup
end
+ # Get paths only from loaded rubygems. In other words, to use rspec
+ # generators, you first have to ensure that rspec gem was already loaded.
+ #
+ def self.rubygems_generators_paths
+ paths = []
+ return paths unless defined?(Gem)
+
+ Gem.loaded_specs.each do |name, spec|
+ generator_path = File.join(spec.full_gem_path, "lib/generators")
+ paths << generator_path if File.exist?(generator_path)
+ end
+
+ paths
+ end
+
+ # If RAILS_ROOT is defined, add vendor/gems, vendor/plugins and lib/generators
+ # paths.
+ #
+ def self.rails_root_generators_paths
+ paths = []
+ if defined?(RAILS_ROOT)
+ paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")]
+ paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
+ paths << File.join(RAILS_ROOT, "lib", "generators")
+ end
+ paths
+ end
+
# Hold configured generators fallbacks. If a plugin developer wants a
# generator group to fallback to another group in case of missing generators,
# they can add a fallback.
@@ -108,30 +136,25 @@ module Rails
# Generators load paths used on lookup. The lookup happens as:
#
- # 1) builtin generators
- # 2) frozen gems generators
- # 3) rubygems gems generators (not available yet)
- # 4) plugin generators
- # 5) lib generators
- # 6) ~/rails/generators
+ # 1) lib generators
+ # 2) vendor/plugin generators
+ # 3) vendor/gems generators
+ # 4) ~/rails/generators
+ # 5) rubygems generators
+ # 6) builtin generators
#
- # TODO Add Rubygems generators (depends on dependencies system rework)
# TODO Remove hardcoded paths for all, except (1).
#
- def self.load_path
- @@load_path ||= begin
- paths = []
- paths << File.expand_path(File.join(File.dirname(__FILE__), "generators"))
- if defined?(RAILS_ROOT)
- paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "*", "lib", "generators")]
- paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
- paths << File.join(RAILS_ROOT, "lib", "generators")
- end
+ def self.load_paths
+ @@load_paths ||= begin
+ paths = self.rails_root_generators_paths
paths << File.join(Thor::Util.user_home, ".rails", "generators")
+ paths += self.rubygems_generators_paths
+ paths << File.expand_path(File.join(File.dirname(__FILE__), "generators"))
paths
end
end
- load_path # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths.
+ load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths.
# Receives a namespace and tries different combinations to find a generator.
#
@@ -204,8 +227,8 @@ module Rails
puts "Builtin: #{rails.join(', ')}."
# Load paths and remove builtin
- paths, others = load_path.dup, []
- paths.shift
+ paths, others = load_paths.dup, []
+ paths.pop
paths.each do |path|
tail = [ "*", "*", "*_generator.rb" ]
@@ -242,7 +265,6 @@ module Rails
#
def self.invoke_fallbacks_for(name, base)
return nil unless base && fallbacks[base.to_sym]
-
invoked_fallbacks = []
Array(fallbacks[base.to_sym]).each do |fallback|
@@ -283,7 +305,7 @@ module Rails
generators_path.uniq!
generators_path = "{#{generators_path.join(',')}}"
- self.load_path.each do |path|
+ self.load_paths.each do |path|
Dir[File.join(path, generators_path, name)].each do |file|
begin
require file
diff --git a/railties/lib/generators/actions.rb b/railties/lib/generators/actions.rb
index 55ef212abb..03d0d11a07 100644
--- a/railties/lib/generators/actions.rb
+++ b/railties/lib/generators/actions.rb
@@ -5,22 +5,31 @@ module Rails
module Actions
# Install a plugin. You must provide either a Subversion url or Git url.
- # For a Git-hosted plugin, you can specify if it should be added as a submodule instead of cloned.
+ #
+ # For a Git-hosted plugin, you can specify a branch and
+ # whether it should be added as a submodule instead of cloned.
+ #
+ # For a Subversion-hosted plugin you can specify a revision.
#
# ==== Examples
#
# plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
+ # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :branch => 'stable'
# plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true
# plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
+ # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk', :revision => 1234
#
def plugin(name, options)
log :plugin, name
if options[:git] && options[:submodule]
+ options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch]
in_root do
run "git submodule add #{options[:git]} vendor/plugins/#{name}", :verbose => false
end
elsif options[:git] || options[:svn]
+ options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch]
+ options[:svn] = "-r #{options[:revision]} #{options[:svn]}" if options[:revision]
in_root do
run_ruby_script "script/plugin install #{options[:svn] || options[:git]}", :verbose => false
end
diff --git a/railties/lib/generators/action_orm.rb b/railties/lib/generators/active_model.rb
index 69cf227fd7..1a849a0e02 100644
--- a/railties/lib/generators/action_orm.rb
+++ b/railties/lib/generators/active_model.rb
@@ -1,6 +1,6 @@
module Rails
module Generators
- # ActionORM is a class to be implemented by each ORM to allow Rails to
+ # ActiveModel is a class to be implemented by each ORM to allow Rails to
# generate customized controller code.
#
# The API has the same methods as ActiveRecord, but each method returns a
@@ -8,22 +8,22 @@ module Rails
#
# For example:
#
- # ActiveRecord::Generators::ActionORM.find(Foo, "params[:id]")
+ # ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]")
# #=> "Foo.find(params[:id])"
#
- # Datamapper::Generators::ActionORM.find(Foo, "params[:id]")
+ # Datamapper::Generators::ActiveModel.find(Foo, "params[:id]")
# #=> "Foo.get(params[:id])"
#
- # On initialization, the ActionORM accepts the instance name that will
+ # On initialization, the ActiveModel accepts the instance name that will
# receive the calls:
#
- # builder = ActiveRecord::Generators::ActionORM.new "@foo"
+ # builder = ActiveRecord::Generators::ActiveModel.new "@foo"
# builder.save #=> "@foo.save"
#
- # The only exception in ActionORM for ActiveRecord is the use of self.build
+ # The only exception in ActiveModel for ActiveRecord is the use of self.build
# instead of self.new.
#
- class ActionORM
+ class ActiveModel
attr_reader :name
def initialize(name)
diff --git a/railties/lib/generators/active_record.rb b/railties/lib/generators/active_record.rb
index 64bee3904e..924b70881a 100644
--- a/railties/lib/generators/active_record.rb
+++ b/railties/lib/generators/active_record.rb
@@ -1,6 +1,6 @@
require 'generators/named_base'
require 'generators/migration'
-require 'generators/action_orm'
+require 'generators/active_model'
require 'active_record'
module ActiveRecord
@@ -20,7 +20,7 @@ module ActiveRecord
end
end
- class ActionORM < Rails::Generators::ActionORM #:nodoc:
+ class ActiveModel < Rails::Generators::ActiveModel #:nodoc:
def self.all(klass)
"#{klass}.all"
end
diff --git a/railties/lib/generators/active_record/model/model_generator.rb b/railties/lib/generators/active_record/model/model_generator.rb
index 54187aede0..2641083e0d 100644
--- a/railties/lib/generators/active_record/model/model_generator.rb
+++ b/railties/lib/generators/active_record/model/model_generator.rb
@@ -12,10 +12,8 @@ module ActiveRecord
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
def create_migration_file
- if options[:migration] && options[:parent].nil?
- file_name = "create_#{file_path.gsub(/\//, '_').pluralize}"
- migration_template "migration.rb", "db/migrate/#{file_name}.rb"
- end
+ return unless options[:migration] && options[:parent].nil?
+ migration_template "migration.rb", "db/migrate/create_#{table_name}.rb"
end
def create_model_file
diff --git a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb
index d60da5c0a5..59c4792066 100644
--- a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb
+++ b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb
@@ -12,7 +12,11 @@ module ActiveRecord
protected
def session_table_name
- ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session'
+ current_table_name = ActiveRecord::SessionStore::Session.table_name
+ if ["sessions", "session"].include?(current_table_name)
+ current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session')
+ end
+ current_table_name
end
end
diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb
index 699b8ed651..cd7aa61b50 100644
--- a/railties/lib/generators/named_base.rb
+++ b/railties/lib/generators/named_base.rb
@@ -28,7 +28,6 @@ module Rails
else
singular_name
end
- @table_name.gsub! '/', '_'
if class_nesting.empty?
@class_name = class_name_without_nesting
@@ -36,6 +35,8 @@ module Rails
@table_name = class_nesting.underscore << "_" << @table_name
@class_name = "#{class_nesting}::#{class_name_without_nesting}"
end
+
+ @table_name.gsub!('/', '_')
end
# Convert attributes hash into an array with GeneratedAttribute objects.
@@ -124,18 +125,18 @@ module Rails
protected
- # Loads the ORM::Generators::ActionORM class. This class is responsable
+ # Loads the ORM::Generators::ActiveModel class. This class is responsable
# to tell scaffold entities how to generate an specific method for the
- # ORM. Check Rails::Generators::ActionORM for more information.
+ # ORM. Check Rails::Generators::ActiveModel for more information.
#
def orm_class
@orm_class ||= begin
# Raise an error if the class_option :orm was not defined.
unless self.class.class_options[:orm]
- raise "You need to have :orm as class option to invoke orm_class and orm_instance"
+ raise "You need to have :orm as class option to invoke orm_class and orm_instance"
end
- action_orm = "#{options[:orm].to_s.classify}::Generators::ActionORM"
+ action_orm = "#{options[:orm].to_s.classify}::Generators::ActiveModel"
# If the orm was not loaded, try to load it at "generators/orm",
# for example "generators/active_record" or "generators/sequel".
@@ -152,7 +153,7 @@ module Rails
end
end
- # Initialize ORM::Generators::ActionORM to access instance methods.
+ # Initialize ORM::Generators::ActiveModel to access instance methods.
#
def orm_instance(name=file_name)
@orm_instance ||= @orm_class.new(name)
diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb
index c8044d13b1..c80a344e0d 100644
--- a/railties/lib/generators/rails/app/app_generator.rb
+++ b/railties/lib/generators/rails/app/app_generator.rb
@@ -49,7 +49,7 @@ module Rails::Generators
self.destination_root = File.expand_path(app_path, destination_root)
empty_directory '.'
- app_name # Sets the app name
+ set_default_accessors!
FileUtils.cd(destination_root)
end
@@ -164,9 +164,9 @@ module Rails::Generators
end
def apply_rails_template
- apply options[:template] if options[:template]
+ apply rails_template if rails_template
rescue Thor::Error, LoadError, Errno::ENOENT => e
- raise Error, "The template [#{options[:template]}] could not be loaded. Error: #{e}"
+ raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}"
end
def freeze?
@@ -175,6 +175,21 @@ module Rails::Generators
protected
+ attr_accessor :rails_template
+
+ def set_default_accessors!
+ app_name # Cache app name
+
+ self.rails_template = case options[:template]
+ when /^http:\/\//
+ options[:template]
+ when String
+ File.expand_path(options[:template], Dir.pwd)
+ else
+ options[:template]
+ end
+ end
+
# Define file as an alias to create_file for backwards compatibility.
#
def file(*args, &block)
diff --git a/railties/lib/generators/rails/app/templates/config/boot.rb b/railties/lib/generators/rails/app/templates/config/boot.rb
index 0ad0f787f8..dd5e3b6916 100644
--- a/railties/lib/generators/rails/app/templates/config/boot.rb
+++ b/railties/lib/generators/rails/app/templates/config/boot.rb
@@ -82,8 +82,8 @@ module Rails
end
def load_rubygems
+ min_version = '1.3.2'
require 'rubygems'
- min_version = '1.3.1'
unless rubygems_version >= min_version
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
exit 1
diff --git a/railties/lib/generators/rails/app/templates/config/environment.rb b/railties/lib/generators/rails/app/templates/config/environment.rb
index 94c90475c6..adb3a3060a 100644
--- a/railties/lib/generators/rails/app/templates/config/environment.rb
+++ b/railties/lib/generators/rails/app/templates/config/environment.rb
@@ -45,9 +45,9 @@ Rails::Initializer.run do |config|
# Configure generators values. Many other options are available, be sure to
# check the documentation.
- config.generators do |g|
- g.orm :active_record
- g.template_engine :erb
- g.test_framework :test_unit, :fixture => true
- end
+ # config.generators do |g|
+ # g.orm :active_record
+ # g.template_engine :erb
+ # g.test_framework :test_unit, :fixture => true
+ # end
end
diff --git a/railties/lib/generators/rails/mailer/templates/mailer.rb b/railties/lib/generators/rails/mailer/templates/mailer.rb
index ce15ae9de9..90e0b712d6 100644
--- a/railties/lib/generators/rails/mailer/templates/mailer.rb
+++ b/railties/lib/generators/rails/mailer/templates/mailer.rb
@@ -1,5 +1,4 @@
class <%= class_name %> < ActionMailer::Base
-
<% for action in actions -%>
def <%= action %>(sent_at = Time.now)
@@ -10,6 +9,6 @@ class <%= class_name %> < ActionMailer::Base
body :greeting => 'Hi,'
end
-<% end -%>
+<% end -%>
end
diff --git a/railties/lib/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/generators/test_unit/plugin/templates/test_helper.rb
index cf148b8b47..348ec33582 100644
--- a/railties/lib/generators/test_unit/plugin/templates/test_helper.rb
+++ b/railties/lib/generators/test_unit/plugin/templates/test_helper.rb
@@ -1,3 +1,5 @@
require 'rubygems'
+require 'test/unit'
require 'active_support'
-require 'active_support/test_case' \ No newline at end of file
+require 'active_support/test_case'
+
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index bb04dfa370..336bff9534 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -244,7 +244,6 @@ module Rails
if RUBY_VERSION < '1.9'
$KCODE='u'
else
- Encoding.default_internal = Encoding::UTF_8
Encoding.default_external = Encoding::UTF_8
end
end
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index fe3cb67d3a..5cc4f80684 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -71,7 +71,8 @@ module Rails
@paths.lib "lib", :load_path => true
@paths.vendor "vendor", :load_path => true
@paths.vendor.plugins "vendor/plugins"
- @paths.cache "tmp/cache"
+ @paths.tmp "tmp"
+ @paths.tmp.cache "tmp/cache"
@paths.config "config"
@paths.config.locales "config/locales"
@paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb"
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index 0d4d658315..b82341ba94 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -101,8 +101,12 @@ namespace :db do
ActiveRecord::Base.configurations.each_value do |config|
# Skip entries that don't have a database key
next unless config['database']
- # Only connect to local databases
- local_database?(config) { drop_database(config) }
+ begin
+ # Only connect to local databases
+ local_database?(config) { drop_database(config) }
+ rescue Exception => e
+ puts "Couldn't drop #{config['database']} : #{e.inspect}"
+ end
end
end
end
@@ -172,6 +176,13 @@ namespace :db do
Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
end
+ desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n'
+ task :forward => :environment do
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
+ ActiveRecord::Migrator.forward('db/migrate/', step)
+ Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ end
+
desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
task :reset => [ 'db:drop', 'db:setup' ]
@@ -429,7 +440,11 @@ def drop_database(config)
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/
- FileUtils.rm(File.join(RAILS_ROOT, config['database']))
+ require 'pathname'
+ path = Pathname.new(config['database'])
+ file = path.absolute? ? path.to_s : File.join(RAILS_ROOT, path)
+
+ FileUtils.rm(file)
when 'postgresql'
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
ActiveRecord::Base.connection.drop_database config['database']
@@ -437,7 +452,7 @@ def drop_database(config)
end
def session_table_name
- ActiveRecord::Base.pluralize_table_names ? :sessions : :session
+ ActiveRecord::SessionStore::Session.table_name
end
def set_firebird_env(config)
diff --git a/railties/lib/tasks/routes.rake b/railties/lib/tasks/routes.rake
index 39b7139167..abbf3258c1 100644
--- a/railties/lib/tasks/routes.rake
+++ b/railties/lib/tasks/routes.rake
@@ -1,6 +1,7 @@
-desc 'Print out all defined routes in match order, with names.'
+desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.'
task :routes => :environment do
- routes = ActionController::Routing::Routes.routes.collect do |route|
+ all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes
+ routes = all_routes.collect do |route|
name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s
verb = route.conditions[:method].to_s.upcase
segs = route.segments.inject("") { |str,s| str << s.to_s }
@@ -14,4 +15,4 @@ task :routes => :environment do
routes.each do |r|
puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}"
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb
deleted file mode 100644
index c846e9ce74..0000000000
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-class Thor
- # Thor::Error is raised when it's caused by the user invoking the task and
- # only errors that inherit from it are rescued.
- #
- # So, for example, if the developer declares a required argument after an
- # option, it should raise an ::ArgumentError and not ::Thor::ArgumentError,
- # because it was caused by the developer and not the "final user".
- #
- class Error < StandardError #:nodoc:
- end
-
- # Raised when a task was not found.
- #
- class UndefinedTaskError < Error #:nodoc:
- end
-
- # Raised when a task was found, but not invoked properly.
- #
- class InvocationError < Error #:nodoc:
- end
-
- class RequiredArgumentMissingError < InvocationError #:nodoc:
- end
-
- class MalformattedArgumentError < InvocationError #:nodoc:
- end
-end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb
deleted file mode 100644
index d1a7b1c673..0000000000
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# This only loads all tasks inside tasks.
-Dir[File.join(File.dirname(__FILE__), "tasks", "*.rb")].each do |task|
- require task
-end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb
deleted file mode 100644
index 6b20ff1634..0000000000
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-class Thor
- # Creates an install task.
- #
- # ==== Parameters
- # spec<Gem::Specification>
- #
- # ==== Options
- # :dir - The directory where the package is hold before installation. Defaults to ./pkg.
- #
- def self.install_task(spec, options={})
- package_task(spec, options)
- tasks['install'] = Thor::InstallTask.new(spec, options)
- end
-
- class InstallTask < Task
- attr_accessor :spec, :config
-
- def initialize(gemspec, config={})
- super(:install, "Install the gem", "install", {})
- @spec = gemspec
- @config = { :dir => File.join(Dir.pwd, "pkg") }.merge(config)
- end
-
- def run(instance, args=[])
- null, sudo, gem = RUBY_PLATFORM =~ /mswin|mingw/ ? ['NUL', '', 'gem.bat'] :
- ['/dev/null', 'sudo', 'gem']
-
- old_stderr, $stderr = $stderr.dup, File.open(null, "w")
- instance.invoke(:package)
- $stderr = old_stderr
-
- system %{#{sudo} #{Gem.ruby} -S #{gem} install #{config[:dir]}/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
- end
- end
-end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb
deleted file mode 100644
index 603d61b4ab..0000000000
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require "fileutils"
-
-class Thor
- # Creates a package task.
- #
- # ==== Parameters
- # spec<Gem::Specification>
- #
- # ==== Options
- # :dir - The package directory. Defaults to ./pkg.
- #
- def self.package_task(spec, options={})
- tasks['package'] = Thor::PackageTask.new(spec, options)
- end
-
- class PackageTask < Task
- attr_accessor :spec, :config
-
- def initialize(gemspec, config={})
- super(:package, "Build a gem package", "package", {})
- @spec = gemspec
- @config = {:dir => File.join(Dir.pwd, "pkg")}.merge(config)
- end
-
- def run(instance, args=[])
- FileUtils.mkdir_p(config[:dir])
- Gem::Builder.new(spec).build
- FileUtils.mv(spec.file_name, File.join(config[:dir], spec.file_name))
- end
- end
-end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb
deleted file mode 100644
index c7d00968e8..0000000000
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require "fileutils"
-
-class Thor
- # Creates a spec task.
- #
- # ==== Parameters
- # files<Array> - Array of files to spec
- #
- # ==== Options
- # :name - The name of the task. It can be rcov or spec. Spec is the default.
- # :rcov - A hash with rcov specific options.
- # :rcov_dir - Where rcov reports should be printed.
- # :verbose - Sets the default value for verbose, although it can be specified
- # also through the command line.
- #
- # All other options are added to rspec.
- #
- def self.spec_task(files, options={})
- name = (options.delete(:name) || 'spec').to_s
- tasks[name] = Thor::SpecTask.new(name, files, options)
- end
-
- class SpecTask < Task
- attr_accessor :name, :files, :rcov_dir, :rcov_config, :spec_config
-
- def initialize(name, files, config={})
- options = { :verbose => Thor::Option.parse(:verbose, config.delete(:verbose) || false) }
- super(name, "#{name.capitalize} task", name, options)
-
- @name = name
- @files = files.map{ |f| %["#{f}"] }.join(" ")
- @rcov_dir = config.delete(:rdoc_dir) || File.join(Dir.pwd, 'coverage')
- @rcov_config = config.delete(:rcov) || {}
- @spec_config = { :format => 'specdoc', :color => true }.merge(config)
- end
-
- def run(instance, args=[])
- rcov_opts = Thor::Options.to_switches(rcov_config)
- spec_opts = Thor::Options.to_switches(spec_config)
-
- require 'rbconfig'
- cmd = RbConfig::CONFIG['ruby_install_name'] << " "
-
- if rcov?
- FileUtils.rm_rf(rcov_dir)
- cmd << "-S #{where('rcov')} -o #{rcov_dir} #{rcov_opts} "
- end
-
- cmd << [where('spec'), rcov? ? " -- " : nil, files, spec_opts].join(" ")
-
- puts cmd if instance.options.verbose?
- system(cmd)
- exit($?.exitstatus)
- end
-
- private
-
- def rcov?
- name == "rcov"
- end
-
- def where(file)
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
- file_with_path = File.join(path, file)
- next unless File.exist?(file_with_path) && File.executable?(file_with_path)
- return File.expand_path(file_with_path)
- end
- end
- end
-end
diff --git a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc
index 544dde8c02..dba25b7205 100644
--- a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc
+++ b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc
@@ -1,9 +1,11 @@
== TODO
* Improve spec coverage for Thor::Runner
-* Improve help output to list shorthand switches, too
-== Current
+== 0.11.x, released 2009-07-01
+
+* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
+ Thor classes.
* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
since it wrong behavior to the invocation system.
diff --git a/railties/lib/vendor/thor-0.11.3/LICENSE b/railties/lib/vendor/thor-0.11.5/LICENSE
index 98722da459..98722da459 100644
--- a/railties/lib/vendor/thor-0.11.3/LICENSE
+++ b/railties/lib/vendor/thor-0.11.5/LICENSE
diff --git a/railties/lib/vendor/thor-0.11.3/README.markdown b/railties/lib/vendor/thor-0.11.5/README.rdoc
index a1d7259775..f1106f02b6 100644
--- a/railties/lib/vendor/thor-0.11.3/README.markdown
+++ b/railties/lib/vendor/thor-0.11.5/README.rdoc
@@ -1,5 +1,4 @@
-thor
-====
+= thor
Map options to a class. Simply create a class with the appropriate annotations
and have options automatically map to functions and parameters.
@@ -34,26 +33,18 @@ That gets converted to:
App.new.install("myname")
# with {'force' => true} as options hash
-1. Inherit from Thor to turn a class into an option mapper
-2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
-3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description
-4. Provide any additional options that will be available the instance method options.
-
-Types for `method_options`
---------------------------
-
-<dl>
- <dt><code>:boolean</code></dt>
- <dd>is parsed as --option or --option=true</dd>
- <dt><code>:string</code></dt>
- <dd>is parsed as --option=VALUE</dd>
- <dt><code>:numeric</code></dt>
- <dd>is parsed as --option=N</dd>
- <dt><code>:array</code></dt>
- <dd>is parsed as --option=one two three</dd>
- <dt><code>:hash</code></dt>
- <dd>is parsed as --option=key:value key:value key:value</dd>
-</dl>
+1. Inherit from Thor to turn a class into an option mapper
+2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
+3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description
+4. Provide any additional options that will be available the instance method options.
+
+== Types for <tt>method_options</tt>
+
+* :boolean - is parsed as <tt>--option</tt> or <tt>--option=true</tt>
+* :string - is parsed as <tt>--option=VALUE</tt>
+* :numeric - is parsed as <tt>--option=N</tt>
+* :array - is parsed as <tt>--option=one two three</tt>
+* :hash - is parsed as <tt>--option=name:string age:integer</tt>
Besides, method_option allows a default value to be given, examples:
@@ -66,9 +57,9 @@ Besides, method_option allows a default value to be given, examples:
method_options :threshold => 3.0
#=> Creates a numeric option with default value 3.0
-You can also supply :option => :required to mark an option as required. The
+You can also supply <tt>:option => :required</tt> to mark an option as required. The
type is assumed to be string. If you want a required hash with default values
-as option, you can use `method_option` which uses a more declarative style:
+as option, you can use <tt>method_option</tt> which uses a more declarative style:
method_option :attributes, :type => :hash, :default => {}, :required => true
@@ -91,8 +82,7 @@ You can supply as many aliases as you want.
NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead.
-Namespaces
-----------
+== Namespaces
By default, your Thor tasks are invoked using Ruby namespace. In the example
above, tasks are invoked as:
@@ -124,8 +114,7 @@ And then your tasks hould be invoked as:
thor myapp:install name --force
-Invocations
------------
+== Invocations
Thor comes with a invocation-dependency system as well which allows a task to be
invoked only once. For example:
@@ -158,8 +147,7 @@ The output is "1 2 3", which means that the three task was invoked only once.
You can even invoke tasks from another class, so be sure to check the
documentation.
-Thor::Group
------------
+== Thor::Group
Thor has a special class called Thor::Group. The main difference to Thor class
is that it invokes all tasks at once. The example above could be rewritten in
@@ -185,10 +173,10 @@ When invoked:
thor counter
-It prints "1 2 3" as well. Notice you should described (desc) only the class
-and not each task anymore. Thor::Group is a great tool to create generators,
-since you can define several steps which are invoked in the order they are
-defined (Thor::Group is the tool use in generators in Rails 3.0).
+It prints "1 2 3" as well. Notice you should describe (using the method <tt>desc</tt>)
+only the class and not each task anymore. Thor::Group is a great tool to create
+generators, since you can define several steps which are invoked in the order they
+are defined (Thor::Group is the tool use in generators in Rails 3.0).
Besides, Thor::Group can parse arguments and options as Thor tasks:
@@ -218,17 +206,17 @@ The counter above expects one parameter and has the folling outputs:
thor counter 11
# Prints "11 12 13"
-You can also give options to Thor::Group, but instead of using `method_option` and
-`method_options`, you should use `class_option` and `class_options`. Both argument
-and class_options methods are available to Thor class as well.
+You can also give options to Thor::Group, but instead of using <tt>method_option</tt>
+and <tt>method_options</tt>, you should use <tt>class_option</tt> and <tt>class_options</tt>.
+Both argument and class_options methods are available to Thor class as well.
-Actions
--------
+== Actions
Thor comes with several actions which helps with script and generator tasks. You
-might be familiar with them since some came from Rails Templates. They are: `say`,
-`ask`, `yes?`, `no?`, `add_file`, `remove_file`, `copy_file`, `template`,
-`directory`, `inside`, `run`, `inject_into_file` and a couple more.
+might be familiar with them since some came from Rails Templates. They are:
+<tt>say</tt>, <tt>ask</tt>, <tt>yes?</tt>, <tt>no?</tt>, <tt>add_file</tt>,
+<tt>remove_file</tt>, <tt>copy_file</tt>, <tt>template</tt>, <tt>directory</tt>,
+<tt>inside</tt>, <tt>run</tt>, <tt>inject_into_file</tt> and a couple more.
To use them, you just need to include Thor::Actions in your Thor classes:
@@ -241,7 +229,6 @@ Some actions like copy file requires that a class method called source_root is
defined in your class. This is the directory where your templates should be
placed. Be sure to check the documentation.
-License
--------
+== License
See MIT LICENSE.
diff --git a/railties/lib/vendor/thor-0.11.3/bin/rake2thor b/railties/lib/vendor/thor-0.11.5/bin/rake2thor
index 50c7410d80..50c7410d80 100755
--- a/railties/lib/vendor/thor-0.11.3/bin/rake2thor
+++ b/railties/lib/vendor/thor-0.11.5/bin/rake2thor
diff --git a/railties/lib/vendor/thor-0.11.3/bin/thor b/railties/lib/vendor/thor-0.11.5/bin/thor
index eaf849fb4a..eaf849fb4a 100755
--- a/railties/lib/vendor/thor-0.11.3/bin/thor
+++ b/railties/lib/vendor/thor-0.11.5/bin/thor
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor.rb b/railties/lib/vendor/thor-0.11.5/lib/thor.rb
index f65455cdda..8dfcfd4c5b 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor.rb
@@ -101,8 +101,7 @@ class Thor
# :required - If the argument is required or not.
# :default - Default value for this argument. It cannot be required and have default values.
# :aliases - Aliases for this option.
- # :type - The type of the argument, can be :string, :hash, :array, :numeric, :boolean or :default.
- # Default accepts arguments as booleans (--switch) or as strings (--switch=VALUE).
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :group - The group for this options. Use by class options to output options in different levels.
# :banner - String to show on usage notes.
#
@@ -160,39 +159,43 @@ class Thor
raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
shell.say "Usage:"
- shell.say " #{banner(task, options[:namespace])}"
+ shell.say " #{banner(task, options[:namespace], false)}"
shell.say
- class_options_help(shell, "Class")
+ class_options_help(shell, "Class", :Method => task.options.map { |_, o| o })
shell.say task.description
else
list = (options[:short] ? tasks : all_tasks).map do |_, task|
- item = [ " " + banner(task, options[:namespace]) ]
- item << if task.short_description
- "\n # #{task.short_description}\n"
- else
- "\n"
- end
+ item = [ banner(task, options[:namespace]) ]
+ item << "# #{task.short_description}" if task.short_description
+ item << " "
end
+ options[:ident] ||= 2
if options[:short]
- shell.print_table(list)
+ shell.print_list(list, :ident => options[:ident])
else
shell.say "Tasks:"
- shell.print_table(list)
- class_options_help(shell, "Class")
+ shell.print_list(list, :ident => options[:ident])
end
+
+ Thor::Util.thor_classes_in(self).each do |subclass|
+ namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
+ subclass.help(shell, options.merge(:short => true, :namespace => namespace))
+ end
+
+ class_options_help(shell, "Class") unless options[:short]
end
end
protected
# The banner for this class. You can customize it if you are invoking the
- # thor class by another means which is not the Thor::Runner. It receives
- # the task that is going to be invoked and if the namespace should be
- # displayed.
+ # thor class by another ways which is not the Thor::Runner. It receives
+ # the task that is going to be invoked and a boolean which indicates if
+ # the namespace should be displayed as arguments.
#
- def banner(task, namespace=true) #:nodoc:
- task.formatted_usage(self, namespace)
+ def banner(task, namespace=true, show_options=true)
+ task.formatted_usage(self, namespace, show_options)
end
def baseclass #:nodoc:
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb
index b8cfde1940..1d09dc38ae 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb
@@ -43,13 +43,11 @@ class Thor
# 3) Parents source paths
#
def source_paths_for_search
- @source_paths_for_search ||= begin
- paths = []
- paths += self.source_paths
- paths << self.source_root if self.respond_to?(:source_root)
- paths += from_superclass(:source_paths, [])
- paths
- end
+ paths = []
+ paths += self.source_paths
+ paths << self.source_root if self.respond_to?(:source_root)
+ paths += from_superclass(:source_paths, [])
+ paths
end
end
@@ -60,8 +58,7 @@ class Thor
# It also accepts :force, :skip and :pretend to set the behavior
# and the respective option.
#
- # destination_root<String>:: The root directory needed for some actions. It's also known
- # as destination root.
+ # destination_root<String>:: The root directory needed for some actions.
#
def initialize(args=[], options={}, config={})
self.behavior = case config[:behavior].to_s
@@ -80,7 +77,7 @@ class Thor
# Wraps an action object and call it accordingly to the thor class behavior.
#
- def action(instance)
+ def action(instance) #:nodoc:
if behavior == :revoke
instance.revoke!
else
@@ -110,18 +107,23 @@ class Thor
remove_dot ? (path[2..-1] || '') : path
end
+ # Holds source paths in instance so they can be manipulated.
+ #
+ def source_paths
+ @source_paths ||= self.class.source_paths_for_search
+ end
+
# Receives a file or directory and search for it in the source paths.
#
def find_in_source_paths(file)
relative_root = relative_to_original_destination_root(destination_root, false)
- paths = self.class.source_paths_for_search
- paths.each do |source|
+ source_paths.each do |source|
source_file = File.expand_path(file, File.join(source, relative_root))
return source_file if File.exists?(source_file)
end
- if paths.empty?
+ if source_paths.empty?
raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " <<
"you can define a source_root in your class."
else
@@ -205,7 +207,7 @@ class Thor
end
say_status :run, desc, config.fetch(:verbose, true)
- `#{command}` unless options[:pretend]
+ system(command) unless options[:pretend]
end
# Executes a ruby script (taking into account WIN32 platform quirks).
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb
index 8f6badee27..8f6badee27 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb
index e33639f4e5..be5eb822ac 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb
@@ -3,7 +3,7 @@ require 'thor/actions/empty_directory'
class Thor
module Actions
- # Copies interactively the files from source directory to root directory.
+ # Copies recursively the files from source directory to root directory.
# If any of the files finishes with .tt, it's considered to be a template
# and is placed in the destination without the extension .tt. If any
# empty directory is found, it's copied and all .empty_directory files are
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb
index 03c1fe4af1..03c1fe4af1 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb
index 74c157ba8c..74c157ba8c 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb
index 089bd894e4..66dd1f5fc1 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb
@@ -34,7 +34,7 @@ class Thor
action InjectIntoFile.new(self, destination, data, config)
end
- class InjectIntoFile < EmptyDirectory
+ class InjectIntoFile < EmptyDirectory #:nodoc:
attr_reader :flag, :replacement
def initialize(base, destination, data, config)
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb
index 0bdcc1f4d5..0fa87f8162 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb
@@ -8,7 +8,10 @@ require 'thor/task'
require 'thor/util'
class Thor
+ # Shortcuts for help.
HELP_MAPPINGS = %w(-h -? --help -D)
+
+ # Thor methods that should not be overwritten by the user.
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
action add_file create_file in_root inside run run_ruby_script)
@@ -78,7 +81,6 @@ class Thor
# Whenever a class inherits from Thor or Thor::Group, we should track the
# class and the file on Thor::Base. This is the method responsable for it.
- # Also adds the source root to the source paths if the klass respond to it.
#
def register_klass_file(klass) #:nodoc:
file = caller[1].match(/(.*):\d+/)[1]
@@ -246,7 +248,8 @@ class Thor
# Returns the tasks for this Thor class.
#
# ==== Returns
- # OrderedHash:: An ordered hash with this class tasks.
+ # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # objects as values.
#
def tasks
@tasks ||= Thor::CoreExt::OrderedHash.new
@@ -255,7 +258,8 @@ class Thor
# Returns the tasks for this Thor class and all subclasses.
#
# ==== Returns
- # OrderedHash
+ # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # objects as values.
#
def all_tasks
@all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
@@ -334,7 +338,7 @@ class Thor
def namespace(name=nil)
case name
when nil
- @namespace ||= Thor::Util.constant_to_namespace(self, false)
+ @namespace ||= Thor::Util.namespace_from_thor_class(self, false)
else
@namespace = name.to_s
end
@@ -342,7 +346,7 @@ class Thor
# Default way to start generators from the command line.
#
- def start(given_args=ARGV, config={}) #:nodoc:
+ def start(given_args=ARGV, config={})
config[:shell] ||= Thor::Base.shell.new
yield
rescue Thor::Error => e
@@ -360,7 +364,7 @@ class Thor
# hooks to add extra options, one of them if the third argument called
# extra_group that should be a hash in the format :group => Array[Options].
#
- # The second is by returning a lamda used to print values. The lambda
+ # The second is by returning a lambda used to print values. The lambda
# requires two options: the group name and the array of options.
#
def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
@@ -377,24 +381,14 @@ class Thor
options.each do |option|
item = [ option.usage(padding) ]
-
- item << if option.description
- "# #{option.description}"
- else
- ""
- end
+ item.push(option.description ? "# #{option.description}" : "")
list << item
list << [ "", "# Default: #{option.default}" ] if option.show_default?
end
unless list.empty?
- if group_name
- shell.say "#{group_name} options:"
- else
- shell.say "Options:"
- end
-
+ shell.say(group_name ? "#{group_name} options:" : "Options:")
shell.print_table(list, :ident => 2)
shell.say ""
end
@@ -412,7 +406,7 @@ class Thor
# Raises an error if the word given is a Thor reserved word.
#
- def is_thor_reserved_word?(word, type)
+ def is_thor_reserved_word?(word, type) #:nodoc:
return false unless THOR_RESERVED_WORDS.include?(word.to_s)
raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
end
@@ -423,7 +417,7 @@ class Thor
# name<Symbol>:: The name of the argument.
# options<Hash>:: Described in both class_option and method_option.
#
- def build_option(name, options, scope)
+ def build_option(name, options, scope) #:nodoc:
scope[name] = Thor::Option.new(name, options[:desc], options[:required],
options[:type], options[:default], options[:banner],
options[:group], options[:aliases])
@@ -437,7 +431,7 @@ class Thor
# ==== Parameters
# Hash[Symbol => Object]
#
- def build_options(options, scope)
+ def build_options(options, scope) #:nodoc:
options.each do |key, value|
scope[key] = Thor::Option.parse(key, value)
end
@@ -447,7 +441,7 @@ class Thor
# class, just return it, otherwise dup it and add the fresh copy to the
# current task hash.
#
- def find_and_refresh_task(name)
+ def find_and_refresh_task(name) #:nodoc:
task = if task = tasks[name.to_s]
task
elsif task = all_tasks[name.to_s]
@@ -465,7 +459,7 @@ class Thor
end
# Fire this callback whenever a method is added. Added methods are
- # tracked as tasks if the requirements set by valid_task? are valid.
+ # tracked as tasks by invoking the create_task method.
#
def method_added(meth)
meth = meth.to_s
@@ -486,7 +480,7 @@ class Thor
end
# Retrieves a value from superclass. If it reaches the baseclass,
- # returns nil.
+ # returns default.
#
def from_superclass(method, default=nil)
if self == baseclass || !superclass.respond_to?(method, true)
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb
index 3213961fe4..78bc5cf4bf 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb
@@ -1,5 +1,5 @@
class Thor
- module CoreExt
+ module CoreExt #:nodoc:
# A hash with indifferent access and magic predicates.
#
@@ -9,7 +9,7 @@ class Thor
# hash['foo'] #=> 'bar'
# hash.foo? #=> true
#
- class HashWithIndifferentAccess < ::Hash
+ class HashWithIndifferentAccess < ::Hash #:nodoc:
def initialize(hash={})
super()
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb
index 5e4ad5609f..27fea5bb35 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb
@@ -1,6 +1,4 @@
-require 'forwardable'
-
-class Thor #:nodoc:
+class Thor
module CoreExt #:nodoc:
if RUBY_VERSION >= '1.9'
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb
new file mode 100644
index 0000000000..f9b31a35d1
--- /dev/null
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb
@@ -0,0 +1,27 @@
+class Thor
+ # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
+ # errors have their backtrace supressed and are nicely shown to the user.
+ #
+ # Errors that are caused by the developer, like declaring a method which
+ # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
+ # ensure that developer errors are shown with full backtrace.
+ #
+ class Error < StandardError
+ end
+
+ # Raised when a task was not found.
+ #
+ class UndefinedTaskError < Error
+ end
+
+ # Raised when a task was found, but not invoked properly.
+ #
+ class InvocationError < Error
+ end
+
+ class RequiredArgumentMissingError < InvocationError
+ end
+
+ class MalformattedArgumentError < InvocationError
+ end
+end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb
index 1be1c35ba5..1e59df2313 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb
@@ -221,9 +221,9 @@ class Thor::Group
protected
# The banner for this class. You can customize it if you are invoking the
- # thor class by another means which is not the Thor::Runner.
+ # thor class by another ways which is not the Thor::Runner.
#
- def banner #:nodoc:
+ def banner
"#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
end
@@ -244,7 +244,7 @@ class Thor::Group
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
#
- def _invoke_for_class_method(klass, task=nil, *args, &block)
+ def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
shell.padding += 1
result = if block_given?
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb
index 34e7a4b911..c0388dd863 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb
@@ -11,7 +11,7 @@ class Thor
def prepare_for_invocation(key, name) #:nodoc:
case name
when Symbol, String
- Thor::Util.namespace_to_thor_class(name.to_s, false)
+ Thor::Util.namespace_to_thor_class_and_task(name.to_s, false)
else
name
end
@@ -96,20 +96,8 @@ class Thor
task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array)
args, opts, config = nil, args, opts if args.is_a?(Hash)
- object, task = _prepare_for_invocation(name, task)
- if object.is_a?(Class)
- klass = object
-
- stored_args, stored_opts, stored_config = @_initializer
- args ||= stored_args.dup
- opts ||= stored_opts.dup
-
- config ||= {}
- config = stored_config.merge(_shared_configuration).merge!(config)
- instance = klass.new(args, opts, config)
- else
- klass, instance = object.class, object
- end
+ object, task = _prepare_for_invocation(name, task)
+ klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
method_args = []
current = @_invocations[klass]
@@ -134,7 +122,7 @@ class Thor
# Configuration values that are shared between invocations.
#
- def _shared_configuration
+ def _shared_configuration #:nodoc:
{ :invocations => @_invocations }
end
@@ -154,13 +142,13 @@ class Thor
# If the object was not set, use self and use the name as task.
object, task = self, name unless object
- return object, _validate_klass_and_task(object, task)
+ return object, _validate_task(object, task)
end
# Check if the object given is a Thor class object and get a task object
# for it.
#
- def _validate_klass_and_task(object, task) #:nodoc:
+ def _validate_task(object, task) #:nodoc:
klass = object.is_a?(Class) ? object : object.class
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
@@ -168,5 +156,23 @@ class Thor
task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task)
task
end
+
+ # Initialize klass using values stored in the @_initializer.
+ #
+ def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
+ if object.is_a?(Class)
+ klass = object
+
+ stored_args, stored_opts, stored_config = @_initializer
+ args ||= stored_args.dup
+ opts ||= stored_opts.dup
+
+ config ||= {}
+ config = stored_config.merge(_shared_configuration).merge!(config)
+ [ klass, klass.new(args, opts, config) ]
+ else
+ [ object.class, object ]
+ end
+ end
end
end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb
index 57a3f6e1a5..57a3f6e1a5 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb
index 2d7f4dbafb..aa8ace4719 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb
@@ -1,5 +1,5 @@
class Thor
- class Argument
+ class Argument #:nodoc:
VALID_TYPES = [ :numeric, :hash, :array, :string ]
attr_reader :name, :description, :required, :type, :default, :banner
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb
index 9a2262d6f7..fb5d965e06 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb
@@ -1,5 +1,5 @@
class Thor
- class Arguments
+ class Arguments #:nodoc:
NUMERIC = /(\d*\.\d+|\d+)/
# Receives an array of args and returns two arrays, one with arguments
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb
index 5c43f6b18f..9e40ec73fa 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb
@@ -1,5 +1,5 @@
class Thor
- class Option < Argument
+ class Option < Argument #:nodoc:
attr_reader :aliases, :group
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb
index 01c86b7b27..75092308b5 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb
@@ -2,7 +2,7 @@ class Thor
# This is a modified version of Daniel Berger's Getopt::Long class, licensed
# under Ruby's license.
#
- class Options < Arguments
+ class Options < Arguments #:nodoc:
LONG_RE = /^(--\w+[-\w+]*)$/
SHORT_RE = /^(-[a-z])$/i
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb
new file mode 100644
index 0000000000..3ab6bb21f5
--- /dev/null
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb
@@ -0,0 +1,67 @@
+require 'rake'
+
+class Thor
+ # Adds a compatibility layer to your Thor classes which allows you to use
+ # rake package tasks. For example, to use rspec rake tasks, one can do:
+ #
+ # require 'thor/rake_compat'
+ #
+ # class Default < Thor
+ # include Thor::RakeCompat
+ #
+ # Spec::Rake::SpecTask.new(:spec) do |t|
+ # t.spec_opts = ['--options', "spec/spec.opts"]
+ # t.spec_files = FileList['spec/**/*_spec.rb']
+ # end
+ # end
+ #
+ module RakeCompat
+ def self.rake_classes
+ @rake_classes ||= []
+ end
+
+ def self.included(base)
+ # Hack. Make rakefile point to invoker, so rdoc task is generated properly.
+ Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1])
+ self.rake_classes << base
+ end
+ end
+end
+
+class Object #:nodoc:
+ alias :rake_task :task
+ alias :rake_namespace :namespace
+
+ def task(*args, &block)
+ task = rake_task(*args, &block)
+
+ if klass = Thor::RakeCompat.rake_classes.last
+ non_namespaced_name = task.name.split(':').last
+
+ description = non_namespaced_name
+ description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
+ description.strip!
+
+ klass.desc description, task.comment || non_namespaced_name
+ klass.class_eval <<-METHOD
+ def #{non_namespaced_name}(#{task.arg_names.join(', ')})
+ Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')})
+ end
+ METHOD
+ end
+
+ task
+ end
+
+ def namespace(name, &block)
+ if klass = Thor::RakeCompat.rake_classes.last
+ const_name = Thor::Util.camel_case(name.to_s).to_sym
+ klass.const_set(const_name, Class.new(Thor))
+ new_klass = klass.const_get(const_name)
+ Thor::RakeCompat.rake_classes << new_klass
+ end
+
+ rake_namespace(name, &block)
+ Thor::RakeCompat.rake_classes.pop
+ end
+end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb
index 6782c61dec..3639ac0aa9 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb
@@ -4,7 +4,7 @@ require 'yaml'
require 'digest/md5'
require 'pathname'
-class Thor::Runner < Thor
+class Thor::Runner < Thor #:nodoc:
map "-T" => :list, "-i" => :install, "-u" => :update
# Override Thor#help so it can give information about any class and any method.
@@ -12,8 +12,9 @@ class Thor::Runner < Thor
def help(meth=nil)
if meth && !self.respond_to?(meth)
initialize_thorfiles(meth)
- klass, task = Thor::Util.namespace_to_thor_class(meth)
- klass.start(["-h", task].compact, :shell => self.shell) # send mapping -h because it works with Thor::Group too
+ klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
+ # Send mapping -h because it works with Thor::Group too
+ klass.start(["-h", task].compact, :shell => self.shell)
else
super
end
@@ -25,12 +26,12 @@ class Thor::Runner < Thor
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
- klass, task = Thor::Util.namespace_to_thor_class(meth)
+ klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
args.unshift(task) if task
klass.start(args, :shell => shell)
end
- desc "install NAME", "Install a Thor file into your system tasks, optionally named for future updates"
+ desc "install NAME", "Install an optionally named Thor file into your system tasks"
method_options :as => :string, :relative => :boolean
def install(name)
initialize_thorfiles
@@ -76,7 +77,7 @@ class Thor::Runner < Thor
thor_yaml[as] = {
:filename => Digest::MD5.hexdigest(name + as),
:location => location,
- :namespaces => Thor::Util.namespaces_in_contents(contents, base)
+ :namespaces => Thor::Util.namespaces_in_content(contents, base)
}
save_yaml(thor_yaml)
@@ -130,8 +131,7 @@ class Thor::Runner < Thor
display_klasses(true, klasses)
end
- desc "list [SEARCH]",
- "List the available thor tasks (--substring means SEARCH anywhere in the namespace)"
+ desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean
def list(search="")
initialize_thorfiles
@@ -269,6 +269,10 @@ class Thor::Runner < Thor
end
unless klasses.empty?
+ klasses.dup.each do |klass|
+ klasses -= Thor::Util.thor_classes_in(klass)
+ end
+
klasses.each { |k| display_tasks(k) }
else
say "\033[1;34mNo Thor tasks available\033[0m"
@@ -285,7 +289,7 @@ class Thor::Runner < Thor
say shell.set_color(base, color, true)
say "-" * base.length
- klass.help(shell, :short => true, :namespace => true)
+ klass.help(shell, :short => true, :ident => 0, :namespace => true)
end
end
end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb
index 7ed4a24bfb..0d3f4d5951 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb
@@ -64,7 +64,7 @@ class Thor
# Allow shell to be shared between invocations.
#
- def _shared_configuration
+ def _shared_configuration #:nodoc:
super.merge!(:shell => self.shell)
end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb
index e294c87567..3c02e47c33 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb
@@ -11,9 +11,9 @@ class Thor
@base, @padding = nil, 0
end
- # Do not allow padding to be less than zero.
+ # Sets the output padding, not allowing less than zero values.
#
- def padding=(value) #:nodoc:
+ def padding=(value)
@padding = [0, value].max
end
@@ -52,7 +52,7 @@ class Thor
# in log_status, avoiding the message from being shown. If a Symbol is
# given in log_status, it's used as the color.
#
- def say_status(status, message, log_status=true) #:nodoc:
+ def say_status(status, message, log_status=true)
return if quiet? || log_status == false
spaces = " " * (padding + 1)
color = log_status.is_a?(Symbol) ? log_status : :green
@@ -80,17 +80,21 @@ class Thor
#
# ==== Parameters
# list<Array[String, String, ...]>
- # mode<Symbol>:: Can be :rows or :inline. Defaults to :rows.
#
- def print_list(list, mode=:rows)
+ # ==== Options
+ # mode:: Can be :rows or :inline. Defaults to :rows.
+ # ident:: Ident each item with the value given.
+ #
+ def print_list(list, options={})
return if list.empty?
- content = case mode
+ ident = " " * (options[:ident] || 0)
+ content = case options[:mode]
when :inline
last = list.pop
"#{list.join(", ")}, and #{last}"
else # rows
- list.join("\n")
+ ident + list.join("\n#{ident}")
end
$stdout.puts content
@@ -103,7 +107,6 @@ class Thor
#
# ==== Options
# ident<Integer>:: Ident the first column by ident value.
- # emphasize_last<Boolean>:: When true, add a different behavior to the last column.
#
def print_table(table, options={})
return if table.empty?
@@ -131,7 +134,7 @@ class Thor
#
# ==== Parameters
# destination<String>:: the destination file to solve conflicts
- # block<Proc>:: an optional proc that returns the value to be used in diff
+ # block<Proc>:: an optional block that returns the value to be used in diff
#
def file_collision(destination)
return true if @always_force
@@ -164,19 +167,20 @@ class Thor
# wrong, you can always raise an exception. If you raise a Thor::Error, it
# will be rescued and wrapped in the method below.
#
- def error(statement) #:nodoc:
+ def error(statement)
$stderr.puts statement
end
- # Apply color to the given string with optional bold.
+ # Apply color to the given string with optional bold. Disabled in the
+ # Thor::Shell::Basic class.
#
- def set_color(string, color, bold=false)
+ def set_color(string, color, bold=false) #:nodoc:
string
end
protected
- def is?(value)
+ def is?(value) #:nodoc:
value = value.to_s
if value.size == 1
@@ -186,7 +190,7 @@ class Thor
end
end
- def file_collision_help
+ def file_collision_help #:nodoc:
<<HELP
Y - yes, overwrite
n - no, do not overwrite
@@ -197,17 +201,17 @@ h - help, show this help
HELP
end
- def show_diff(destination, content)
+ def show_diff(destination, content) #:nodoc:
diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
temp.write content
temp.rewind
- say `#{diff_cmd} "#{destination}" "#{temp.path}"`
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
end
end
- def quiet?
+ def quiet? #:nodoc:
base && base.options[:quiet]
end
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb
index be7995146a..24704f7885 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb
@@ -2,7 +2,8 @@ require 'thor/shell/basic'
class Thor
module Shell
- # Set color in the output. Got color values from HighLine.
+ # Inherit from Thor::Shell::Basic and add set_color behavior. Check
+ # Thor::Shell::Basic to see all available methods.
#
class Color < Basic
# Embed in a String to clear all previous ANSI sequences.
@@ -44,9 +45,10 @@ class Thor
# Set the terminal's background ANSI color to white.
ON_WHITE = "\e[47m"
- # Set color by using a string or one of the defined constants. Based
- # on Highline implementation. CLEAR is automatically be embedded to
- # the end of the returned String.
+ # Set color by using a string or one of the defined constants. If a third
+ # option is set to true, it also adds bold to the string. This is based
+ # on Highline implementation and it automatically appends CLEAR to the end
+ # of the returned String.
#
def set_color(string, color, bold=false)
color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
@@ -59,7 +61,7 @@ class Thor
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
- def show_diff(destination, content)
+ def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
actual = File.read(destination).to_s.split("\n")
content = content.to_s.split("\n")
@@ -72,7 +74,7 @@ class Thor
end
end
- def output_diff_line(diff)
+ def output_diff_line(diff) #:nodoc:
case diff.action
when '-'
say "- #{diff.old_element.chomp}", :red, true
@@ -89,7 +91,7 @@ class Thor
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
- def diff_lcs_loaded?
+ def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/task.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb
index 92c0776c04..23d35b883c 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/task.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb
@@ -12,7 +12,7 @@ class Thor
super(name.to_s, description, usage, options || {})
end
- def initialize_copy(other)
+ def initialize_copy(other) #:nodoc:
super(other)
self.options = other.options.dup if other.options
end
@@ -36,11 +36,19 @@ class Thor
# Returns the formatted usage. If a class is given, the class arguments are
# injected in the usage.
#
- def formatted_usage(klass=nil, namespace=false)
+ def formatted_usage(klass=nil, namespace=false, show_options=true)
formatted = ''
- formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace
+
+ formatted = if namespace.is_a?(String)
+ "#{namespace}:"
+ elsif klass && namespace
+ "#{klass.namespace.gsub(/^default/,'')}:"
+ else
+ ""
+ end
+
formatted << formatted_arguments(klass)
- formatted << " #{formatted_options}"
+ formatted << " #{formatted_options}" if show_options
formatted.strip!
formatted
end
@@ -67,20 +75,20 @@ class Thor
# Given a target, checks if this class name is not a private/protected method.
#
- def public_method?(instance)
+ def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods
!(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9
end
# Clean everything that comes from the Thor gempath and remove the caller.
#
- def sans_backtrace(backtrace, caller)
+ def sans_backtrace(backtrace, caller) #:nodoc:
dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
saned = backtrace.reject { |frame| frame =~ dirname }
saned -= caller
end
- def parse_argument_error(instance, e, caller)
+ def parse_argument_error(instance, e, caller) #:nodoc:
backtrace = sans_backtrace(e.backtrace, caller)
if backtrace.empty? && e.message =~ /wrong number of arguments/
@@ -95,7 +103,7 @@ class Thor
end
end
- def parse_no_method_error(instance, e)
+ def parse_no_method_error(instance, e) #:nodoc:
if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
raise UndefinedTaskError, "The #{instance.class.namespace} namespace " <<
"doesn't have a '#{name}' task"
diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb
index 26db24aadb..4938dc4aca 100644
--- a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb
+++ b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb
@@ -1,14 +1,14 @@
require 'rbconfig'
class Thor
- module Sandbox; end
+ module Sandbox #:nodoc:
+ end
# This module holds several utilities:
#
# 1) Methods to convert thor namespaces to constants and vice-versa.
#
- # Thor::Utils.constant_to_namespace(Foo::Bar::Baz) #=> "foo:bar:baz"
- # Thor::Utils.namespace_to_constant("foo:bar:baz") #=> Foo::Bar::Baz
+ # Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
#
# 2) Loading thor files and sandboxing:
#
@@ -43,15 +43,15 @@ class Thor
# ==== Returns
# String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
#
- def self.constant_to_namespace(constant, remove_default=true)
+ def self.namespace_from_thor_class(constant, remove_default=true)
constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
constant = snake_case(constant).squeeze(":")
constant.gsub!(/^default/, '') if remove_default
constant
end
- # Given the contents, evaluate it inside the sandbox and returns the thor
- # classes defined in the sandbox.
+ # Given the contents, evaluate it inside the sandbox and returns the
+ # namespaces defined in the sandbox.
#
# ==== Parameters
# contents<String>
@@ -59,7 +59,7 @@ class Thor
# ==== Returns
# Array[Object]
#
- def self.namespaces_in_contents(contents, file=__FILE__)
+ def self.namespaces_in_content(contents, file=__FILE__)
old_constants = Thor::Base.subclasses.dup
Thor::Base.subclasses.clear
@@ -73,6 +73,14 @@ class Thor
new_constants
end
+ # Returns the thor classes declared inside the given class.
+ #
+ def self.thor_classes_in(klass)
+ Thor::Base.subclasses.select do |subclass|
+ klass.constants.include?(subclass.name.gsub("#{klass.name}::", ''))
+ end
+ end
+
# Receives a string and convert it to snake case. SnakeCase returns snake_case.
#
# ==== Parameters
@@ -87,6 +95,19 @@ class Thor
return $+.downcase
end
+ # Receives a string and convert it to camel case. camel_case returns CamelCase.
+ #
+ # ==== Parameters
+ # String
+ #
+ # ==== Returns
+ # String
+ #
+ def self.camel_case(str)
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
+ str.split('_').map { |i| i.capitalize }.join
+ end
+
# Receives a namespace and tries to retrieve a Thor or Thor::Group class
# from it. It first searches for a class using the all the given namespace,
# if it's not found, removes the highest entry and searches for the class
@@ -115,7 +136,7 @@ class Thor
# Thor::Error:: raised if the namespace evals to a class which does not
# inherit from Thor or Thor::Group.
#
- def self.namespace_to_thor_class(namespace, raise_if_nil=true)
+ def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
klass, task_name = Thor::Util.find_by_namespace(namespace), nil
if klass.nil? && namespace.include?(?:)
@@ -156,7 +177,7 @@ class Thor
yaml.each do |k, v|
next unless v[:constants] && v[:namespaces].nil?
yaml_changed = true
- yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.constant_to_namespace(c)}
+ yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)}
end
yaml_changed
@@ -214,7 +235,7 @@ class Thor
# Return the path to the ruby interpreter taking into account multiple
# installations and windows extensions.
#
- def self.ruby_command #:nodoc:
+ def self.ruby_command
@ruby_command ||= begin
ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
ruby << Config::CONFIG['EXEEXT']
diff --git a/railties/test/fixtures/lib/template.rb b/railties/test/fixtures/lib/template.rb
new file mode 100644
index 0000000000..c14a1a8784
--- /dev/null
+++ b/railties/test/fixtures/lib/template.rb
@@ -0,0 +1 @@
+say "It works from file!"
diff --git a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb
index 191bdbf2fc..191bdbf2fc 100644
--- a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb
+++ b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb
diff --git a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb
index 6aa7cb052e..6aa7cb052e 100644
--- a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb
+++ b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb
diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb
index 0cda49702b..fdaef6d9cb 100644
--- a/railties/test/generators/actions_test.rb
+++ b/railties/test/generators/actions_test.rb
@@ -29,11 +29,26 @@ class ActionsTest < GeneratorsTestCase
action :plugin, 'restful-authentication', :svn => @svn_plugin_uri
end
+ def test_plugin_with_git_option_and_branch_should_run_plugin_install
+ generator.expects(:run_ruby_script).once.with("script/plugin install -b stable #{@git_plugin_uri}", :verbose => false)
+ action :plugin, 'restful-authentication', :git => @git_plugin_uri, :branch => 'stable'
+ end
+
+ def test_plugin_with_svn_option_and_revision_should_run_plugin_install
+ generator.expects(:run_ruby_script).once.with("script/plugin install -r 1234 #{@svn_plugin_uri}", :verbose => false)
+ action :plugin, 'restful-authentication', :svn => @svn_plugin_uri, :revision => 1234
+ end
+
def test_plugin_with_git_option_and_submodule_should_use_git_scm
generator.expects(:run).with("git submodule add #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false)
action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true
end
+ def test_plugin_with_git_option_and_submodule_should_use_git_scm
+ generator.expects(:run).with("git submodule add -b stable #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false)
+ action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true, :branch => 'stable'
+ end
+
def test_plugin_with_no_options_should_skip_method
generator.expects(:run).never
action :plugin, 'rest_auth', {}
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index c794a2ade6..19e41c15c8 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -131,6 +131,11 @@ class AppGeneratorTest < GeneratorsTestCase
assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/
end
+ def test_template_from_dir_pwd
+ FileUtils.cd(RAILS_ROOT)
+ assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"])
+ end
+
def test_template_raises_an_error_with_invalid_path
content = capture(:stderr){ run_generator(["-m", "non/existant/path"]) }
assert_match /The template \[.*\] could not be loaded/, content
diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb
index 011bd518f8..9444a9ed4b 100644
--- a/railties/test/generators/generators_test_helper.rb
+++ b/railties/test/generators/generators_test_helper.rb
@@ -20,7 +20,7 @@ class GeneratorsTestCase < Test::Unit::TestCase
def destination_root
@destination_root ||= File.expand_path(File.join(File.dirname(__FILE__),
- '..', '..', 'fixtures', 'tmp'))
+ '..', 'fixtures', 'tmp'))
end
def setup
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index a9b772d67b..501c7d10c6 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -31,6 +31,50 @@ class ModelGeneratorTest < GeneratorsTestCase
assert_migration "db/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/
end
+ def test_migration_with_namespace
+ run_generator ["Gallery::Image"]
+ assert_migration "db/migrate/create_gallery_images", /class CreateGalleryImages < ActiveRecord::Migration/
+ assert_no_migration "db/migrate/create_images"
+ end
+
+ def test_migration_with_nested_namespace
+ run_generator ["Admin::Gallery::Image"]
+ assert_no_migration "db/migrate/create_images"
+ assert_no_migration "db/migrate/create_gallery_images"
+ assert_migration "db/migrate/create_admin_gallery_images", /class CreateAdminGalleryImages < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_admin_gallery_images", /create_table :admin_gallery_images/
+ end
+
+ def test_migration_with_nested_namespace_without_pluralization
+ ActiveRecord::Base.pluralize_table_names = false
+ run_generator ["Admin::Gallery::Image"]
+ assert_no_migration "db/migrate/create_images"
+ assert_no_migration "db/migrate/create_gallery_images"
+ assert_no_migration "db/migrate/create_admin_gallery_images"
+ assert_migration "db/migrate/create_admin_gallery_image", /class CreateAdminGalleryImage < ActiveRecord::Migration/
+ assert_migration "db/migrate/create_admin_gallery_image", /create_table :admin_gallery_image/
+ ensure
+ ActiveRecord::Base.pluralize_table_names = true
+ end
+
+ def test_migration_with_namespaces_in_model_name_without_plurization
+ ActiveRecord::Base.pluralize_table_names = false
+ run_generator ["Gallery::Image"]
+ assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration/
+ assert_no_migration "db/migrate/create_gallery_images"
+ ensure
+ ActiveRecord::Base.pluralize_table_names = true
+ end
+
+ def test_migration_without_pluralization
+ ActiveRecord::Base.pluralize_table_names = false
+ run_generator
+ assert_migration "db/migrate/create_account", /class CreateAccount < ActiveRecord::Migration/
+ assert_no_migration "db/migrate/create_accounts"
+ ensure
+ ActiveRecord::Base.pluralize_table_names = true
+ end
+
def test_migration_is_skipped
run_generator ["account", "--no-migration"]
assert_no_migration "db/migrate/create_accounts.rb"
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 024ea439ef..834e43e776 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -99,7 +99,7 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase
def test_error_is_shown_if_orm_does_not_provide_interface
error = capture(:stderr){ run_generator ["User", "--orm=unknown"] }
- assert_equal "Could not load Unknown::Generators::ActionORM, skipping controller. " <<
+ assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " <<
"Error: no such file to load -- generators/unknown.\n", error
end
diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb
index f83109800b..57bd755a9a 100644
--- a/railties/test/generators/session_migration_generator_test.rb
+++ b/railties/test/generators/session_migration_generator_test.rb
@@ -2,6 +2,16 @@ require 'abstract_unit'
require 'generators/generators_test_helper'
require 'generators/rails/session_migration/session_migration_generator'
+module ActiveRecord
+ module SessionStore
+ class Session
+ class << self
+ attr_accessor :table_name
+ end
+ end
+ end
+end
+
class SessionMigrationGeneratorTest < GeneratorsTestCase
def test_session_migration_with_default_name
@@ -14,6 +24,14 @@ class SessionMigrationGeneratorTest < GeneratorsTestCase
assert_migration "db/migrate/create_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/
end
+ def test_session_migration_with_custom_table_name
+ ActiveRecord::SessionStore::Session.table_name = "custom_table_name"
+ run_generator
+ assert_migration "db/migrate/add_sessions_table.rb" do |migration|
+ assert_match /class AddSessionsTable < ActiveRecord::Migration/, migration
+ assert_match /create_table :custom_table_name/, migration
+ end
+ end
protected
def run_generator(args=[])
diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb
index 1b73cdc73e..8de3161546 100644
--- a/railties/test/initializer/path_test.rb
+++ b/railties/test/initializer/path_test.rb
@@ -30,7 +30,8 @@ class PathsTest < Test::Unit::TestCase
assert_path @paths.lib, "lib"
assert_path @paths.vendor, "vendor"
assert_path @paths.vendor.plugins, "vendor", "plugins"
- assert_path @paths.cache, "tmp", "cache"
+ assert_path @paths.tmp, "tmp"
+ assert_path @paths.tmp.cache, "tmp", "cache"
assert_path @paths.config, "config"
assert_path @paths.config.locales, "config", "locales"
assert_path @paths.config.environments, "config", "environments"