aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2010-08-10 18:15:12 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2010-08-10 18:15:12 +0100
commit62658500049fbb7a5e7d75537dd6f6a374204207 (patch)
tree8892d8305ced43866068a6c1c66548e465e45b38
parentcd2bbed9846d84a1230a1b9e52843eedca17b28d (diff)
parente86cced311539932420f9cda49d736606d106c28 (diff)
downloadrails-62658500049fbb7a5e7d75537dd6f6a374204207.tar.gz
rails-62658500049fbb7a5e7d75537dd6f6a374204207.tar.bz2
rails-62658500049fbb7a5e7d75537dd6f6a374204207.zip
Merge branch 'master' of http://github.com/rails/rails
-rw-r--r--Gemfile56
-rw-r--r--RAILS_VERSION2
-rw-r--r--README.rdoc67
-rw-r--r--Rakefile70
-rw-r--r--actionmailer/CHANGELOG5
-rw-r--r--actionmailer/README.rdoc (renamed from actionmailer/README)46
-rw-r--r--actionmailer/Rakefile18
-rw-r--r--actionmailer/actionmailer.gemspec4
-rw-r--r--actionmailer/install.rb30
-rw-r--r--actionmailer/lib/action_mailer/base.rb38
-rw-r--r--actionmailer/lib/action_mailer/deprecated_api.rb2
-rw-r--r--actionmailer/lib/action_mailer/railtie.rb17
-rw-r--r--actionmailer/lib/action_mailer/test_case.rb2
-rw-r--r--actionmailer/lib/action_mailer/version.rb2
-rw-r--r--actionmailer/test/base_test.rb168
-rw-r--r--actionmailer/test/fixtures/asset_mailer/welcome.html.erb1
-rw-r--r--actionmailer/test/mailers/asset_mailer.rb7
-rw-r--r--actionmailer/test/mailers/base_mailer.rb114
-rw-r--r--actionmailer/test/mailers/proc_mailer.rb16
-rw-r--r--actionpack/CHANGELOG5
-rw-r--r--actionpack/README.rdoc (renamed from actionpack/README)198
-rw-r--r--actionpack/Rakefile14
-rw-r--r--actionpack/actionpack.gemspec6
-rw-r--r--actionpack/install.rb30
-rw-r--r--actionpack/lib/abstract_controller.rb1
-rw-r--r--actionpack/lib/abstract_controller/asset_paths.rb2
-rw-r--r--actionpack/lib/abstract_controller/base.rb12
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb2
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb1
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb23
-rw-r--r--actionpack/lib/action_controller/metal/rescue.rb9
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb183
-rw-r--r--actionpack/lib/action_controller/railtie.rb33
-rw-r--r--actionpack/lib/action_controller/railties/url_helpers.rb14
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb33
-rw-r--r--actionpack/lib/action_controller/test_case.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb7
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb76
-rw-r--r--actionpack/lib/action_dispatch/http/parameter_filter.rb72
-rw-r--r--actionpack/lib/action_dispatch/middleware/best_standards_support.rb22
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb15
-rw-r--r--actionpack/lib/action_dispatch/railtie.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing.rb12
-rw-r--r--actionpack/lib/action_dispatch/routing/deprecated_mapper.rb3
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb71
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb186
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb18
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb5
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/model.rb19
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb5
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb32
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb60
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb74
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb159
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb113
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb210
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb53
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb36
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb69
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb130
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb409
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb68
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb160
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y34
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb83
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex12
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb148
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb569
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb185
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb158
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb45
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb40
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb4
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb59
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb56
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb83
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb126
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb90
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb55
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb415
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb213
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb3
-rw-r--r--actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb5
-rw-r--r--actionpack/lib/action_pack/version.rb2
-rw-r--r--actionpack/lib/action_view.rb1
-rw-r--r--actionpack/lib/action_view/helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/atom_feed_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb29
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb5
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb48
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/record_identification_helper.rb23
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb30
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb24
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb4
-rw-r--r--actionpack/lib/action_view/test_case.rb2
-rw-r--r--actionpack/test/abstract/abstract_controller_test.rb14
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb15
-rw-r--r--actionpack/test/controller/record_identifier_test.rb40
-rw-r--r--actionpack/test/controller/rescue_test.rb27
-rw-r--r--actionpack/test/controller/resources_test.rb1
-rw-r--r--actionpack/test/controller/test_test.rb12
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb10
-rw-r--r--actionpack/test/dispatch/request_test.rb8
-rw-r--r--actionpack/test/dispatch/routing_test.rb73
-rw-r--r--actionpack/test/dispatch/session/cookie_store_test.rb20
-rw-r--r--actionpack/test/template/capture_helper_test.rb2
-rw-r--r--actionpack/test/template/form_helper_test.rb35
-rw-r--r--actionpack/test/template/form_options_helper_test.rb23
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb2
-rw-r--r--actionpack/test/template/javascript_helper_test.rb9
-rw-r--r--actionpack/test/template/number_helper_test.rb27
-rw-r--r--actionpack/test/template/prototype_helper_test.rb6
-rw-r--r--actionpack/test/template/url_helper_test.rb22
-rw-r--r--activemodel/CHANGELOG5
-rw-r--r--activemodel/README.rdoc (renamed from activemodel/README)124
-rw-r--r--activemodel/Rakefile12
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb2
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/conversion.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb12
-rw-r--r--activemodel/lib/active_model/errors.rb34
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb3
-rw-r--r--activemodel/lib/active_model/naming.rb38
-rw-r--r--activemodel/lib/active_model/serialization.rb2
-rw-r--r--activemodel/lib/active_model/serializers/json.rb18
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activemodel/lib/active_model/validations.rb24
-rw-r--r--activemodel/lib/active_model/validations/length.rb11
-rw-r--r--activemodel/lib/active_model/validations/validates.rb2
-rw-r--r--activemodel/lib/active_model/validator.rb6
-rw-r--r--activemodel/lib/active_model/version.rb2
-rw-r--r--activemodel/test/cases/dirty_test.rb90
-rw-r--r--activemodel/test/cases/errors_test.rb65
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb2
-rw-r--r--activemodel/test/cases/mass_assignment_security_test.rb6
-rw-r--r--activemodel/test/cases/naming_test.rb39
-rw-r--r--activemodel/test/cases/serializeration/json_serialization_test.rb16
-rw-r--r--activemodel/test/cases/validations/length_validation_test.rb14
-rw-r--r--activemodel/test/cases/validations_test.rb8
-rw-r--r--activemodel/test/models/contact.rb1
-rw-r--r--activemodel/test/models/sheep.rb4
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/README351
-rw-r--r--activerecord/README.rdoc222
-rw-r--r--activerecord/Rakefile32
-rw-r--r--activerecord/activerecord.gemspec8
-rw-r--r--activerecord/examples/performance.rb2
-rw-r--r--activerecord/install.rb30
-rw-r--r--activerecord/lib/active_record/aggregations.rb115
-rw-r--r--activerecord/lib/active_record/association_preload.rb22
-rw-r--r--activerecord/lib/active_record/associations.rb521
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb41
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb35
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/through_association_scope.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb3
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb4
-rw-r--r--activerecord/lib/active_record/autosave_association.rb38
-rw-r--r--activerecord/lib/active_record/base.rb257
-rw-r--r--activerecord/lib/active_record/callbacks.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb639
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb112
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb31
-rw-r--r--activerecord/lib/active_record/dynamic_finder_match.rb8
-rw-r--r--activerecord/lib/active_record/errors.rb14
-rw-r--r--activerecord/lib/active_record/fixtures.rb61
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb1
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb20
-rw-r--r--activerecord/lib/active_record/migration.rb2
-rw-r--r--activerecord/lib/active_record/named_scope.rb25
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb4
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb77
-rw-r--r--activerecord/lib/active_record/railtie.rb19
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb4
-rw-r--r--activerecord/lib/active_record/railties/databases.rake4
-rw-r--r--activerecord/lib/active_record/reflection.rb97
-rw-r--r--activerecord/lib/active_record/relation.rb45
-rw-r--r--activerecord/lib/active_record/relation/batches.rb8
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb80
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb27
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb77
-rw-r--r--activerecord/lib/active_record/schema.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb18
-rw-r--r--activerecord/lib/active_record/session_store.rb155
-rw-r--r--activerecord/lib/active_record/timestamp.rb69
-rw-r--r--activerecord/lib/active_record/validations/associated.rb7
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb22
-rw-r--r--activerecord/lib/active_record/version.rb2
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb3
-rw-r--r--activerecord/test/cases/adapters/mysql2/active_schema_test.rb125
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb42
-rw-r--r--activerecord/test/cases/adapters/mysql2/reserved_word_test.rb176
-rw-r--r--activerecord/test/cases/adapters/postgresql/active_schema_test.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb17
-rw-r--r--activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb98
-rw-r--r--activerecord/test/cases/aggregations_test.rb2
-rw-r--r--activerecord/test/cases/ar_schema_test.rb2
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb8
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb2
-rw-r--r--activerecord/test/cases/associations/cascaded_eager_loading_test.rb8
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb62
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb87
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb40
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb12
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations_test.rb67
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb280
-rw-r--r--activerecord/test/cases/autosave_association_test.rb10
-rw-r--r--activerecord/test/cases/base_test.rb855
-rw-r--r--activerecord/test/cases/calculations_test.rb2
-rw-r--r--activerecord/test/cases/column_definition_test.rb34
-rw-r--r--activerecord/test/cases/connection_management_test.rb25
-rw-r--r--activerecord/test/cases/connection_pool_test.rb50
-rw-r--r--activerecord/test/cases/defaults_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb9
-rw-r--r--activerecord/test/cases/fixtures_test.rb6
-rw-r--r--activerecord/test/cases/i18n_test.rb2
-rw-r--r--activerecord/test/cases/invalid_date_test.rb2
-rw-r--r--activerecord/test/cases/json_serialization_test.rb7
-rw-r--r--activerecord/test/cases/locking_test.rb3
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb24
-rw-r--r--activerecord/test/cases/method_scoping_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb12
-rw-r--r--activerecord/test/cases/named_scope_test.rb16
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb69
-rw-r--r--activerecord/test/cases/persistence_test.rb470
-rw-r--r--activerecord/test/cases/primary_keys_test.rb (renamed from activerecord/test/cases/pk_test.rb)4
-rw-r--r--activerecord/test/cases/query_cache_test.rb4
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb29
-rw-r--r--activerecord/test/cases/relations_test.rb24
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb4
-rw-r--r--activerecord/test/cases/serialization_test.rb134
-rw-r--r--activerecord/test/cases/session_store/session_test.rb68
-rw-r--r--activerecord/test/cases/session_store/sql_bypass.rb56
-rw-r--r--activerecord/test/cases/timestamp_test.rb30
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb42
-rw-r--r--activerecord/test/cases/transactions_test.rb23
-rw-r--r--activerecord/test/cases/validations/i18n_generate_message_validation_test.rb1
-rw-r--r--activerecord/test/cases/validations_test.rb5
-rw-r--r--activerecord/test/cases/xml_serialization_test.rb1
-rw-r--r--activerecord/test/connections/native_mysql2/connection.rb25
-rw-r--r--activerecord/test/fixtures/dashboards.yml3
-rw-r--r--activerecord/test/fixtures/minivans.yml5
-rw-r--r--activerecord/test/fixtures/speedometers.yml4
-rw-r--r--activerecord/test/fixtures/subscriptions.yml2
-rw-r--r--activerecord/test/models/author.rb2
-rw-r--r--activerecord/test/models/book.rb3
-rw-r--r--activerecord/test/models/company_in_module.rb2
-rw-r--r--activerecord/test/models/country.rb7
-rw-r--r--activerecord/test/models/dashboard.rb3
-rw-r--r--activerecord/test/models/developer.rb1
-rw-r--r--activerecord/test/models/electron.rb3
-rw-r--r--activerecord/test/models/liquid.rb5
-rw-r--r--activerecord/test/models/minivan.rb9
-rw-r--r--activerecord/test/models/molecule.rb4
-rw-r--r--activerecord/test/models/person.rb2
-rw-r--r--activerecord/test/models/reference.rb5
-rw-r--r--activerecord/test/models/speedometer.rb4
-rw-r--r--activerecord/test/models/treaty.rb7
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb24
-rw-r--r--activerecord/test/schema/schema.rb48
-rw-r--r--activeresource/CHANGELOG5
-rw-r--r--activeresource/README.rdoc (renamed from activeresource/README)0
-rw-r--r--activeresource/Rakefile18
-rw-r--r--activeresource/activeresource.gemspec6
-rw-r--r--activeresource/lib/active_resource/base.rb11
-rw-r--r--activeresource/lib/active_resource/http_mock.rb6
-rw-r--r--activeresource/lib/active_resource/version.rb2
-rw-r--r--activeresource/test/cases/base/load_test.rb12
-rw-r--r--activeresource/test/cases/base_test.rb4
-rw-r--r--activesupport/CHANGELOG4
-rw-r--r--activesupport/README43
-rw-r--r--activesupport/README.rdoc33
-rw-r--r--activesupport/Rakefile18
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/install.rb30
-rw-r--r--activesupport/lib/active_support/buffered_logger.rb6
-rw-r--r--activesupport/lib/active_support/cache.rb102
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb7
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb10
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb10
-rw-r--r--activesupport/lib/active_support/callbacks.rb18
-rw-r--r--activesupport/lib/active_support/concern.rb35
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/array/uniq_by.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb34
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb30
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute_accessors.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb28
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/requires.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/object/misc.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/returning.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/object/to_param.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb10
-rw-r--r--activesupport/lib/active_support/dependencies.rb19
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb7
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb8
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb1
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb12
-rw-r--r--activesupport/lib/active_support/json/backends/yaml.rb2
-rw-r--r--activesupport/lib/active_support/lazy_load_hooks.rb21
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb25
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb23
-rw-r--r--activesupport/lib/active_support/message_verifier.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb73
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb19
-rw-r--r--activesupport/lib/active_support/notifications.rb48
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb74
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb16
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb11
-rw-r--r--activesupport/lib/active_support/ordered_options.rb16
-rw-r--r--activesupport/lib/active_support/secure_random.rb26
-rw-r--r--activesupport/lib/active_support/testing/setup_and_teardown.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb13
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb6
-rw-r--r--activesupport/lib/active_support/version.rb2
-rw-r--r--activesupport/lib/active_support/xml_mini.rb8
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb7
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb9
-rw-r--r--activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb3
-rw-r--r--activesupport/test/autoloading_fixtures/loads_constant.rb5
-rw-r--r--activesupport/test/autoloading_fixtures/requires_constant.rb5
-rw-r--r--activesupport/test/buffered_logger_test.rb15
-rw-r--r--activesupport/test/caching_test.rb45
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb20
-rw-r--r--activesupport/test/core_ext/class_test.rb9
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb31
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb29
-rw-r--r--activesupport/test/core_ext/module_test.rb2
-rw-r--r--activesupport/test/core_ext/object/to_query_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb54
-rw-r--r--activesupport/test/deprecation/proxy_wrappers_test.rb22
-rw-r--r--activesupport/test/json/encoding_test.rb2
-rw-r--r--activesupport/test/load_paths_test.rb4
-rw-r--r--activesupport/test/memoizable_test.rb1
-rw-r--r--activesupport/test/multibyte_chars_test.rb2
-rw-r--r--activesupport/test/multibyte_test_helpers.rb5
-rw-r--r--activesupport/test/notifications_test.rb27
-rw-r--r--activesupport/test/option_merger_test.rb2
-rw-r--r--activesupport/test/ordered_hash_test.rb2
-rw-r--r--activesupport/test/rescuable_test.rb37
-rw-r--r--activesupport/test/test_test.rb2
-rw-r--r--activesupport/test/test_xml_mini.rb49
-rwxr-xr-xci/ci_build.rb4
-rw-r--r--doc/template/horo.rb618
-rw-r--r--install.rb11
-rw-r--r--rails.gemspec2
-rw-r--r--railties/CHANGELOG19
-rw-r--r--railties/README281
-rw-r--r--railties/README.rdoc25
-rw-r--r--railties/Rakefile14
-rw-r--r--railties/guides/rails_guides.rb21
-rw-r--r--railties/guides/source/3_0_release_notes.textile7
-rw-r--r--railties/guides/source/action_controller_overview.textile35
-rw-r--r--railties/guides/source/active_record_querying.textile48
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile27
-rw-r--r--railties/guides/source/active_support_core_extensions.textile563
-rw-r--r--railties/guides/source/api_documentation_guidelines.textile187
-rw-r--r--railties/guides/source/association_basics.textile36
-rw-r--r--railties/guides/source/command_line.textile12
-rw-r--r--railties/guides/source/configuring.textile2
-rw-r--r--railties/guides/source/contributing_to_rails.textile78
-rw-r--r--railties/guides/source/form_helpers.textile2
-rw-r--r--railties/guides/source/getting_started.textile20
-rw-r--r--railties/guides/source/i18n.textile2
-rw-r--r--railties/guides/source/index.html.erb20
-rw-r--r--railties/guides/source/initialization.textile34
-rw-r--r--railties/guides/source/layout.html.erb6
-rw-r--r--railties/guides/source/layouts_and_rendering.textile2
-rw-r--r--railties/guides/source/migrations.textile7
-rw-r--r--railties/guides/source/plugins.textile2
-rw-r--r--railties/guides/source/routing.textile90
-rw-r--r--railties/guides/source/security.textile2
-rw-r--r--railties/lib/rails/application.rb14
-rw-r--r--railties/lib/rails/commands.rb2
-rw-r--r--railties/lib/rails/commands/console.rb5
-rw-r--r--railties/lib/rails/configuration.rb5
-rw-r--r--railties/lib/rails/engine/configuration.rb2
-rw-r--r--railties/lib/rails/generators/actions.rb6
-rw-r--r--railties/lib/rails/generators/active_model.rb6
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb28
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/application.rb16
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/index.html23
-rw-r--r--railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js2907
-rw-r--r--railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt2
-rw-r--r--railties/lib/rails/generators/test_case.rb8
-rw-r--r--railties/lib/rails/info.rb3
-rw-r--r--railties/lib/rails/paths.rb40
-rw-r--r--railties/lib/rails/rack/log_tailer.rb7
-rw-r--r--railties/lib/rails/railtie.rb62
-rw-r--r--railties/lib/rails/script_rails_loader.rb3
-rw-r--r--railties/lib/rails/tasks/documentation.rake14
-rw-r--r--railties/lib/rails/tasks/framework.rake2
-rw-r--r--railties/lib/rails/test_unit/testing.rake2
-rw-r--r--railties/lib/rails/version.rb2
-rw-r--r--railties/railties.gemspec4
-rw-r--r--railties/test/application/console_test.rb21
-rw-r--r--railties/test/application/generators_test.rb15
-rw-r--r--railties/test/application/initializers/frameworks_test.rb12
-rw-r--r--railties/test/application/initializers/notifications_test.rb19
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/application/routing_test.rb97
-rw-r--r--railties/test/generators/app_generator_test.rb57
-rw-r--r--railties/test/generators/scaffold_generator_test.rb15
-rw-r--r--railties/test/generators_test.rb2
-rw-r--r--railties/test/paths_test.rb24
-rw-r--r--railties/test/railties/railtie_test.rb16
-rw-r--r--version.rb2
446 files changed, 10118 insertions, 10545 deletions
diff --git a/Gemfile b/Gemfile
index 827c67e522..d594d20fac 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,43 +1,47 @@
source 'http://rubygems.org'
-gem "arel", :git => "git://github.com/rails/arel.git"
-#gem "rack-mount", :git => "git://github.com/rails/rack-mount.git"
+if ENV['AREL']
+ gem "arel", :path => ENV['AREL']
+else
+ gem "arel", :git => "git://github.com/rails/arel.git"
+end
+
gem "rails", :path => File.dirname(__FILE__)
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
-gem "rdoc", "2.2"
+gem "rdoc", ">= 2.5.9"
+gem "horo", ">= 1.0.1"
-mri = !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
-if mri && RUBY_VERSION < '1.9'
+# AS
+gem "memcache-client", ">= 1.8.5"
+
+# AM
+gem "text-format", "~> 1.0.0"
+
+platforms :mri_18 do
gem "system_timer"
gem "ruby-debug", ">= 0.10.3"
end
-if mri || RUBY_ENGINE == "rbx"
+platforms :ruby do
gem 'json'
gem 'yajl-ruby'
- gem "nokogiri", ">= 1.4.0"
-elsif RUBY_ENGINE == "jruby"
- gem "ruby-debug"
- gem "jruby-openssl"
-end
-
-# AS
-gem "memcache-client", ">= 1.7.5"
+ gem "nokogiri", ">= 1.4.3.1"
-# AM
-gem "text-format", "~> 1.0.0"
-
-# AR
-if mri || RUBY_ENGINE == "rbx"
- gem "sqlite3-ruby", "~> 1.3.0", :require => 'sqlite3'
+ # AR
+ gem "sqlite3-ruby", "~> 1.3.1", :require => 'sqlite3'
group :db do
gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
+ gem "mysql2", :git => 'git://github.com/brianmario/mysql2.git'
end
-elsif RUBY_ENGINE == "jruby"
+end
+
+platforms :jruby do
+ gem "ruby-debug", ">= 0.10.3"
+
gem "activerecord-jdbcsqlite3-adapter"
group :db do
@@ -46,9 +50,11 @@ elsif RUBY_ENGINE == "jruby"
end
end
-if ENV['CI']
- gem "nokogiri", ">= 1.4.0"
+env 'CI' do
+ gem "nokogiri", ">= 1.4.3.1"
- # fcgi gem doesn't compile on 1.9
- gem "fcgi", ">= 0.8.7" if RUBY_VERSION < '1.9.0'
+ platforms :ruby_18 do
+ # fcgi gem doesn't compile on 1.9
+ gem "fcgi", ">= 0.8.8"
+ end
end
diff --git a/RAILS_VERSION b/RAILS_VERSION
index c3eddb1401..7706a9b5ac 100644
--- a/RAILS_VERSION
+++ b/RAILS_VERSION
@@ -1 +1 @@
-3.0.0.beta4
+3.0.0.rc
diff --git a/README.rdoc b/README.rdoc
new file mode 100644
index 0000000000..090a6bb68c
--- /dev/null
+++ b/README.rdoc
@@ -0,0 +1,67 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+This pattern splits the view (also called the presentation) into "dumb"
+templates that are primarily responsible for inserting pre-built data in between
+HTML tags. The model contains the "smart" domain objects (such as Account,
+Product, Person, Post) that holds all the business logic and knows how to
+persist themselves to a database. The controller handles the incoming requests
+(such as Save New Account, Update Product, Show Post) by manipulating the model
+and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails. You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting Started
+
+1. Install Rails at the command prompt if you haven't yet:
+
+ gem install rails
+
+2. At the command prompt, create a new Rails application:
+
+ rails new myapp
+
+ where "myapp" is the application name.
+
+3. Change directory to +myapp+ and start the web server:
+
+ cd myapp; rails server
+
+ Run with <tt>--help</tt> for options.
+
+4. Go to http://localhost:3000/ and you'll see:
+
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+5. Follow the guidelines to start developing your application. You can find
+the following resources handy:
+
+* The README file created within your application.
+* The {Getting Started Guide}[http://guides.rubyonrails.org/getting_started.html].
+* The {Ruby on Rails Tutorial Book}[http://railstutorial.org/book].
+
+
+== Contributing
+
+We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails
+guide}[http://edgeguides.rubyonrails.org/contributing_to_rails.html] for guidelines about how
+to proceed. {Join us}[http://contributors.rubyonrails.org]!
+
+== License
+
+Ruby on Rails is released under the MIT license.
diff --git a/Rakefile b/Rakefile
index e608af0319..ceb0e832b3 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,16 +1,41 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/gempackagetask'
+# RDoc skips some files in the Rails tree due to its binary? predicate. This is a quick
+# hack for edge docs, until we decide which is the correct way to address this issue.
+# If not fixed in RDoc itself, via an option or something, we should probably move this
+# to railties and use it also in doc:rails.
+def hijack_rdoc!
+ require "rdoc/parser"
+ class << RDoc::Parser
+ def binary?(file)
+ s = File.read(file, 1024) or return false
+
+ if s[0, 2] == Marshal.dump('')[0, 2] then
+ true
+ elsif file =~ /erb\.rb$/ then
+ false
+ elsif s.index("\x00") then # ORIGINAL is s.scan(/<%|%>/).length >= 4 || s.index("\x00")
+ true
+ elsif 0.respond_to? :fdiv then
+ s.count("^ -~\t\r\n").fdiv(s.size) > 0.3
+ else # HACK 1.8.6
+ (s.count("^ -~\t\r\n").to_f / s.size) > 0.3
+ end
+ end
+ end
+end
+
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
desc 'Run all tests by default'
task :default => %w(test test:isolated)
-%w(test test:isolated rdoc package gem).each do |task_name|
+%w(test test:isolated package gem).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
errors = []
@@ -62,59 +87,53 @@ task :install => :gem do
end
desc "Generate documentation for the Rails framework"
-Rake::RDocTask.new do |rdoc|
+RDoc::Task.new do |rdoc|
+ hijack_rdoc!
+
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
- rdoc.options << '--line-numbers' << '--inline-source'
- rdoc.options << '-A cattr_accessor=object'
- rdoc.options << '--charset' << 'utf-8'
- rdoc.options << '--main' << 'railties/README'
-
- # Workaround: RDoc assumes that rdoc.template can be required, and that
- # rdoc.template.upcase is a constant living in RDoc::Generator::HTML
- # which holds the actual template class.
- #
- # We put 'doc/template' in the load path to be able to set the template
- # to the string 'horo' and thus meet those RDoc's assumptions.
- $:.unshift('doc/template')
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '-c' << 'utf-8'
+ rdoc.options << '-m' << 'README.rdoc'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : 'horo'
+ rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('railties/CHANGELOG')
rdoc.rdoc_files.include('railties/MIT-LICENSE')
- rdoc.rdoc_files.include('railties/README')
+ rdoc.rdoc_files.include('railties/README.rdoc')
rdoc.rdoc_files.include('railties/lib/**/*.rb')
rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/*')
- rdoc.rdoc_files.include('activerecord/README')
+ rdoc.rdoc_files.include('activerecord/README.rdoc')
rdoc.rdoc_files.include('activerecord/CHANGELOG')
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
- rdoc.rdoc_files.include('activeresource/README')
+ rdoc.rdoc_files.include('activeresource/README.rdoc')
rdoc.rdoc_files.include('activeresource/CHANGELOG')
rdoc.rdoc_files.include('activeresource/lib/active_resource.rb')
rdoc.rdoc_files.include('activeresource/lib/active_resource/*')
- rdoc.rdoc_files.include('actionpack/README')
+ rdoc.rdoc_files.include('actionpack/README.rdoc')
rdoc.rdoc_files.include('actionpack/CHANGELOG')
+ rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb')
rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
- rdoc.rdoc_files.include('actionmailer/README')
+ rdoc.rdoc_files.include('actionmailer/README.rdoc')
rdoc.rdoc_files.include('actionmailer/CHANGELOG')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
- rdoc.rdoc_files.include('activesupport/README')
+ rdoc.rdoc_files.include('activesupport/README.rdoc')
rdoc.rdoc_files.include('activesupport/CHANGELOG')
rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
- rdoc.rdoc_files.include('activemodel/README')
+ rdoc.rdoc_files.include('activemodel/README.rdoc')
rdoc.rdoc_files.include('activemodel/CHANGELOG')
rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb')
end
@@ -129,9 +148,6 @@ desc "Publish API docs for Rails as a whole and for each component"
task :pdoc => :rdoc do
require 'rake/contrib/sshpublisher'
Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/api", "doc/rdoc").upload
- PROJECTS.each do |project|
- system %(cd #{project} && #{$0} pdoc)
- end
end
task :update_versions do
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG
index f0b9368874..76eab935e5 100644
--- a/actionmailer/CHANGELOG
+++ b/actionmailer/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* No material changes
+
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") [JK]
diff --git a/actionmailer/README b/actionmailer/README.rdoc
index 2a4d507d8a..b52c993f56 100644
--- a/actionmailer/README
+++ b/actionmailer/README.rdoc
@@ -74,9 +74,9 @@ Or you can just chain the methods together like:
== Receiving emails
-To receive emails, you need to implement a public instance method called receive that takes a
+To receive emails, you need to implement a public instance method called <tt>receive</tt> that takes a
tmail object as its single parameter. The Action Mailer framework has a corresponding class method,
-which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns
+which is also called <tt>receive</tt>, that accepts a raw, unprocessed email as a string, which it then turns
into the tmail object and calls the receive instance method.
Example:
@@ -119,36 +119,16 @@ The Base class has the full list of configuration options. Here's an example:
:authentication => :plain # :plain, :login or :cram_md5
}
-== Dependencies
-Action Mailer requires that the Action Pack is either available to be required immediately
-or is accessible as a GEM.
+== Download and installation
-Additionally, Action Mailer requires the Mail gem, http://github.com/mikel/mail
+The latest version of Action Mailer can be installed with Rubygems:
-== Bundled software
+ % [sudo] gem install actionmailer
-* Text::Format 0.63 by Austin Ziegler released under OpenSource
- Read more on http://www.halostatue.ca/ruby/Text__Format.html
+Source code can be downloaded as part of the Rails project on GitHub
-== Download
-
-The latest version of Action Mailer can be found at
-
-* http://rubyforge.org/project/showfiles.php?group_id=361
-
-Documentation can be found at
-
-* http://actionmailer.rubyonrails.org
-
-
-== Installation
-
-You can install Action Mailer with the following command.
-
- % [sudo] ruby install.rb
-
-from its distribution directory.
+* http://github.com/rails/rails/tree/master/actionmailer/
== License
@@ -158,10 +138,10 @@ Action Mailer is released under the MIT license.
== Support
-The Action Mailer homepage is http://www.rubyonrails.org. You can find
-the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
-And as Jim from Rake says:
+API documentation is at
+
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
- Feel free to submit commits or feature requests. If you send a patch,
- remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index f20e7ea928..a47426bd07 100644
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
@@ -26,13 +26,13 @@ namespace :test do
end
# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Mailer -- Easy email delivery and testing"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'CHANGELOG')
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG')
rdoc.rdoc_files.include('lib/action_mailer.rb')
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
rdoc.rdoc_files.include('lib/action_mailer/delivery_method/*.rb')
@@ -50,9 +50,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/am", "doc").upload
-end
diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec
index 2b7c21b3f2..daf30e434a 100644
--- a/actionmailer/actionmailer.gemspec
+++ b/actionmailer/actionmailer.gemspec
@@ -13,12 +13,12 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'actionmailer'
- s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
s.has_rdoc = true
s.add_dependency('actionpack', version)
- s.add_dependency('mail', '~> 2.2.3')
+ s.add_dependency('mail', '~> 2.2.5')
end
diff --git a/actionmailer/install.rb b/actionmailer/install.rb
deleted file mode 100644
index 8d7c140c3b..0000000000
--- a/actionmailer/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by way of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("action_mailer", "action_mailer.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-}
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index ed4bea0c77..f742f982f2 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -41,16 +41,16 @@ module ActionMailer #:nodoc:
# in the same manner as <tt>attachments[]=</tt>
#
# * <tt>headers[]=</tt> - Allows you to specify any header field in your email such
- # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields (like <tt>To:</tt>
+ # as <tt>headers['X-No-Spam'] = 'True'</tt>. Note, while most fields like <tt>To:</tt>
# <tt>From:</tt> can only appear once in an email header, other fields like <tt>X-Anything</tt>
# can appear multiple times. If you want to change a field that can appear multiple times,
- # you need to set it to nil first so that Mail knows you are replacing it, not adding
- # another field of the same name.)
+ # you need to set it to nil first so that Mail knows you are replacing it and not adding
+ # another field of the same name.
#
# * <tt>headers(hash)</tt> - Allows you to specify multiple headers in your email such
# as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt>
#
- # * <tt>mail</tt> - Allows you to specify your email to send.
+ # * <tt>mail</tt> - Allows you to specify email to be sent.
#
# The hash passed to the mail method allows you to specify any header that a Mail::Message
# will accept (any valid Email header including optional fields).
@@ -66,7 +66,7 @@ module ActionMailer #:nodoc:
# format.html
# end
#
- # The block syntax is useful if also need to specify information specific to a part:
+ # The block syntax is also useful in providing information specific to a part:
#
# mail(:to => user.email) do |format|
# format.text(:content_transfer_encoding => "base64")
@@ -121,19 +121,18 @@ module ActionMailer #:nodoc:
#
# <%= users_url(:host => "example.com") %>
#
- # You will want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't
+ # You want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't
# make sense to generate relative URLs in email messages.
#
# It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt>
- # option in the <tt>ActionMailer::Base.default_url_options</tt> hash as follows:
- #
- # ActionMailer::Base.default_url_options[:host] = "example.com"
- #
- # This can also be set as a configuration option in <tt>config/environment.rb</tt>:
+ # option as a configuration option in <tt>config/application.rb</tt>:
#
# config.action_mailer.default_url_options = { :host => "example.com" }
#
- # If you do decide to set a default <tt>:host</tt> for your mailers you will want to use the
+ # Setting <tt>ActionMailer::Base.default_url_options</tt> directly is now deprecated, use the configuration
+ # option mentioned above to set the default host.
+ #
+ # If you do decide to set a default <tt>:host</tt> for your mailers you want to use the
# <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are
# generated because the <tt>url_for</tt> view helper will, by default, generate relative URLs when a
# <tt>:host</tt> option isn't explicitly provided.
@@ -155,7 +154,7 @@ module ActionMailer #:nodoc:
# detect and use multipart templates, where each template is named after the name of the action, followed
# by the content type. Each such detected template will be added as separate part to the message.
#
- # For example, if the following templates existed:
+ # For example, if the following templates exist:
# * signup_notification.text.plain.erb
# * signup_notification.text.html.erb
# * signup_notification.text.xml.builder
@@ -172,8 +171,7 @@ module ActionMailer #:nodoc:
#
# = Attachments
#
- # You can see above how to make a multipart HTML / Text email, to send attachments is just
- # as easy:
+ # Sending attachment in emails is easy:
#
# class ApplicationMailer < ActionMailer::Base
# def welcome(recipient)
@@ -190,10 +188,8 @@ module ActionMailer #:nodoc:
#
# = Inline Attachments
#
- # You can also specify that a file should be displayed inline with other HTML. For example a
- # corporate logo or a photo or the like.
- #
- # To do this is simple, in the Mailer:
+ # You can also specify that a file should be displayed inline with other HTML. This is useful
+ # if you want to display a corporate logo or a photo.
#
# class ApplicationMailer < ActionMailer::Base
# def welcome(recipient)
@@ -497,10 +493,10 @@ module ActionMailer #:nodoc:
# You can also search for specific attachments:
#
# # By Filename
- # mail.attachments['filename.jpg'] #=> Mail::Part object or nil
+ # mail.attachments['filename.jpg'] # => Mail::Part object or nil
#
# # or by index
- # mail.attachments[0] #=> Mail::Part (first attachment)
+ # mail.attachments[0] # => Mail::Part (first attachment)
#
def attachments
@_message.attachments
diff --git a/actionmailer/lib/action_mailer/deprecated_api.rb b/actionmailer/lib/action_mailer/deprecated_api.rb
index 0070d8e016..7d57feba04 100644
--- a/actionmailer/lib/action_mailer/deprecated_api.rb
+++ b/actionmailer/lib/action_mailer/deprecated_api.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/try'
+
module ActionMailer
# This is the API which is deprecated and is going to be removed on Rails 3.1 release.
# Part of the old API will be deprecated after 3.1, for a smoother deprecation process.
diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb
index d7b09b2dc6..ce6d8cc5b5 100644
--- a/actionmailer/lib/action_mailer/railtie.rb
+++ b/actionmailer/lib/action_mailer/railtie.rb
@@ -10,21 +10,16 @@ module ActionMailer
end
initializer "action_mailer.set_configs" do |app|
- paths = app.config.paths
- am = app.config.action_mailer
+ paths = app.config.paths
+ options = app.config.action_mailer
- am.assets_dir ||= paths.public.to_a.first
- am.javascripts_dir ||= paths.public.javascripts.to_a.first
- am.stylesheets_dir ||= paths.public.stylesheets.to_a.first
+ options.assets_dir ||= paths.public.to_a.first
+ options.javascripts_dir ||= paths.public.javascripts.to_a.first
+ options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
ActiveSupport.on_load(:action_mailer) do
- self.config.merge!(am)
-
include app.routes.url_helpers
-
- app.config.action_mailer.each do |k,v|
- send "#{k}=", v
- end
+ options.each { |k,v| send("#{k}=", v) }
end
end
end
diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb
index b91eed592a..f4d1bb59f1 100644
--- a/actionmailer/lib/action_mailer/test_case.rb
+++ b/actionmailer/lib/action_mailer/test_case.rb
@@ -28,7 +28,7 @@ module ActionMailer
def determine_default_mailer(name)
name.sub(/Test$/, '').constantize
- rescue NameError => e
+ rescue NameError
raise NonInferrableMailerError.new(name)
end
end
diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb
index 8c19082f61..805c89be1d 100644
--- a/actionmailer/lib/action_mailer/version.rb
+++ b/actionmailer/lib/action_mailer/version.rb
@@ -3,7 +3,7 @@ module ActionMailer
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb
index 5bc0491cff..fec0ecf477 100644
--- a/actionmailer/test/base_test.rb
+++ b/actionmailer/test/base_test.rb
@@ -2,139 +2,17 @@
require 'abstract_unit'
require 'active_support/time'
+require 'mailers/base_mailer'
+require 'mailers/proc_mailer'
+require 'mailers/asset_mailer'
+
class BaseTest < ActiveSupport::TestCase
# TODO Add some tests for implicity layout render and url helpers
# so we can get rid of old base tests altogether with old base.
- class BaseMailer < ActionMailer::Base
- self.mailer_name = "base_mailer"
-
- default :to => 'system@test.lindsaar.net',
- :from => 'jose@test.plataformatec.com',
- :reply_to => 'mikel@test.lindsaar.net'
-
- def welcome(hash = {})
- headers['X-SPAM'] = "Not SPAM"
- mail({:subject => "The first email on new API!"}.merge!(hash))
- end
-
- def welcome_with_headers(hash = {})
- headers hash
- mail
- end
-
- def welcome_from_another_path(path)
- mail(:template_name => "welcome", :template_path => path)
- end
-
- def html_only(hash = {})
- mail(hash)
- end
-
- def plain_text_only(hash = {})
- mail(hash)
- end
-
- def inline_attachment
- attachments.inline['logo.png'] = "\312\213\254\232"
- mail
- end
-
- def attachment_with_content(hash = {})
- attachments['invoice.pdf'] = 'This is test File content'
- mail(hash)
- end
-
- def attachment_with_hash
- attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
- :mime_type => "image/x-jpg",
- :transfer_encoding => "base64" }
- mail
- end
-
- def attachment_with_hash_default_encoding
- attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
- :mime_type => "image/x-jpg" }
- mail
- end
-
- def implicit_multipart(hash = {})
- attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
- mail(hash)
- end
-
- def implicit_with_locale(hash = {})
- mail(hash)
- end
-
- def explicit_multipart(hash = {})
- attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
- mail(hash) do |format|
- format.text { render :text => "TEXT Explicit Multipart" }
- format.html { render :text => "HTML Explicit Multipart" }
- end
- end
-
- def explicit_multipart_templates(hash = {})
- mail(hash) do |format|
- format.html
- format.text
- end
- end
-
- def explicit_multipart_with_any(hash = {})
- mail(hash) do |format|
- format.any(:text, :html){ render :text => "Format with any!" }
- end
- end
- def explicit_multipart_with_options(include_html = false)
- mail do |format|
- format.text(:content_transfer_encoding => "base64"){ render "welcome" }
- format.html{ render "welcome" } if include_html
- end
- end
-
- def explicit_multipart_with_one_template(hash = {})
- mail(hash) do |format|
- format.html
- format.text
- end
- end
-
- def implicit_different_template(template_name='')
- mail(:template_name => template_name)
- end
-
- def explicit_different_template(template_name='')
- mail do |format|
- format.text { render :template => "#{mailer_name}/#{template_name}" }
- format.html { render :template => "#{mailer_name}/#{template_name}" }
- end
- end
-
- def different_layout(layout_name='')
- mail do |format|
- format.text { render :layout => layout_name }
- format.html { render :layout => layout_name }
- end
- end
- end
-
- class ProcMailer < ActionMailer::Base
- default :to => 'system@test.lindsaar.net',
- 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s },
- :subject => Proc.new { give_a_greeting }
-
- def welcome
- mail
- end
-
- private
-
- def give_a_greeting
- "Thanks for signing up this afternoon"
- end
-
+ def teardown
+ ActionMailer::Base.asset_host = nil
+ ActionMailer::Base.assets_dir = nil
end
test "method call to mail does not raise error" do
@@ -570,6 +448,26 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Welcome from another path", mail.body.encoded)
end
+ test "assets tags should use ActionMailer's asset_host settings" do
+ ActionMailer::Base.config.asset_host = "http://global.com"
+ ActionMailer::Base.config.assets_dir = "global/"
+
+ mail = AssetMailer.welcome
+
+ assert_equal(%{<img alt="Dummy" src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip)
+ end
+
+ test "assets tags should use a Mailer's asset_host settings when available" do
+ ActionMailer::Base.config.asset_host = "global.com"
+ ActionMailer::Base.config.assets_dir = "global/"
+
+ AssetMailer.asset_host = "http://local.com"
+
+ mail = AssetMailer.welcome
+
+ assert_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
+ end
+
# Before and After hooks
class MyObserver
@@ -609,6 +507,18 @@ class BaseTest < ActiveSupport::TestCase
assert_equal("Thanks for signing up this afternoon", mail.subject)
end
+ test "action methods should be refreshed after defining new method" do
+ class FooMailer < ActionMailer::Base
+ # this triggers action_methods
+ self.respond_to?(:foo)
+
+ def notify
+ end
+ end
+
+ assert_equal ["notify"], FooMailer.action_methods
+ end
+
protected
# Execute the block setting the given values and restoring old values after
diff --git a/actionmailer/test/fixtures/asset_mailer/welcome.html.erb b/actionmailer/test/fixtures/asset_mailer/welcome.html.erb
new file mode 100644
index 0000000000..90d26130d9
--- /dev/null
+++ b/actionmailer/test/fixtures/asset_mailer/welcome.html.erb
@@ -0,0 +1 @@
+<%= image_tag "dummy.png" %> \ No newline at end of file
diff --git a/actionmailer/test/mailers/asset_mailer.rb b/actionmailer/test/mailers/asset_mailer.rb
new file mode 100644
index 0000000000..f54a50d00d
--- /dev/null
+++ b/actionmailer/test/mailers/asset_mailer.rb
@@ -0,0 +1,7 @@
+class AssetMailer < ActionMailer::Base
+ self.mailer_name = "asset_mailer"
+
+ def welcome
+ mail
+ end
+end
diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb
new file mode 100644
index 0000000000..2c6de36ccf
--- /dev/null
+++ b/actionmailer/test/mailers/base_mailer.rb
@@ -0,0 +1,114 @@
+class BaseMailer < ActionMailer::Base
+ self.mailer_name = "base_mailer"
+
+ default :to => 'system@test.lindsaar.net',
+ :from => 'jose@test.plataformatec.com',
+ :reply_to => 'mikel@test.lindsaar.net'
+
+ def welcome(hash = {})
+ headers['X-SPAM'] = "Not SPAM"
+ mail({:subject => "The first email on new API!"}.merge!(hash))
+ end
+
+ def welcome_with_headers(hash = {})
+ headers hash
+ mail
+ end
+
+ def welcome_from_another_path(path)
+ mail(:template_name => "welcome", :template_path => path)
+ end
+
+ def html_only(hash = {})
+ mail(hash)
+ end
+
+ def plain_text_only(hash = {})
+ mail(hash)
+ end
+
+ def inline_attachment
+ attachments.inline['logo.png'] = "\312\213\254\232"
+ mail
+ end
+
+ def attachment_with_content(hash = {})
+ attachments['invoice.pdf'] = 'This is test File content'
+ mail(hash)
+ end
+
+ def attachment_with_hash
+ attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
+ :mime_type => "image/x-jpg",
+ :transfer_encoding => "base64" }
+ mail
+ end
+
+ def attachment_with_hash_default_encoding
+ attachments['invoice.jpg'] = { :data => "\312\213\254\232)b",
+ :mime_type => "image/x-jpg" }
+ mail
+ end
+
+ def implicit_multipart(hash = {})
+ attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
+ mail(hash)
+ end
+
+ def implicit_with_locale(hash = {})
+ mail(hash)
+ end
+
+ def explicit_multipart(hash = {})
+ attachments['invoice.pdf'] = 'This is test File content' if hash.delete(:attachments)
+ mail(hash) do |format|
+ format.text { render :text => "TEXT Explicit Multipart" }
+ format.html { render :text => "HTML Explicit Multipart" }
+ end
+ end
+
+ def explicit_multipart_templates(hash = {})
+ mail(hash) do |format|
+ format.html
+ format.text
+ end
+ end
+
+ def explicit_multipart_with_any(hash = {})
+ mail(hash) do |format|
+ format.any(:text, :html){ render :text => "Format with any!" }
+ end
+ end
+
+ def explicit_multipart_with_options(include_html = false)
+ mail do |format|
+ format.text(:content_transfer_encoding => "base64"){ render "welcome" }
+ format.html{ render "welcome" } if include_html
+ end
+ end
+
+ def explicit_multipart_with_one_template(hash = {})
+ mail(hash) do |format|
+ format.html
+ format.text
+ end
+ end
+
+ def implicit_different_template(template_name='')
+ mail(:template_name => template_name)
+ end
+
+ def explicit_different_template(template_name='')
+ mail do |format|
+ format.text { render :template => "#{mailer_name}/#{template_name}" }
+ format.html { render :template => "#{mailer_name}/#{template_name}" }
+ end
+ end
+
+ def different_layout(layout_name='')
+ mail do |format|
+ format.text { render :layout => layout_name }
+ format.html { render :layout => layout_name }
+ end
+ end
+end
diff --git a/actionmailer/test/mailers/proc_mailer.rb b/actionmailer/test/mailers/proc_mailer.rb
new file mode 100644
index 0000000000..6a79cd71fc
--- /dev/null
+++ b/actionmailer/test/mailers/proc_mailer.rb
@@ -0,0 +1,16 @@
+class ProcMailer < ActionMailer::Base
+ default :to => 'system@test.lindsaar.net',
+ 'X-Proc-Method' => Proc.new { Time.now.to_i.to_s },
+ :subject => Proc.new { give_a_greeting }
+
+ def welcome
+ mail
+ end
+
+ private
+
+ def give_a_greeting
+ "Thanks for signing up this afternoon"
+ end
+
+end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 32aba2091a..81abb8b5f1 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,4 +1,6 @@
-*Rails 3.0.0 [Release Candidate] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Allow stylesheet/javascript extensions to be changed through railties. [Josh Kalderimis]
* link_to, button_to, and tag/tag_options now rely on html_escape instead of escape_once. [fxn]
@@ -31,6 +33,7 @@
* Removed textilize, textilize_without_paragraph and markdown helpers. [Santiago Pastorino]
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* Remove middleware laziness [José Valim]
diff --git a/actionpack/README b/actionpack/README.rdoc
index 1a59f728cc..b297ceb0e2 100644
--- a/actionpack/README
+++ b/actionpack/README.rdoc
@@ -1,34 +1,35 @@
-= Action Pack -- On rails from request to response
-
-Action Pack splits the response to a web request into a controller part
-(performing the logic) and a view part (rendering a template). This two-step
-approach is known as an action, which will normally create, read, update, or
-delete (CRUD for short) some sort of model part (often backed by a database)
-before choosing either to render a template or redirecting to another action.
-
-Action Pack implements these actions as public methods on Action Controllers
-and uses Action Views to implement the template rendering. Action Controllers
-are then responsible for handling all the actions relating to a certain part
-of an application. This grouping usually consists of actions for lists and for
-CRUDs revolving around a single (or a few) model objects. So ContactsController
-would be responsible for listing contacts, creating, deleting, and updating
-contacts. A WeblogController could be responsible for both posts and comments.
-
-Action View templates are written using embedded Ruby in tags mingled in with
-the HTML. To avoid cluttering the templates with code, a bunch of helper
-classes provide common behavior for forms, dates, and strings. And it's easy
-to add specific helpers to keep the separation as the application evolves.
-
-Note: Some of the features, such as scaffolding and form building, are tied to
-ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
-mapping package), but that doesn't mean that Action Pack depends on Active
-Record. Action Pack is an independent package that can be used with any sort
-of backend (Instiki[http://www.instiki.org], which is based on an older version
-of Action Pack, used Madeleine for example). Read more about the role Action
-Pack can play when used together with Active Record on
-http://www.rubyonrails.org.
-
-A short rundown of the major features:
+= Action Pack -- From request to response
+
+Action Pack is a framework for handling and responding to web requests. It
+provides mechanisms for *routing* (mapping request URLs to actions), defining
+*controllers* that implement actions, and generating responses by rendering
+*views*, which are templates of various formats. In short, Action Pack
+provides the view and controller layers in the MVC paradigm.
+
+It consists of several modules:
+
+* Action Dispatch, which parses information about the web request, handles
+ routing as defined by the user, and does advanced processing related to HTTP
+ such as MIME-type negotiation, decoding parameters in POST/PUT bodies,
+ handling HTTP caching logic, cookies and sessions.
+
+* Action Controller, which provides a base controller class that can be
+ subclassed to implement filters and actions to handle requests. The result
+ of an action is typically content generated from views.
+
+* Action View, which handles view template lookup and rendering, and provides
+ view helpers that assist when building HTML forms, Atom feeds and more.
+ Template formats that Action View handles are ERb (embedded Ruby, typically
+ used to inline short Ruby snippets inside HTML), XML Builder and RJS
+ (dynamically generated JavaScript from Ruby code).
+
+With the Ruby on Rails framework, users only directly interface with the
+Action Controller module. Necessary Action Dispatch functionality is activated
+by default and Action View rendering is implicitly triggered by Action
+Controller. However, these modules are designed to function on their own and
+can be used outside of Rails.
+
+A short rundown of some of the major features:
* Actions grouped in controller as methods instead of separate command objects
and can therefore share helper methods
@@ -40,26 +41,29 @@ A short rundown of the major features:
def update
@customer = find_customer
- @customer.attributes = params[:customer]
- @customer.save ?
- redirect_to(:action => "show") :
- render(:action => "edit")
+ if @customer.update_attributes(params[:customer])
+ redirect_to :action => "show"
+ else
+ render :action => "edit"
+ end
end
private
- def find_customer() Customer.find(params[:id]) end
+ def find_customer
+ Customer.find params[:id]
+ end
end
{Learn more}[link:classes/ActionController/Base.html]
-* Embedded Ruby for templates (no new "easy" template language)
+* ERb templates (static content mixed with dynamic output from ruby)
<% for post in @posts %>
Title: <%= post.title %>
<% end %>
- All post titles: <%= @posts.collect{ |p| p.title }.join ", " %>
+ All post titles: <%= @posts.collect{ |p| p.title }.join(", ") %>
<% unless @person.is_client? %>
Not for clients to see...
@@ -68,7 +72,7 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionView.html]
-* Builder-based templates (great for XML content, like RSS)
+* "Builder" templates (great for XML content, like RSS)
xml.rss("version" => "2.0") do
xml.channel do
@@ -93,11 +97,16 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionView/Base.html]
-* Filters for pre and post processing of the response (as methods, procs, and classes)
+* Filters for pre- and post-processing of the response
class WeblogController < ActionController::Base
+ # filters as methods
before_filter :authenticate, :cache, :audit
+
+ # filter as a proc
after_filter { |c| c.response.body = Gzip::compress(c.response.body) }
+
+ # class filter
after_filter LocalizeFilter
def index
@@ -120,16 +129,14 @@ A short rundown of the major features:
* Helpers for forms, dates, action links, and text
- <%= text_field "post", "title", "size" => 30 %>
- <%= html_date_select(Date.today) %>
+ <%= text_field_tag "post", "title", "size" => 30 %>
<%= link_to "New post", :controller => "post", :action => "new" %>
<%= truncate(post.title, :length => 25) %>
{Learn more}[link:classes/ActionView/Helpers.html]
-* Layout sharing for template reuse (think simple version of Struts
- Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
+* Layout sharing for template reuse
class WeblogController < ActionController::Base
layout "weblog_layout"
@@ -150,22 +157,22 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
-* Routing makes pretty urls incredibly easy
+* Routing makes pretty URLs incredibly easy
- map.connect 'clients/:client_name/:project_name/:controller/:action'
+ match 'clients/:client_name/:project_name/:controller/:action'
- Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
- { "client_name" => "37signals", "project_name" => "basecamp" } in params[:params]
+ Accessing "/clients/37signals/basecamp/project/index" calls ProjectController#index with
+ { "client_name" => "37signals", "project_name" => "basecamp" } in `params`
- From that URL, you can rewrite the redirect in a number of ways:
+ From that action, you can write the redirect in a number of ways:
redirect_to(:action => "edit") =>
- /clients/37signals/basecamp/project/dash
+ /clients/37signals/basecamp/project/edit
redirect_to(:client_name => "nextangle", :project_name => "rails") =>
- /clients/nextangle/rails/project/dash
+ /clients/nextangle/rails/project/index
- {Learn more}[link:classes/ActionController/Base.html]
+ {Learn more}[link:classes/ActionDispatch/Routing.html]
* Easy testing of both controller and rendered template through ActionController::TestCase
@@ -242,62 +249,6 @@ A short rundown of the major features:
{Learn more}[link:classes/ActionController/Rescue.html]
-* Scaffolding for Active Record model objects
-
- class AccountController < ActionController::Base
- scaffold :account
- end
-
- The AccountController now has the full CRUD range of actions and default
- templates: list, show, destroy, new, create, edit, update
-
- {Learn more}[link:classes/ActionController/Scaffolding/ClassMethods.html]
-
-
-* Form building for Active Record model objects
-
- The post object has a title (varchar), content (text), and
- written_on (date)
-
- <%= form "post" %>
-
- ...will generate something like (the selects will have more options, of
- course):
-
- <form action="create" method="POST">
- <p>
- <b>Title:</b><br/>
- <input type="text" name="post[title]" value="<%= @post.title %>" />
- </p>
- <p>
- <b>Content:</b><br/>
- <textarea name="post[content]"><%= @post.title %></textarea>
- </p>
- <p>
- <b>Written on:</b><br/>
- <select name='post[written_on(3i)]'><option>18</option></select>
- <select name='post[written_on(2i)]'><option value='7'>July</option></select>
- <select name='post[written_on(1i)]'><option>2004</option></select>
- </p>
-
- <input type="submit" value="Create">
- </form>
-
- This form generates a params[:post] array that can be used directly in a save action:
-
- class WeblogController < ActionController::Base
- def create
- post = Post.create(params[:post])
- redirect_to :action => "show", :id => post.id
- end
- end
-
- {Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
-
-
-* Runs on top of WEBrick, Mongrel, CGI, FCGI, and mod_ruby
-
-
== Simple example (from outside of Rails)
This example will implement a simple weblog system using inline templates and
@@ -364,24 +315,15 @@ new model). After creating the post, it'll redirect to the show page using
an URL such as /weblog/5 (where 5 is the id of the post).
-== Download
+== Download and installation
-The latest version of Action Pack can be found at
+The latest version of Action Pack can be installed with Rubygems:
-* http://rubyforge.org/project/showfiles.php?group_id=249
+ % [sudo] gem install actionpack
-Documentation can be found at
+Source code can be downloaded as part of the Rails project on GitHub
-* http://api.rubyonrails.com
-
-
-== Installation
-
-You can install Action Pack with the following command.
-
- % [sudo] ruby install.rb
-
-from its distribution directory.
+* http://github.com/rails/rails/tree/master/actionpack/
== License
@@ -391,10 +333,10 @@ Action Pack is released under the MIT license.
== Support
-The Action Pack homepage is http://www.rubyonrails.org. You can find
-the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
-And as Jim from Rake says:
+API documentation is at
+
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
- Feel free to submit commits or feature requests. If you send a patch,
- remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index aed5278e38..4af8ea167a 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
@@ -38,16 +38,16 @@ end
# Genereate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Pack -- On rails from request to response"
- rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
if ENV['DOC_FILES']
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
else
- rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include(Dir['lib/**/*.rb'] -
Dir['lib/*/vendor/**/*.rb'])
rdoc.rdoc_files.exclude('lib/actionpack.rb')
@@ -89,4 +89,4 @@ task :lines do
end
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
-end \ No newline at end of file
+end
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index df0b5ac9a2..99deff234c 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'actionpack'
- s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.4.1')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.4')
- #s.add_dependency('rack-mount', '~> 0.6.6')
- s.add_dependency('tzinfo', '~> 0.3.16')
+ s.add_dependency('rack-mount', '~> 0.6.9')
+ s.add_dependency('tzinfo', '~> 0.3.22')
s.add_dependency('erubis', '~> 2.6.6')
end
diff --git a/actionpack/install.rb b/actionpack/install.rb
deleted file mode 100644
index d3b83c3b00..0000000000
--- a/actionpack/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by way of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-} \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb
index 5990a1bbd0..c565c940a1 100644
--- a/actionpack/lib/abstract_controller.rb
+++ b/actionpack/lib/abstract_controller.rb
@@ -1,6 +1,7 @@
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
+require 'action_pack'
require 'active_support/ruby/shim'
require 'active_support/dependencies/autoload'
require 'active_support/core_ext/class/attribute'
diff --git a/actionpack/lib/abstract_controller/asset_paths.rb b/actionpack/lib/abstract_controller/asset_paths.rb
index 6d6f6ac607..9ca2fb742f 100644
--- a/actionpack/lib/abstract_controller/asset_paths.rb
+++ b/actionpack/lib/abstract_controller/asset_paths.rb
@@ -3,7 +3,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- config_accessor :assets_dir, :javascripts_dir, :stylesheets_dir
+ config_accessor :asset_host, :asset_path, :assets_dir, :javascripts_dir, :stylesheets_dir
end
end
end \ No newline at end of file
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 8a8337858b..db0a6736e0 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -72,6 +72,13 @@ module AbstractController
end
end
+ # action_methods are cached and there is sometimes need to refresh
+ # them. clear_action_methods! allows you to do that, so next time
+ # you run action_methods, they will be recalculated
+ def clear_action_methods!
+ @action_methods = nil
+ end
+
# Returns the full controller name, underscored, without the ending Controller.
# For instance, MyApp::MyPostsController would return "my_app/my_posts" for
# controller_name.
@@ -81,6 +88,11 @@ module AbstractController
def controller_path
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
end
+
+ def method_added(name)
+ super
+ clear_action_methods!
+ end
end
abstract!
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 1bd4572a47..ca0e5d6ff6 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -6,7 +6,6 @@ module ActionController
autoload :Base
autoload :Caching
- autoload :PolymorphicRoutes
autoload :Metal
autoload :Middleware
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 1a2cbaab65..9dfffced75 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -28,7 +28,6 @@ module ActionController
SessionManagement,
Caching,
MimeResponds,
- PolymorphicRoutes,
ImplicitRender,
Cookies,
@@ -64,9 +63,8 @@ module ActionController
klass.helper :all
end
- config_accessor :asset_host, :asset_path
ActiveSupport.run_load_hooks(:action_controller, self)
end
end
-require "action_controller/deprecated/base" \ No newline at end of file
+require "action_controller/deprecated/base"
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index be7448ce01..b0eb24a4a8 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -63,7 +63,7 @@ module ActionController
# def test_access_granted_from_xml
# get(
# "/notes/1.xml", nil,
- # :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
+ # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
# )
#
# assert_equal 200, status
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index b2c119d7e4..b08d9a8434 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -1,3 +1,4 @@
+require 'benchmark'
require 'abstract_controller/logger'
module ActionController
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 4f384d1ec5..c6d4c6d936 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -12,27 +12,30 @@ module ActionController #:nodoc:
end
module ClassMethods
- # Defines mimes that are rendered by default when invoking respond_with.
+ # Defines mime types that are rendered by default when invoking
+ # <tt>respond_with</tt>.
#
# Examples:
#
# respond_to :html, :xml, :json
#
- # All actions on your controller will respond to :html, :xml and :json.
+ # Specifies that all actions in the controller respond to requests
+ # for <tt>:html</tt>, <tt>:xml</tt> and <tt>:json</tt>.
#
- # But if you want to specify it based on your actions, you can use only and
- # except:
+ # To specify on per-action basis, use <tt>:only</tt> and
+ # <tt>:except</tt> with an array of actions or a single action:
#
# respond_to :html
# respond_to :xml, :json, :except => [ :edit ]
#
- # The definition above explicits that all actions respond to :html. And all
- # actions except :edit respond to :xml and :json.
- #
- # You can specify also only parameters:
+ # This specifies that all actions respond to <tt>:html</tt>
+ # and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
+ # <tt>:json</tt>.
#
# respond_to :rjs, :only => :create
#
+ # This specifies that the <tt>:create</tt> action and no other responds
+ # to <tt>:rjs</tt>.
def respond_to(*mimes)
options = mimes.extract_options!
@@ -49,7 +52,7 @@ module ActionController #:nodoc:
self.mimes_for_respond_to = new.freeze
end
- # Clear all mimes in respond_to.
+ # Clear all mime types in <tt>respond_to</tt>.
#
def clear_respond_to
self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
@@ -145,7 +148,7 @@ module ActionController #:nodoc:
# and accept Rails' defaults, life will be much easier.
#
# If you need to use a MIME type which isn't supported by default, you can register your own handlers in
- # environment.rb as follows.
+ # config/initializers/mime_types.rb as follows.
#
# Mime::Type.register "image/jpg", :jpg
#
diff --git a/actionpack/lib/action_controller/metal/rescue.rb b/actionpack/lib/action_controller/metal/rescue.rb
index bbca1b2179..eb037aa1b0 100644
--- a/actionpack/lib/action_controller/metal/rescue.rb
+++ b/actionpack/lib/action_controller/metal/rescue.rb
@@ -3,6 +3,15 @@ module ActionController #:nodoc:
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
+ def rescue_with_handler(exception)
+ if (exception.respond_to?(:original_exception) &&
+ (orig_exception = exception.original_exception) &&
+ handler_for_rescue(orig_exception))
+ exception = orig_exception
+ end
+ super(exception)
+ end
+
private
def process_action(*args)
super
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 753af3dc58..d75b46dace 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/file/path'
+
module ActionController #:nodoc:
# Methods for sending arbitrary data and for streaming files to the browser,
# instead of rendering.
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
deleted file mode 100644
index bee50a7a3b..0000000000
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ /dev/null
@@ -1,183 +0,0 @@
-module ActionController
- # Polymorphic URL helpers are methods for smart resolution to a named route call when
- # given an Active Record model instance. They are to be used in combination with
- # ActionController::Resources.
- #
- # These methods are useful when you want to generate correct URL or path to a RESTful
- # resource without having to know the exact type of the record in question.
- #
- # Nested resources and/or namespaces are also supported, as illustrated in the example:
- #
- # polymorphic_url([:admin, @article, @comment])
- #
- # results in:
- #
- # admin_article_comment_url(@article, @comment)
- #
- # == Usage within the framework
- #
- # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
- #
- # * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
- # <tt>url_for(@article)</tt>;
- # * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
- # <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
- # action;
- # * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
- # <tt>redirect_to(post)</tt> in your controllers;
- # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
- # for feed entries.
- #
- # == Prefixed polymorphic helpers
- #
- # In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
- # number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
- # in options. Those are:
- #
- # * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
- # * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
- #
- # Example usage:
- #
- # edit_polymorphic_path(@post) # => "/posts/1/edit"
- # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
- module PolymorphicRoutes
- # Constructs a call to a named RESTful route for the given record and returns the
- # resulting URL string. For example:
- #
- # # calls post_url(post)
- # polymorphic_url(post) # => "http://example.com/posts/1"
- # 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
- #
- # * <tt>:action</tt> - Specifies the action prefix for the named route:
- # <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
- # * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
- # Default is <tt>:url</tt>.
- #
- # ==== Examples
- #
- # # an Article record
- # polymorphic_url(record) # same as article_url(record)
- #
- # # a Comment record
- # polymorphic_url(record) # same as comment_url(record)
- #
- # # it recognizes new records and maps to the collection
- # 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
- record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
- end
-
- record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
-
- args = case record_or_hash_or_array
- when Hash; [ record_or_hash_or_array ]
- when Array; record_or_hash_or_array.dup
- else [ record_or_hash_or_array ]
- end
-
- inflection = if options[:action].to_s == "new"
- args.pop
- :singular
- elsif (record.respond_to?(:persisted?) && !record.persisted?)
- 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)}
- named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
-
- url_options = options.except(:action, :routing_type)
- unless url_options.empty?
- args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
- end
-
- __send__(named_route, *args)
- end
-
- # Returns the path component of a URL for the given record. It uses
- # <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
- def polymorphic_path(record_or_hash_or_array, options = {})
- polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
- end
-
- %w(edit new).each do |action|
- module_eval <<-EOT, __FILE__, __LINE__ + 1
- def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
- end # end
- #
- def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
- polymorphic_url( # polymorphic_url(
- record_or_hash, # record_or_hash,
- options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
- end # end
- EOT
- end
-
- private
- def action_prefix(options)
- options[:action] ? "#{options[:action]}_" : ''
- end
-
- def routing_type(options)
- options[:routing_type] || :url
- end
-
- def build_named_route_call(records, inflection, options = {})
- unless records.is_a?(Array)
- record = extract_record(records)
- route = ''
- else
- record = records.pop
- route = records.inject("") do |string, parent|
- if parent.is_a?(Symbol) || parent.is_a?(String)
- string << "#{parent}_"
- else
- string << RecordIdentifier.__send__("plural_class_name", parent).singularize
- string << "_"
- end
- end
- end
-
- if record.is_a?(Symbol) || record.is_a?(String)
- route << "#{record}_"
- else
- route << RecordIdentifier.__send__("plural_class_name", record)
- route = route.singularize if inflection == :singular
- route << "_"
- route << "index_" if RecordIdentifier.uncountable?(record) && inflection == :plural
- end
-
- action_prefix(options) + route + routing_type(options).to_s
- end
-
- def extract_record(record_or_hash_or_array)
- case record_or_hash_or_array
- when Array; record_or_hash_or_array.last
- when Hash; record_or_hash_or_array[:id]
- else record_or_hash_or_array
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 9261422f0b..cd2dfafbe6 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -5,8 +5,6 @@ require "action_view/railtie"
require "active_support/deprecation/proxy_wrappers"
require "active_support/deprecation"
-require "action_controller/railties/url_helpers"
-
module ActionController
class Railtie < Rails::Railtie
config.action_controller = ActiveSupport::OrderedOptions.new
@@ -33,21 +31,6 @@ module ActionController
end
end
- initializer "action_controller.set_configs" do |app|
- paths = app.config.paths
- ac = app.config.action_controller
-
- ac.assets_dir ||= paths.public.to_a.first
- ac.javascripts_dir ||= paths.public.javascripts.to_a.first
- ac.stylesheets_dir ||= paths.public.stylesheets.to_a.first
- ac.page_cache_directory ||= paths.public.to_a.first
- ac.helpers_path ||= paths.app.helpers.to_a
-
- ActiveSupport.on_load(:action_controller) do
- self.config.merge!(ac)
- end
- end
-
initializer "action_controller.logger" do
ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger }
end
@@ -56,11 +39,23 @@ module ActionController
ActiveSupport.on_load(:action_controller) { self.cache_store ||= RAILS_CACHE }
end
- initializer "action_controller.url_helpers" do |app|
+ initializer "action_controller.set_configs" do |app|
+ paths = app.config.paths
+ options = app.config.action_controller
+
+ options.assets_dir ||= paths.public.to_a.first
+ options.javascripts_dir ||= paths.public.javascripts.to_a.first
+ options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
+ options.page_cache_directory ||= paths.public.to_a.first
+ options.helpers_path ||= paths.app.helpers.to_a
+
ActiveSupport.on_load(:action_controller) do
- extend ::ActionController::Railties::UrlHelpers.with(app.routes)
+ include app.routes.url_helpers
+ options.each { |k,v| send("#{k}=", v) }
end
+ end
+ initializer "action_controller.deprecated_routes" do |app|
message = "ActionController::Routing::Routes is deprecated. " \
"Instead, use Rails.application.routes"
diff --git a/actionpack/lib/action_controller/railties/url_helpers.rb b/actionpack/lib/action_controller/railties/url_helpers.rb
deleted file mode 100644
index 9df5665542..0000000000
--- a/actionpack/lib/action_controller/railties/url_helpers.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-module ActionController
- module Railties
- module UrlHelpers
- def self.with(routes)
- Module.new do
- define_method(:inherited) do |klass|
- super(klass)
- klass.send(:include, routes.url_helpers)
- end
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index d20c3b64c5..3de40b0de3 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -46,7 +46,7 @@ module ActionController
# dom_class(post, :edit) # => "edit_post"
# dom_class(Person, :edit) # => "edit_person"
def dom_class(record_or_class, prefix = nil)
- singular = singular_class_name(record_or_class)
+ singular = ActiveModel::Naming.singular(record_or_class)
prefix ? "#{prefix}#{JOIN}#{singular}" : singular
end
@@ -67,6 +67,8 @@ module ActionController
end
end
+ protected
+
# Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id.
# This can be overwritten to customize the default generated string representation if desired.
# If you need to read back a key from a dom_id in order to query for the underlying database record,
@@ -85,34 +87,5 @@ module ActionController
def sanitize_dom_id(candidate_id)
candidate_id # TODO implement conversion to valid DOM id values
end
-
- # Returns the plural class name of a record or class. Examples:
- #
- # plural_class_name(post) # => "posts"
- # plural_class_name(Highrise::Person) # => "highrise_people"
- def plural_class_name(record_or_class)
- model_name_from_record_or_class(record_or_class).plural
- end
-
- # Returns the singular class name of a record or class. Examples:
- #
- # singular_class_name(post) # => "post"
- # singular_class_name(Highrise::Person) # => "highrise_person"
- def singular_class_name(record_or_class)
- model_name_from_record_or_class(record_or_class).singular
- end
-
- # Identifies whether the class name of a record or class is uncountable. Examples:
- #
- # uncountable?(Sheep) # => true
- # uncountable?(Post) => false
- def uncountable?(record_or_class)
- plural_class_name(record_or_class) == singular_class_name(record_or_class)
- end
-
- private
- def model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
- end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 650eb16ac0..e306697f4b 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -365,7 +365,7 @@ module ActionController
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
- returning __send__(request_method, action, parameters, session, flash) do
+ __send__(request_method, action, parameters, session, flash).tap do
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index cdf81c6648..aeec934be8 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -24,9 +24,14 @@
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
+activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
+$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
+
require 'active_support'
require 'active_support/dependencies/autoload'
+require 'action_pack'
+require 'active_model'
require 'rack'
module Rack
@@ -42,6 +47,7 @@ module ActionDispatch
end
autoload_under 'middleware' do
+ autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
autoload :Flash
@@ -63,6 +69,7 @@ module ActionDispatch
autoload :Headers
autoload :MimeNegotiation
autoload :Parameters
+ autoload :ParameterFilter
autoload :FilterParameters
autoload :Upload
autoload :UploadedFile, 'action_dispatch/http/upload'
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 152aaa2e67..1ab48ae04d 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -26,88 +26,32 @@ module ActionDispatch
module FilterParameters
extend ActiveSupport::Concern
- @@compiled_parameter_filter_for = {}
+ @@parameter_filter_for = {}
# Return a hash of parameters with all sensitive data replaced.
def filtered_parameters
- @filtered_parameters ||= if filtering_parameters?
- process_parameter_filter(parameters)
- else
- parameters.dup
- end
+ @filtered_parameters ||= parameter_filter.filter(parameters)
end
- alias :fitered_params :filtered_parameters
# Return a hash of request.env with all sensitive data replaced.
def filtered_env
- filtered_env = @env.dup
- filtered_env.each do |key, value|
- if (key =~ /RAW_POST_DATA/i)
- filtered_env[key] = '[FILTERED]'
- elsif value.is_a?(Hash)
- filtered_env[key] = process_parameter_filter(value)
- end
- end
- filtered_env
+ @filtered_env ||= env_filter.filter(@env)
end
protected
- def filtering_parameters? #:nodoc:
- @env["action_dispatch.parameter_filter"].present?
+ def parameter_filter
+ parameter_filter_for(@env["action_dispatch.parameter_filter"])
end
- def process_parameter_filter(params) #:nodoc:
- compiled_parameter_filter_for(@env["action_dispatch.parameter_filter"]).call(params)
+ def env_filter
+ parameter_filter_for(Array.wrap(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/)
end
- def compile_parameter_filter(filters) #:nodoc:
- strings, regexps, blocks = [], [], []
-
- filters.each do |item|
- case item
- when NilClass
- when Proc
- blocks << item
- when Regexp
- regexps << item
- else
- strings << item.to_s
- end
- end
-
- regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
- [regexps, blocks]
- end
-
- def compiled_parameter_filter_for(filters) #:nodoc:
- @@compiled_parameter_filter_for[filters] ||= begin
- regexps, blocks = compile_parameter_filter(filters)
-
- lambda do |original_params|
- filtered_params = {}
-
- original_params.each do |key, value|
- if regexps.find { |r| key =~ r }
- value = '[FILTERED]'
- elsif value.is_a?(Hash)
- value = process_parameter_filter(value)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? process_parameter_filter(v) : v }
- elsif blocks.present?
- key = key.dup
- value = value.dup if value.duplicable?
- blocks.each { |b| b.call(key, value) }
- end
-
- filtered_params[key] = value
- end
-
- filtered_params
- end
- end
+ def parameter_filter_for(filters)
+ @@parameter_filter_for[filters] ||= ParameterFilter.new(filters)
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/http/parameter_filter.rb b/actionpack/lib/action_dispatch/http/parameter_filter.rb
new file mode 100644
index 0000000000..1480e8f77c
--- /dev/null
+++ b/actionpack/lib/action_dispatch/http/parameter_filter.rb
@@ -0,0 +1,72 @@
+module ActionDispatch
+ module Http
+ class ParameterFilter
+
+ def initialize(filters)
+ @filters = filters
+ end
+
+ def filter(params)
+ if enabled?
+ compiled_filter.call(params)
+ else
+ params.dup
+ end
+ end
+
+ private
+
+ def enabled?
+ @filters.present?
+ end
+
+ def compiled_filter
+ @compiled_filter ||= begin
+ regexps, blocks = compile_filter
+
+ lambda do |original_params|
+ filtered_params = {}
+
+ original_params.each do |key, value|
+ if regexps.find { |r| key =~ r }
+ value = '[FILTERED]'
+ elsif value.is_a?(Hash)
+ value = filter(value)
+ elsif value.is_a?(Array)
+ value = value.map { |v| v.is_a?(Hash) ? filter(v) : v }
+ elsif blocks.present?
+ key = key.dup
+ value = value.dup if value.duplicable?
+ blocks.each { |b| b.call(key, value) }
+ end
+
+ filtered_params[key] = value
+ end
+
+ filtered_params
+ end
+ end
+ end
+
+ def compile_filter
+ strings, regexps, blocks = [], [], []
+
+ @filters.each do |item|
+ case item
+ when NilClass
+ when Proc
+ blocks << item
+ when Regexp
+ regexps << item
+ else
+ strings << item.to_s
+ end
+ end
+
+ regexps << Regexp.new(strings.join('|'), true) unless strings.empty?
+ [regexps, blocks]
+ end
+
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/best_standards_support.rb b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
new file mode 100644
index 0000000000..69adcc419f
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/best_standards_support.rb
@@ -0,0 +1,22 @@
+module ActionDispatch
+ class BestStandardsSupport
+ def initialize(app, type = true)
+ @app = app
+
+ @header = case type
+ when true
+ "IE=Edge,chrome=1"
+ when :builtin
+ "IE=Edge"
+ when false
+ nil
+ end
+ end
+
+ def call(env)
+ status, headers, body = @app.call(env)
+ headers["X-UA-Compatible"] = @header
+ [status, headers, body]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index d69ba39728..4d33cd3b0c 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -45,10 +45,10 @@ module ActionDispatch
# * <tt>:value</tt> - The cookie's value or list of values (as an array).
# * <tt>:path</tt> - The path for which this cookie applies. Defaults to the root
# of the application.
- # * <tt>:domain</tt> - The domain for which this cookie applies so you can
- # restrict to the domain level. If you use a schema like www.example.com
+ # * <tt>:domain</tt> - The domain for which this cookie applies so you can
+ # restrict to the domain level. If you use a schema like www.example.com
# and want to share session with user.example.com set <tt>:domain</tt>
- # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
+ # to <tt>:all</tt>. Make sure to specify the <tt>:domain</tt> option with
# <tt>:all</tt> again when deleting keys.
#
# :domain => nil # Does not sets cookie domain. (default)
@@ -63,7 +63,7 @@ module ActionDispatch
class Cookies
HTTP_HEADER = "Set-Cookie".freeze
TOKEN_KEY = "action_dispatch.secret_token".freeze
-
+
# Raised when storing more than 4K of session data.
class CookieOverflow < StandardError; end
@@ -101,7 +101,7 @@ module ActionDispatch
def handle_options(options) #:nodoc:
options[:path] ||= "/"
-
+
if options[:domain] == :all
@host =~ DOMAIN_REGEXP
options[:domain] = ".#{$2}.#{$3}"
@@ -122,7 +122,7 @@ module ActionDispatch
value = super(key.to_s, value)
handle_options(options)
-
+
@set_cookies[key] = options
@delete_cookies.delete(key)
value
@@ -151,7 +151,7 @@ module ActionDispatch
# This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples:
#
# cookies.permanent.signed[:remember_me] = current_user.id
- # # => Set-Cookie: discount=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
+ # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT
def permanent
@permanent ||= PermanentCookieJar.new(self, @secret)
end
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 64f4d1d532..dd82294644 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -24,9 +24,9 @@ module ActionDispatch
def [](key)
if key == :id
- load_session_id! unless super(:id) || has_session_id?
+ load_session_id! unless key?(:id) || has_session_id?
end
- super(key)
+ super
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
index dce47c63bc..ca1494425f 100644
--- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
@@ -35,7 +35,7 @@ module ActionDispatch
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
#
# To generate a secret key for an existing application, run
- # "rake secret" and set the key in config/environment.rb.
+ # "rake secret" and set the key in config/initializers/secret_token.rb.
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < AbstractStore
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index 4618f3befc..41078eced7 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -46,7 +46,7 @@ module ActionDispatch
end
def insert(index, *args, &block)
- index = self.index(index) unless index.is_a?(Integer)
+ index = assert_index(index, :before)
middleware = self.class::Middleware.new(*args, &block)
super(index, middleware)
end
@@ -54,9 +54,8 @@ module ActionDispatch
alias_method :insert_before, :insert
def insert_after(index, *args, &block)
- i = index.is_a?(Integer) ? index : self.index(index)
- raise "No such middleware to insert after: #{index.inspect}" unless i
- insert(i + 1, *args, &block)
+ index = assert_index(index, :after)
+ insert(index + 1, *args, &block)
end
def swap(target, *args, &block)
@@ -79,5 +78,13 @@ module ActionDispatch
raise "MiddlewareStack#build requires an app" unless app
reverse.inject(app) { |a, e| e.build(a) }
end
+
+ protected
+
+ def assert_index(index, where)
+ i = index.is_a?(Integer) ? index : self.index(index)
+ raise "No such middleware to insert #{where}: #{index.inspect}" unless i
+ i
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb
index ed93211255..a3af37947a 100644
--- a/actionpack/lib/action_dispatch/railtie.rb
+++ b/actionpack/lib/action_dispatch/railtie.rb
@@ -7,10 +7,11 @@ module ActionDispatch
config.action_dispatch.x_sendfile_header = ""
config.action_dispatch.ip_spoofing_check = true
config.action_dispatch.show_exceptions = true
+ config.action_dispatch.best_standards_support = true
# Prepare dispatcher callbacks and run 'prepare' callbacks
initializer "action_dispatch.prepare_dispatcher" do |app|
ActionDispatch::Callbacks.to_prepare { app.routes_reloader.execute_if_updated }
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index c664fb0bc2..683dd72555 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
-require 'action_controller/polymorphic_routes'
module ActionDispatch
# = Routing
@@ -31,7 +30,7 @@ module ActionDispatch
# Think of creating routes as drawing a map for your requests. The map tells
# them where to go based on some predefined pattern:
#
- # AppName::Application.routes.draw do |map|
+ # AppName::Application.routes.draw do
# Pattern 1 tells some request to go to one place
# Pattern 2 tell them to go to another
# ...
@@ -106,7 +105,7 @@ module ActionDispatch
# You can specify a regular expression to define a format for a parameter.
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show', :constraints => {
+ # match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /\d{5}(-\d{4})?/
# }
#
@@ -114,13 +113,13 @@ module ActionDispatch
# expression modifiers:
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show', :constraints => {
+ # match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# controller 'geocode' do
- # match 'geocode/:postalcode' => :show', :constraints => {
+ # match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
@@ -217,13 +216,14 @@ module ActionDispatch
autoload :Route, 'action_dispatch/routing/route'
autoload :RouteSet, 'action_dispatch/routing/route_set'
autoload :UrlFor, 'action_dispatch/routing/url_for'
+ autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
# A helper module to hold URL related helpers.
module Helpers #:nodoc:
- include ActionController::PolymorphicRoutes
+ include PolymorphicRoutes
end
end
end
diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
index b3146a1c60..e04062ce8b 100644
--- a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/with_options'
+require 'active_support/core_ext/object/try'
module ActionDispatch
module Routing
@@ -500,7 +501,7 @@ module ActionDispatch
end
def add_conditions_for(conditions, method)
- returning({:conditions => conditions.dup}) do |options|
+ {:conditions => conditions.dup}.tap do |options|
options[:conditions][:method] = method unless method == :any
end
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 430f6fc5a3..c118c72440 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1,3 +1,4 @@
+require 'erb'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
@@ -35,11 +36,12 @@ module ActionDispatch
end
class Mapping #:nodoc:
- IGNORE_OPTIONS = [:to, :as, :controller, :action, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
+ IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
def initialize(set, scope, args)
@set, @scope = set, scope
@path, @options = extract_path_and_options(args)
+ normalize_options!
end
def to_route
@@ -57,14 +59,6 @@ module ActionDispatch
path = args.first
end
- if @scope[:module] && options[:to] && !options[:to].is_a?(Proc)
- if options[:to].to_s.include?("#")
- options[:to] = "#{@scope[:module]}/#{options[:to]}"
- elsif @scope[:controller].nil?
- options[:to] = "#{@scope[:module]}##{options[:to]}"
- end
- end
-
if path.match(':controller')
raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
@@ -75,15 +69,19 @@ module ActionDispatch
options.reverse_merge!(:controller => /.+?/)
end
- path = normalize_path(path)
- path_without_format = path.sub(/\(\.:format\)$/, '')
+ [ normalize_path(path), options ]
+ end
+
+ def normalize_options!
+ path_without_format = @path.sub(/\(\.:format\)$/, '')
- if using_match_shorthand?(path_without_format, options)
- options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
- options[:as] ||= path_without_format[1..-1].gsub("/", "_")
+ if using_match_shorthand?(path_without_format, @options)
+ to_shorthand = @options[:to].blank?
+ @options[:to] ||= path_without_format[1..-1].sub(%r{/([^/]*)$}, '#\1')
+ @options[:as] ||= path_without_format[1..-1].gsub("/", "_")
end
- [ path, options ]
+ @options.merge!(default_controller_and_action(to_shorthand))
end
# match "account" => "account#index"
@@ -122,44 +120,43 @@ module ActionDispatch
def defaults
@defaults ||= (@options[:defaults] || {}).tap do |defaults|
- defaults.merge!(default_controller_and_action)
defaults.reverse_merge!(@scope[:defaults]) if @scope[:defaults]
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
end
end
- def default_controller_and_action
+ def default_controller_and_action(to_shorthand=nil)
if to.respond_to?(:call)
{ }
else
- defaults = case to
- when String
+ if to.is_a?(String)
controller, action = to.split('#')
- { :controller => controller, :action => action }
- when Symbol
- { :action => to.to_s }
- else
- {}
+ elsif to.is_a?(Symbol)
+ action = to.to_s
end
- defaults[:controller] ||= default_controller
- defaults[:action] ||= default_action
+ controller ||= default_controller
+ action ||= default_action
- defaults.delete(:controller) if defaults[:controller].blank? || defaults[:controller].is_a?(Regexp)
- defaults.delete(:action) if defaults[:action].blank? || defaults[:action].is_a?(Regexp)
+ unless controller.is_a?(Regexp) || to_shorthand
+ controller = [@scope[:module], controller].compact.join("/").presence
+ end
- defaults[:controller] = defaults[:controller].to_s if defaults.key?(:controller)
- defaults[:action] = defaults[:action].to_s if defaults.key?(:action)
+ controller = controller.to_s unless controller.is_a?(Regexp)
+ action = action.to_s unless action.is_a?(Regexp)
- if defaults[:controller].blank? && segment_keys.exclude?("controller")
+ if controller.blank? && segment_keys.exclude?("controller")
raise ArgumentError, "missing :controller"
end
- if defaults[:action].blank? && segment_keys.exclude?("action")
+ if action.blank? && segment_keys.exclude?("action")
raise ArgumentError, "missing :action"
end
- defaults
+ { :controller => controller, :action => action }.tap do |hash|
+ hash.delete(:controller) if hash[:controller].blank?
+ hash.delete(:action) if hash[:action].blank?
+ end
end
end
@@ -207,6 +204,8 @@ module ActionDispatch
def default_action
if @options[:action]
@options[:action]
+ elsif @scope[:action]
+ @scope[:action]
end
end
end
@@ -279,7 +278,6 @@ module ActionDispatch
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc { |params| path % params }
status = options[:status] || 301
- body = 'Moved Permanently'
lambda do |env|
req = Request.new(env)
@@ -292,11 +290,14 @@ module ActionDispatch
uri.host ||= req.host
uri.port ||= req.port unless req.port == 80
+ body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
+
headers = {
'Location' => uri.to_s,
'Content-Type' => 'text/html',
'Content-Length' => body.length.to_s
}
+
[ status, headers, [body] ]
end
end
@@ -424,7 +425,7 @@ module ActionDispatch
end
def merge_controller_scope(parent, child)
- @scope[:module] ? "#{@scope[:module]}/#{child}" : child
+ child
end
def merge_path_names_scope(parent, child)
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
new file mode 100644
index 0000000000..31dba835ac
--- /dev/null
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -0,0 +1,186 @@
+module ActionDispatch
+ module Routing
+ # Polymorphic URL helpers are methods for smart resolution to a named route call when
+ # given an Active Record model instance. They are to be used in combination with
+ # ActionController::Resources.
+ #
+ # These methods are useful when you want to generate correct URL or path to a RESTful
+ # resource without having to know the exact type of the record in question.
+ #
+ # Nested resources and/or namespaces are also supported, as illustrated in the example:
+ #
+ # polymorphic_url([:admin, @article, @comment])
+ #
+ # results in:
+ #
+ # admin_article_comment_url(@article, @comment)
+ #
+ # == Usage within the framework
+ #
+ # Polymorphic URL helpers are used in a number of places throughout the Rails framework:
+ #
+ # * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
+ # <tt>url_for(@article)</tt>;
+ # * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
+ # <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
+ # action;
+ # * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
+ # <tt>redirect_to(post)</tt> in your controllers;
+ # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
+ # for feed entries.
+ #
+ # == Prefixed polymorphic helpers
+ #
+ # In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
+ # number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
+ # in options. Those are:
+ #
+ # * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
+ # * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
+ #
+ # Example usage:
+ #
+ # edit_polymorphic_path(@post) # => "/posts/1/edit"
+ # polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
+ module PolymorphicRoutes
+ # Constructs a call to a named RESTful route for the given record and returns the
+ # resulting URL string. For example:
+ #
+ # # calls post_url(post)
+ # polymorphic_url(post) # => "http://example.com/posts/1"
+ # 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
+ #
+ # * <tt>:action</tt> - Specifies the action prefix for the named route:
+ # <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
+ # * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
+ # Default is <tt>:url</tt>.
+ #
+ # ==== Examples
+ #
+ # # an Article record
+ # polymorphic_url(record) # same as article_url(record)
+ #
+ # # a Comment record
+ # polymorphic_url(record) # same as comment_url(record)
+ #
+ # # it recognizes new records and maps to the collection
+ # 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
+ record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
+ end
+
+ record = extract_record(record_or_hash_or_array)
+ record = record.to_model if record.respond_to?(:to_model)
+
+ args = case record_or_hash_or_array
+ when Hash; [ record_or_hash_or_array ]
+ when Array; record_or_hash_or_array.dup
+ else [ record_or_hash_or_array ]
+ end
+
+ inflection = if options[:action].to_s == "new"
+ args.pop
+ :singular
+ elsif (record.respond_to?(:persisted?) && !record.persisted?)
+ 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)}
+ named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
+
+ url_options = options.except(:action, :routing_type)
+ unless url_options.empty?
+ args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
+ end
+
+ send(named_route, *args)
+ end
+
+ # Returns the path component of a URL for the given record. It uses
+ # <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
+ def polymorphic_path(record_or_hash_or_array, options = {})
+ polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
+ end
+
+ %w(edit new).each do |action|
+ module_eval <<-EOT, __FILE__, __LINE__ + 1
+ def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
+ end # end
+ #
+ def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
+ polymorphic_url( # polymorphic_url(
+ record_or_hash, # record_or_hash,
+ options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
+ end # end
+ EOT
+ end
+
+ private
+ def action_prefix(options)
+ options[:action] ? "#{options[:action]}_" : ''
+ end
+
+ def routing_type(options)
+ options[:routing_type] || :url
+ end
+
+ def build_named_route_call(records, inflection, options = {})
+ unless records.is_a?(Array)
+ record = extract_record(records)
+ route = ''
+ else
+ record = records.pop
+ route = records.inject("") do |string, parent|
+ if parent.is_a?(Symbol) || parent.is_a?(String)
+ string << "#{parent}_"
+ else
+ string << ActiveModel::Naming.plural(parent).singularize
+ string << "_"
+ end
+ end
+ end
+
+ if record.is_a?(Symbol) || record.is_a?(String)
+ route << "#{record}_"
+ else
+ route << ActiveModel::Naming.plural(record)
+ route = route.singularize if inflection == :singular
+ route << "_"
+ route << "index_" if ActiveModel::Naming.uncountable?(record) && inflection == :plural
+ end
+
+ action_prefix(options) + route + routing_type(options).to_s
+ end
+
+ def extract_record(record_or_hash_or_array)
+ case record_or_hash_or_array
+ when Array; record_or_hash_or_array.last
+ when Hash; record_or_hash_or_array[:id]
+ else record_or_hash_or_array
+ end
+ end
+ end
+ end
+end
+
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 1b1a221c60..d23b580d97 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -1,10 +1,8 @@
+require 'rack/mount'
require 'forwardable'
require 'active_support/core_ext/object/to_query'
require 'action_dispatch/routing/deprecated_mapper'
-$: << File.expand_path('../../vendor/rack-mount-0.6.6.pre', __FILE__)
-require 'rack/mount'
-
module ActionDispatch
module Routing
class RouteSet #:nodoc:
@@ -394,10 +392,9 @@ module ActionDispatch
end
def generate
- error = ActionController::RoutingError.new("No route matches #{options.inspect}")
path, params = @set.set.generate(:path_info, named_route, options, recall, opts)
- raise error unless path
+ raise_routing_error unless path
params.reject! {|k,v| !v }
@@ -406,7 +403,7 @@ module ActionDispatch
path << "?#{params.to_query}" if params.any?
"#{script_name}#{path}"
rescue Rack::Mount::RoutingError
- raise error
+ raise_routing_error
end
def opts
@@ -416,12 +413,17 @@ module ActionDispatch
elsif value.is_a?(Array)
value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
else
- Rack::Mount::Utils.escape_uri(value.to_param)
+ return nil unless param = value.to_param
+ param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/")
end
end
{:parameterize => parameterize}
end
+ def raise_routing_error
+ raise ActionController::RoutingError.new("No route matches #{options.inspect}")
+ end
+
def different_controller?
return false unless current_controller
controller.to_param != current_controller.to_param
@@ -455,7 +457,7 @@ module ActionDispatch
def url_for(options)
finalize!
- options = default_url_options.merge(options || {})
+ options = (options || {}).reverse_merge!(default_url_options)
handle_positional_args(options)
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index 980abd44df..ba93ff8630 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -20,7 +20,7 @@ module ActionDispatch
# <%= link_to('Click here', :controller => 'users',
# :action => 'new', :message => 'Welcome!') %>
#
- # #=> Generates a link to: /users/new?message=Welcome%21
+ # # Generates a link to /users/new?message=Welcome%21
#
# link_to, and all other functions that require URL generation functionality,
# actually use ActionController::UrlFor under the hood. And in particular,
@@ -82,6 +82,7 @@ module ActionDispatch
#
module UrlFor
extend ActiveSupport::Concern
+ include PolymorphicRoutes
included do
# TODO: with_routing extends @controller with url_helpers, trickling down to including this module which overrides its default_url_options
@@ -128,7 +129,7 @@ module ActionDispatch
when String
options
when nil, Hash
- _routes.url_for(url_options.merge((options || {}).symbolize_keys))
+ _routes.url_for((options || {}).reverse_merge!(url_options).symbolize_keys)
else
polymorphic_url(options)
end
diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb
index 0e4a92048f..822150b768 100644
--- a/actionpack/lib/action_dispatch/testing/assertions.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions.rb
@@ -1,7 +1,6 @@
module ActionDispatch
module Assertions
autoload :DomAssertions, 'action_dispatch/testing/assertions/dom'
- autoload :ModelAssertions, 'action_dispatch/testing/assertions/model'
autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector'
@@ -11,7 +10,6 @@ module ActionDispatch
included do
include DomAssertions
- include ModelAssertions
include ResponseAssertions
include RoutingAssertions
include SelectorAssertions
diff --git a/actionpack/lib/action_dispatch/testing/assertions/model.rb b/actionpack/lib/action_dispatch/testing/assertions/model.rb
deleted file mode 100644
index 46714418c6..0000000000
--- a/actionpack/lib/action_dispatch/testing/assertions/model.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActionDispatch
- module Assertions
- module ModelAssertions
- # Ensures that the passed record is valid by Active Record standards and
- # returns any error messages if it is not.
- #
- # ==== Examples
- #
- # # assert that a newly created record is valid
- # model = Model.new
- # assert_valid(model)
- #
- def assert_valid(record)
- ::ActiveSupport::Deprecation.warn("assert_valid is deprecated. Use assert record.valid? instead", caller)
- assert record.valid?, record.errors.full_messages.join("\n")
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 64eb6d8de7..b52795c575 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -1,6 +1,7 @@
require 'stringio'
require 'uri'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/object/try'
require 'rack/test'
require 'test/unit/assertions'
@@ -319,7 +320,7 @@ module ActionDispatch
reset! unless @integration_session
# reset the html_document variable, but only for new get/post calls
@html_document = nil unless %w(cookies assigns).include?(method)
- returning @integration_session.__send__(method, *args) do
+ @integration_session.__send__(method, *args).tap do
copy_session_variables!
end
end
@@ -362,7 +363,7 @@ module ActionDispatch
def method_missing(sym, *args, &block)
reset! unless @integration_session
if @integration_session.respond_to?(sym)
- returning @integration_session.__send__(sym, *args, &block) do
+ @integration_session.__send__(sym, *args, &block).tap do
copy_session_variables!
end
else
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb
deleted file mode 100644
index 9fbf707724..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'rack'
-
-module Rack #:nodoc:
- # A stackable dynamic tree based Rack router.
- #
- # Rack::Mount supports Rack's Cascade style of trying several routes until
- # it finds one that is not a 404. This allows multiple routes to be nested
- # or stacked on top of each other. Since the application endpoint can
- # trigger the router to continue matching, middleware can be used to add
- # arbitrary conditions to any route. This allows you to route based on
- # other request attributes, session information, or even data dynamically
- # pulled from a database.
- module Mount
- autoload :CodeGeneration, 'rack/mount/code_generation'
- autoload :GeneratableRegexp, 'rack/mount/generatable_regexp'
- autoload :Multimap, 'rack/mount/multimap'
- autoload :Prefix, 'rack/mount/prefix'
- autoload :RegexpWithNamedGroups, 'rack/mount/regexp_with_named_groups'
- autoload :Route, 'rack/mount/route'
- autoload :RouteSet, 'rack/mount/route_set'
- autoload :RoutingError, 'rack/mount/route_set'
- autoload :Strexp, 'rack/mount/strexp'
- autoload :Utils, 'rack/mount/utils'
- autoload :Version, 'rack/mount/version'
-
- module Analysis #:nodoc:
- autoload :Frequency, 'rack/mount/analysis/frequency'
- autoload :Histogram, 'rack/mount/analysis/histogram'
- autoload :Splitting, 'rack/mount/analysis/splitting'
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb
deleted file mode 100644
index 671258f807..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/frequency.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-require 'rack/mount/utils'
-
-module Rack::Mount
- module Analysis
- class Frequency #:nodoc:
- def initialize(*keys)
- clear
- keys.each { |key| self << key }
- end
-
- def clear
- @raw_keys = []
- @key_frequency = Analysis::Histogram.new
- self
- end
-
- def <<(key)
- raise ArgumentError unless key.is_a?(Hash)
- @raw_keys << key
- nil
- end
-
- def possible_keys
- @possible_keys ||= begin
- @raw_keys.map do |key|
- key.inject({}) { |requirements, (method, requirement)|
- process_key(requirements, method, requirement)
- requirements
- }
- end
- end
- end
-
- def process_key(requirements, method, requirement)
- if requirement.is_a?(Regexp)
- expression = Utils.parse_regexp(requirement)
-
- if expression.is_a?(Regin::Expression) && expression.anchored_to_line?
- expression = Regin::Expression.new(expression.reject { |e| e.is_a?(Regin::Anchor) })
- return requirements[method] = expression.to_s if expression.literal?
- end
- end
-
- requirements[method] = requirement
- end
-
- def report
- @report ||= begin
- possible_keys.each { |keys| keys.each_pair { |key, _| @key_frequency << key } }
- return [] if @key_frequency.count <= 1
- @key_frequency.keys_in_upper_quartile
- end
- end
-
- def expire!
- @possible_keys = @report = nil
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb
deleted file mode 100644
index 20aaa132f9..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/histogram.rb
+++ /dev/null
@@ -1,74 +0,0 @@
-module Rack::Mount
- module Analysis
- class Histogram < Hash #:nodoc:
- attr_reader :count
-
- def initialize
- @count = 0
- super(0)
- expire_caches!
- end
-
- def <<(value)
- @count += 1
- self[value] += 1 if value
- expire_caches!
- self
- end
-
- def sorted_by_frequency
- sort_by { |_, value| value }.reverse!
- end
-
- def max
- @max ||= values.max || 0
- end
-
- def min
- @min ||= values.min || 0
- end
-
- def mean
- @mean ||= calculate_mean
- end
-
- def standard_deviation
- @standard_deviation ||= calculate_standard_deviation
- end
-
- def upper_quartile_limit
- @upper_quartile_limit ||= calculate_upper_quartile_limit
- end
-
- def keys_in_upper_quartile
- @keys_in_upper_quartile ||= compute_keys_in_upper_quartile
- end
-
- private
- def calculate_mean
- count / size
- end
-
- def calculate_variance
- values.inject(0) { |sum, e| sum + (e - mean) ** 2 } / count.to_f
- end
-
- def calculate_standard_deviation
- Math.sqrt(calculate_variance)
- end
-
- def calculate_upper_quartile_limit
- mean + standard_deviation
- end
-
- def compute_keys_in_upper_quartile
- sorted_by_frequency.select { |_, value| value >= upper_quartile_limit }.map! { |key, _| key }
- end
-
- def expire_caches!
- @max = @min = @mean = @standard_deviation = nil
- @keys_in_upper_quartile = nil
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb
deleted file mode 100644
index 8a8c551302..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/analysis/splitting.rb
+++ /dev/null
@@ -1,159 +0,0 @@
-require 'rack/mount/utils'
-
-module Rack::Mount
- module Analysis
- class Splitting < Frequency
- NULL = "\0".freeze
-
- class Key < Struct.new(:method, :index, :separators)
- def self.split(value, separator_pattern)
- keys = value.split(separator_pattern)
- keys.shift if keys[0] == ''
- keys << NULL
- keys
- end
-
- def call(cache, obj)
- (cache[method] ||= self.class.split(obj.send(method), separators))[index]
- end
-
- def call_source(cache, obj)
- "(#{cache}[:#{method}] ||= Analysis::Splitting::Key.split(#{obj}.#{method}, #{separators.inspect}))[#{index}]"
- end
-
- def inspect
- "#{method}[#{index}]"
- end
- end
-
- def clear
- @boundaries = {}
- super
- end
-
- def <<(key)
- super
- key.each_pair do |k, v|
- analyze_capture_boundaries(v, @boundaries[k] ||= Histogram.new)
- end
- end
-
- def separators(key)
- (@boundaries[key].keys_in_upper_quartile + ['/']).uniq
- end
-
- def process_key(requirements, method, requirement)
- separators = separators(method)
- if requirement.is_a?(Regexp) && separators.any?
- generate_split_keys(requirement, separators).each_with_index do |value, index|
- requirements[Key.new(method, index, Regexp.union(*separators))] = value
- end
- else
- super
- end
- end
-
- private
- def analyze_capture_boundaries(regexp, boundaries) #:nodoc:
- return boundaries unless regexp.is_a?(Regexp)
-
- parts = Utils.parse_regexp(regexp)
- parts.each_with_index do |part, index|
- if part.is_a?(Regin::Group)
- if index > 0
- previous = parts[index-1]
- if previous.is_a?(Regin::Character) && previous.literal?
- boundaries << previous.to_s
- end
- end
-
- if inside = part.expression[0]
- if inside.is_a?(Regin::Character) && inside.literal?
- boundaries << inside.to_s
- end
- end
-
- if index < parts.length
- following = parts[index+1]
- if following.is_a?(Regin::Character) && following.literal?
- boundaries << following.to_s
- end
- end
- end
- end
-
- boundaries
- end
-
- def generate_split_keys(regexp, separators) #:nodoc:
- segments = []
- buf = nil
- parts = Utils.parse_regexp(regexp)
- parts.each_with_index do |part, index|
- case part
- when Regin::Anchor
- if part.value == '$' || part.value == '\Z'
- segments << join_buffer(buf, regexp) if buf
- segments << NULL
- buf = nil
- break
- end
- when Regin::CharacterClass
- break if separators.any? { |s| part.include?(s) }
- buf = nil
- segments << part.to_regexp(true)
- when Regin::Character
- if separators.any? { |s| part.include?(s) }
- segments << join_buffer(buf, regexp) if buf
- peek = parts[index+1]
- if peek.is_a?(Regin::Character) && separators.include?(peek.value)
- segments << ''
- end
- buf = nil
- else
- buf ||= Regin::Expression.new([])
- buf += [part]
- end
- when Regin::Group
- if part.quantifier == '?'
- value = part.expression.first
- if separators.any? { |s| value.include?(s) }
- segments << join_buffer(buf, regexp) if buf
- buf = nil
- end
- break
- elsif part.quantifier == nil
- break if separators.any? { |s| part.include?(s) }
- buf = nil
- segments << part.to_regexp(true)
- else
- break
- end
- else
- break
- end
-
- if index + 1 == parts.size
- segments << join_buffer(buf, regexp) if buf
- buf = nil
- break
- end
- end
-
- while segments.length > 0 && (segments.last.nil? || segments.last == '')
- segments.pop
- end
-
- segments
- end
-
- def join_buffer(parts, regexp)
- if parts.literal?
- parts.to_s
- else
- parts.to_regexp(true)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb
deleted file mode 100644
index 903c79fdc6..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/code_generation.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-module Rack::Mount
- module CodeGeneration #:nodoc:
- def _expired_recognize(env) #:nodoc:
- raise 'route set not finalized'
- end
-
- def rehash
- super
- optimize_recognize!
- end
-
- private
- def expire!
- if @optimized_recognize_defined
- remove_metaclass_method :recognize
-
- class << self
- alias_method :recognize, :_expired_recognize
- end
-
- @optimized_recognize_defined = false
- end
-
- super
- end
-
- def optimize_container_iterator(container)
- body = []
-
- container.each_with_index { |route, i|
- body << "route = self[#{i}]"
- body << 'matches = {}'
- body << 'params = route.defaults.dup'
-
- conditions = []
- route.conditions.each do |method, condition|
- b = []
- if condition.is_a?(Regexp)
- b << "if m = obj.#{method}.match(#{condition.inspect})"
- b << "matches[:#{method}] = m"
- if (named_captures = route.named_captures[method]) && named_captures.any?
- b << 'captures = m.captures'
- b << 'p = nil'
- b << named_captures.map { |k, j| "params[#{k.inspect}] = p if p = captures[#{j}]" }.join('; ')
- end
- else
- b << "if m = obj.#{method} == route.conditions[:#{method}]"
- end
- b << 'true'
- b << 'end'
- conditions << "(#{b.join('; ')})"
- end
-
- body << <<-RUBY
- if #{conditions.join(' && ')}
- yield route, matches, params
- end
- RUBY
- }
-
- container.instance_eval(<<-RUBY, __FILE__, __LINE__)
- def optimized_each(obj)
- #{body.join("\n")}
- nil
- end
- RUBY
- end
-
- def optimize_recognize!
- keys = @recognition_keys.map { |key|
- if key.respond_to?(:call_source)
- key.call_source(:cache, :obj)
- else
- "obj.#{key}"
- end
- }.join(', ')
-
- @optimized_recognize_defined = true
-
- remove_metaclass_method :recognize
-
- instance_eval(<<-RUBY, __FILE__, __LINE__)
- def recognize(obj)
- cache = {}
- container = @recognition_graph[#{keys}]
- optimize_container_iterator(container) unless container.respond_to?(:optimized_each)
-
- if block_given?
- container.optimized_each(obj) do |route, matches, params|
- yield route, matches, params
- end
- else
- container.optimized_each(obj) do |route, matches, params|
- return route, matches, params
- end
- end
-
- nil
- end
- RUBY
- end
-
- # method_defined? can't distinguish between instance
- # and meta methods. So we have to rescue if the method
- # has not been defined in the metaclass yet.
- def remove_metaclass_method(symbol)
- metaclass = class << self; self; end
- metaclass.send(:remove_method, symbol)
- rescue NameError => e
- nil
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb
deleted file mode 100644
index 47bbab3784..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/generatable_regexp.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-require 'rack/mount/utils'
-
-module Rack::Mount
- class GeneratableRegexp < Regexp #:nodoc:
- class DynamicSegment #:nodoc:
- attr_reader :name, :requirement
-
- def initialize(name, requirement)
- @name, @requirement = name.to_sym, requirement
- freeze
- end
-
- def ==(obj)
- @name == obj.name && @requirement == obj.requirement
- end
-
- def =~(str)
- @requirement =~ str
- end
-
- def to_hash
- { @name => @requirement }
- end
-
- def inspect
- "/(?<#{@name}>#{@requirement.source})/"
- end
- end
-
- module InstanceMethods
- def self.extended(obj)
- obj.segments
- end
-
- def defaults=(defaults)
- @required_captures = nil
- @required_params = nil
- @required_defaults = nil
- @defaults = defaults
- end
-
- def defaults
- @defaults ||= {}
- end
-
- def generatable?
- segments.any?
- end
-
- def generate(params = {}, recall = {}, options = {})
- return nil unless generatable?
-
- merged = recall.merge(params)
- return nil unless required_params.all? { |p| merged.include?(p) }
- return nil unless required_defaults.all? { |k, v| merged[k] == v }
-
- generate_from_segments(segments, params, merged, options)
- end
-
- def segments
- @segments ||= begin
- defaults
- segments = []
- catch(:halt) do
- expression = Utils.parse_regexp(self)
- segments = parse_segments(expression)
- end
- segments
- end
- end
-
- def captures
- segments.flatten.find_all { |s| s.is_a?(DynamicSegment) }
- end
-
- def required_captures
- @required_captures ||= segments.find_all { |s|
- s.is_a?(DynamicSegment) && !@defaults.include?(s.name)
- }.freeze
- end
-
- def required_params
- @required_params ||= required_captures.map { |s| s.name }.freeze
- end
-
- def required_defaults
- @required_defaults ||= begin
- required_defaults = @defaults.dup
- captures.inject({}) { |h, s| h.merge!(s.to_hash) }.keys.each { |name|
- required_defaults.delete(name)
- }
- required_defaults
- end
- end
-
- def freeze
- segments
- captures
- required_captures
- required_params
- required_defaults
- super
- end
-
- private
- def parse_segments(segments)
- s = []
- segments.each_with_index do |part, index|
- case part
- when Regin::Anchor
- # ignore
- when Regin::Character
- throw :halt unless part.literal?
-
- if s.last.is_a?(String)
- s.last << part.value.dup
- else
- s << part.value.dup
- end
- when Regin::Group
- if part.name
- s << DynamicSegment.new(part.name, part.expression.to_regexp(true))
- else
- s << parse_segments(part.expression)
- end
- when Regin::Expression
- return parse_segments(part)
- else
- throw :halt
- end
- end
-
- s
- end
-
- EMPTY_STRING = ''.freeze
-
- def generate_from_segments(segments, params, merged, options, optional = false)
- if optional
- return EMPTY_STRING if segments.all? { |s| s.is_a?(String) }
- return EMPTY_STRING unless segments.flatten.any? { |s|
- params.has_key?(s.name) if s.is_a?(DynamicSegment)
- }
- return EMPTY_STRING if segments.any? { |segment|
- if segment.is_a?(DynamicSegment)
- value = merged[segment.name] || @defaults[segment.name]
- value = parameterize(segment.name, value, options)
-
- merged_value = parameterize(segment.name, merged[segment.name], options)
- default_value = parameterize(segment.name, @defaults[segment.name], options)
-
- if value.nil? || segment !~ value
- true
- elsif merged_value == default_value
- # Nasty control flow
- return :clear_remaining_segments
- else
- false
- end
- end
- }
- end
-
- generated = segments.map do |segment|
- case segment
- when String
- segment
- when DynamicSegment
- value = params[segment.name] || merged[segment.name] || @defaults[segment.name]
- value = parameterize(segment.name, value, options)
- if value && segment =~ value.to_s
- value
- else
- return
- end
- when Array
- value = generate_from_segments(segment, params, merged, options, true)
- if value == :clear_remaining_segments
- segment.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
- EMPTY_STRING
- elsif value.nil?
- EMPTY_STRING
- else
- value
- end
- end
- end
-
- # Delete any used items from the params
- segments.each { |s| params.delete(s.name) if s.is_a?(DynamicSegment) }
-
- generated.join
- end
-
- def parameterize(name, value, options)
- if block = options[:parameterize]
- block.call(name, value)
- else
- value
- end
- end
- end
- include InstanceMethods
-
- def initialize(regexp)
- super
- segments
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb
deleted file mode 100644
index 0f8eaaec67..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/multimap.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-begin
- require 'nested_multimap'
-rescue LoadError
- $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/multimap'))
- require 'nested_multimap'
-end
-
-module Rack::Mount
- class Multimap < NestedMultimap #:nodoc:
- def store(*args)
- keys = args.dup
- value = keys.pop
- key = keys.shift
-
- raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
-
- unless key.respond_to?(:=~)
- raise ArgumentError, "unsupported key: #{args.first.inspect}"
- end
-
- if key.is_a?(Regexp)
- if keys.empty?
- @hash.each_pair { |k, l| l << value if k =~ key }
- self.default << value
- else
- @hash.each_pair { |k, _|
- if k =~ key
- args[0] = k
- super(*args)
- end
- }
-
- self.default = self.class.new(default) unless default.is_a?(self.class)
- default[*keys.dup] = value
- end
- else
- super(*args)
- end
- end
- alias_method :[]=, :store
-
- undef :index, :invert
-
- def height
- containers_with_default.max { |a, b| a.length <=> b.length }.length
- end
-
- def average_height
- lengths = containers_with_default.map { |e| e.length }
- lengths.inject(0) { |sum, len| sum += len }.to_f / lengths.size
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb
deleted file mode 100644
index 58892e4c4f..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/prefix.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-require 'rack/mount/utils'
-
-module Rack::Mount
- class Prefix #:nodoc:
- EMPTY_STRING = ''.freeze
- PATH_INFO = 'PATH_INFO'.freeze
- SCRIPT_NAME = 'SCRIPT_NAME'.freeze
- SLASH = '/'.freeze
-
- KEY = 'rack.mount.prefix'.freeze
-
- def initialize(app, prefix = nil)
- @app, @prefix = app, prefix.freeze
- freeze
- end
-
- def call(env)
- if prefix = env[KEY] || @prefix
- old_path_info = env[PATH_INFO].dup
- old_script_name = env[SCRIPT_NAME].dup
-
- begin
- env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO].sub(prefix, EMPTY_STRING))
- env[PATH_INFO] = EMPTY_STRING if env[PATH_INFO] == SLASH
- env[SCRIPT_NAME] = Utils.normalize_path(env[SCRIPT_NAME].to_s + prefix)
- @app.call(env)
- ensure
- env[PATH_INFO] = old_path_info
- env[SCRIPT_NAME] = old_script_name
- end
- else
- @app.call(env)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb
deleted file mode 100644
index c11292b2a2..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/regexp_with_named_groups.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module Rack::Mount
- if Regin.regexp_supports_named_captures?
- RegexpWithNamedGroups = Regexp
- else
- require 'strscan'
-
- # A wrapper that adds shim named capture support to older
- # versions of Ruby.
- #
- # Because the named capture syntax causes a parse error, an
- # alternate syntax is used to indicate named captures.
- #
- # Ruby 1.9+ named capture syntax:
- #
- # /(?<foo>[a-z]+)/
- #
- # Ruby 1.8 shim syntax:
- #
- # /(?:<foo>[a-z]+)/
- class RegexpWithNamedGroups < Regexp
- def self.new(regexp) #:nodoc:
- if regexp.is_a?(RegexpWithNamedGroups)
- regexp
- else
- super
- end
- end
-
- # Wraps Regexp with named capture support.
- def initialize(regexp)
- regexp = Regexp.compile(regexp) unless regexp.is_a?(Regexp)
- source, options = regexp.source, regexp.options
- @names, scanner = [], StringScanner.new(source)
-
- while scanner.skip_until(/\(/)
- if scanner.scan(/\?:<([^>]+)>/)
- @names << scanner[1]
- elsif scanner.scan(/\?(i?m?x?\-?i?m?x?)?:/)
- # ignore noncapture
- else
- @names << nil
- end
- end
- source.gsub!(/\?:<([^>]+)>/, '')
-
- @names = [] unless @names.any?
- @names.freeze
-
- super(source, options)
- end
-
- def names
- @names.dup
- end
-
- def named_captures
- named_captures = {}
- names.each_with_index { |n, i|
- named_captures[n] = [i+1] if n
- }
- named_captures
- end
-
- def eql?(other)
- super && @names.eql?(other.names)
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb
deleted file mode 100644
index 680c40f147..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route.rb
+++ /dev/null
@@ -1,130 +0,0 @@
-require 'rack/mount/generatable_regexp'
-require 'rack/mount/regexp_with_named_groups'
-require 'rack/mount/utils'
-
-module Rack::Mount
- # Route is an internal class used to wrap a single route attributes.
- #
- # Plugins should not depend on any method on this class or instantiate
- # new Route objects. Instead use the factory method, RouteSet#add_route
- # to create new routes and add them to the set.
- class Route
- # Valid rack application to call if conditions are met
- attr_reader :app
-
- # A hash of conditions to match against. Conditions may be expressed
- # as strings or regexps to match against.
- attr_reader :conditions
-
- # A hash of values that always gets merged into the parameters hash
- attr_reader :defaults
-
- # Symbol identifier for the route used with named route generations
- attr_reader :name
-
- attr_reader :named_captures
-
- def initialize(app, conditions, defaults, name)
- unless app.respond_to?(:call)
- raise ArgumentError, 'app must be a valid rack application' \
- ' and respond to call'
- end
- @app = app
-
- @name = name ? name.to_sym : nil
- @defaults = (defaults || {}).freeze
-
- @conditions = {}
-
- conditions.each do |method, pattern|
- next unless method && pattern
-
- pattern = Regexp.compile("\\A#{Regexp.escape(pattern)}\\Z") if pattern.is_a?(String)
-
- if pattern.is_a?(Regexp)
- pattern = Utils.normalize_extended_expression(pattern)
- pattern = RegexpWithNamedGroups.new(pattern)
- pattern.extend(GeneratableRegexp::InstanceMethods)
- pattern.defaults = @defaults
- end
-
- @conditions[method] = pattern.freeze
- end
-
- @named_captures = {}
- @conditions.map { |method, condition|
- next unless condition.respond_to?(:named_captures)
- @named_captures[method] = condition.named_captures.inject({}) { |named_captures, (k, v)|
- named_captures[k.to_sym] = v.last - 1
- named_captures
- }.freeze
- }
- @named_captures.freeze
-
- @has_significant_params = @conditions.any? { |method, condition|
- (condition.respond_to?(:required_params) && condition.required_params.any?) ||
- (condition.respond_to?(:required_defaults) && condition.required_defaults.any?)
- }
-
- if @conditions.has_key?(:path_info) &&
- !Utils.regexp_anchored?(@conditions[:path_info])
- @prefix = true
- @app = Prefix.new(@app)
- else
- @prefix = false
- end
-
- @conditions.freeze
- end
-
- def prefix?
- @prefix
- end
-
-
- def generation_keys
- @conditions.inject({}) { |keys, (method, condition)|
- if condition.respond_to?(:required_defaults)
- keys.merge!(condition.required_defaults)
- else
- keys
- end
- }
- end
-
- def significant_params?
- @has_significant_params
- end
-
- def generate(method, params = {}, recall = {}, options = {})
- if method.nil?
- result = @conditions.inject({}) { |h, (m, condition)|
- if condition.respond_to?(:generate)
- h[m] = condition.generate(params, recall, options)
- end
- h
- }
- return nil if result.values.compact.empty?
- else
- if condition = @conditions[method]
- if condition.respond_to?(:generate)
- result = condition.generate(params, recall, options)
- end
- end
- end
-
- if result
- @defaults.each do |key, value|
- params.delete(key) if params[key] == value
- end
- end
-
- result
- end
-
-
- def inspect #:nodoc:
- "#<#{self.class.name} @app=#{@app.inspect} @conditions=#{@conditions.inspect} @defaults=#{@defaults.inspect} @name=#{@name.inspect}>"
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
deleted file mode 100644
index 0e5a65a640..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/route_set.rb
+++ /dev/null
@@ -1,409 +0,0 @@
-require 'rack/mount/multimap'
-require 'rack/mount/route'
-require 'rack/mount/utils'
-
-module Rack::Mount
- class RoutingError < StandardError; end
-
- class RouteSet
- # Initialize a new RouteSet without optimizations
- def self.new_without_optimizations(options = {}, &block)
- new(options.merge(:_optimize => false), &block)
- end
-
- # Basic RouteSet initializer.
- #
- # If a block is given, the set is yielded and finalized.
- #
- # See other aspects for other valid options:
- # - <tt>Generation::RouteSet.new</tt>
- # - <tt>Recognition::RouteSet.new</tt>
- def initialize(options = {}, &block)
- @parameters_key = options.delete(:parameters_key) || 'rack.routing_args'
- @parameters_key.freeze
-
- @named_routes = {}
-
- @recognition_key_analyzer = Analysis::Splitting.new
- @generation_key_analyzer = Analysis::Frequency.new
-
- @request_class = options.delete(:request_class) || Rack::Request
- @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym }
-
- extend CodeGeneration unless options[:_optimize] == false
- @optimized_recognize_defined = false
-
- @routes = []
- expire!
-
- if block_given?
- yield self
- rehash
- end
- end
-
- # Builder method to add a route to the set
- #
- # <tt>app</tt>:: A valid Rack app to call if the conditions are met.
- # <tt>conditions</tt>:: A hash of conditions to match against.
- # Conditions may be expressed as strings or
- # regexps to match against.
- # <tt>defaults</tt>:: A hash of values that always gets merged in
- # <tt>name</tt>:: Symbol identifier for the route used with named
- # route generations
- def add_route(app, conditions = {}, defaults = {}, name = nil)
- unless conditions.is_a?(Hash)
- raise ArgumentError, 'conditions must be a Hash'
- end
-
- unless conditions.all? { |method, pattern|
- @valid_conditions.include?(method)
- }
- raise ArgumentError, 'conditions may only include ' +
- @valid_conditions.inspect
- end
-
- route = Route.new(app, conditions, defaults, name)
- @routes << route
-
- @recognition_key_analyzer << route.conditions
-
- @named_routes[route.name] = route if route.name
- @generation_key_analyzer << route.generation_keys
-
- expire!
- route
- end
-
- def recognize(obj)
- raise 'route set not finalized' unless @recognition_graph
-
- cache = {}
- keys = @recognition_keys.map { |key|
- if key.respond_to?(:call)
- key.call(cache, obj)
- else
- obj.send(key)
- end
- }
-
- @recognition_graph[*keys].each do |route|
- matches = {}
- params = route.defaults.dup
-
- if route.conditions.all? { |method, condition|
- value = obj.send(method)
- if condition.is_a?(Regexp) && (m = value.match(condition))
- matches[method] = m
- captures = m.captures
- route.named_captures[method].each do |k, i|
- if v = captures[i]
- params[k] = v
- end
- end
- true
- elsif value == condition
- true
- else
- false
- end
- }
- if block_given?
- yield route, matches, params
- else
- return route, matches, params
- end
- end
- end
-
- nil
- end
-
- X_CASCADE = 'X-Cascade'.freeze
- PASS = 'pass'.freeze
- PATH_INFO = 'PATH_INFO'.freeze
-
- # Rack compatible recognition and dispatching method. Routes are
- # tried until one returns a non-catch status code. If no routes
- # match, the catch status code is returned.
- #
- # This method can only be invoked after the RouteSet has been
- # finalized.
- def call(env)
- raise 'route set not finalized' unless @recognition_graph
-
- env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO])
-
- request = nil
- req = @request_class.new(env)
- recognize(req) do |route, matches, params|
- # TODO: We only want to unescape params from uri related methods
- params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) }
-
- if route.prefix?
- env[Prefix::KEY] = matches[:path_info].to_s
- end
-
- env[@parameters_key] = params
- result = route.app.call(env)
- return result unless result[1][X_CASCADE] == PASS
- end
-
- request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']]
- end
-
- # Generates a url from Rack env and identifiers or significant keys.
- #
- # To generate a url by named route, pass the name in as a +Symbol+.
- # url(env, :dashboard) # => "/dashboard"
- #
- # Additional parameters can be passed in as a hash
- # url(env, :people, :id => "1") # => "/people/1"
- #
- # If no name route is given, it will fall back to a slower
- # generation search.
- # url(env, :controller => "people", :action => "show", :id => "1")
- # # => "/people/1"
- def url(env, *args)
- named_route, params = nil, {}
-
- case args.length
- when 2
- named_route, params = args[0], args[1].dup
- when 1
- if args[0].is_a?(Hash)
- params = args[0].dup
- else
- named_route = args[0]
- end
- else
- raise ArgumentError
- end
-
- only_path = params.delete(:only_path)
- recall = env[@parameters_key] || {}
-
- unless result = generate(:all, named_route, params, recall,
- :parameterize => lambda { |name, param| Utils.escape_uri(param) })
- return
- end
-
- parts, params = result
- return unless parts
-
- params.each do |k, v|
- if v
- params[k] = v
- else
- params.delete(k)
- end
- end
-
- req = stubbed_request_class.new(env)
- req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params))
- only_path ? req.fullpath : req.url
- end
-
- def generate(method, *args) #:nodoc:
- raise 'route set not finalized' unless @generation_graph
-
- method = nil if method == :all
- named_route, params, recall, options = extract_params!(*args)
- merged = recall.merge(params)
- route = nil
-
- if named_route
- if route = @named_routes[named_route.to_sym]
- recall = route.defaults.merge(recall)
- url = route.generate(method, params, recall, options)
- [url, params]
- else
- raise RoutingError, "#{named_route} failed to generate from #{params.inspect}"
- end
- else
- keys = @generation_keys.map { |key|
- if k = merged[key]
- k.to_s
- else
- nil
- end
- }
- @generation_graph[*keys].each do |r|
- next unless r.significant_params?
- if url = r.generate(method, params, recall, options)
- return [url, params]
- end
- end
-
- raise RoutingError, "No route matches #{params.inspect}"
- end
- end
-
- # Number of routes in the set
- def length
- @routes.length
- end
-
- def rehash #:nodoc:
- @recognition_keys = build_recognition_keys
- @recognition_graph = build_recognition_graph
- @generation_keys = build_generation_keys
- @generation_graph = build_generation_graph
- end
-
- # Finalizes the set and builds optimized data structures. You *must*
- # freeze the set before you can use <tt>call</tt> and <tt>url</tt>.
- # So remember to call freeze after you are done adding routes.
- def freeze
- unless frozen?
- rehash
-
- @recognition_key_analyzer = nil
- @generation_key_analyzer = nil
- @valid_conditions = nil
-
- @routes.each { |route| route.freeze }
- @routes.freeze
- end
-
- super
- end
-
- def marshal_dump #:nodoc:
- hash = {}
-
- instance_variables_to_serialize.each do |ivar|
- hash[ivar] = instance_variable_get(ivar)
- end
-
- if graph = hash[:@recognition_graph]
- hash[:@recognition_graph] = graph.dup
- end
-
- hash
- end
-
- def marshal_load(hash) #:nodoc:
- hash.each do |ivar, value|
- instance_variable_set(ivar, value)
- end
- end
-
- protected
- def recognition_stats
- { :keys => @recognition_keys,
- :keys_size => @recognition_keys.size,
- :graph_size => @recognition_graph.size,
- :graph_height => @recognition_graph.height,
- :graph_average_height => @recognition_graph.average_height }
- end
-
- private
- def expire! #:nodoc:
- @recognition_keys = @recognition_graph = nil
- @recognition_key_analyzer.expire!
-
- @generation_keys = @generation_graph = nil
- @generation_key_analyzer.expire!
- end
-
- def instance_variables_to_serialize
- instance_variables.map { |ivar| ivar.to_sym } - [:@stubbed_request_class, :@optimized_recognize_defined]
- end
-
- # An internal helper method for constructing a nested set from
- # the linear route set.
- #
- # build_nested_route_set([:request_method, :path_info]) { |route, method|
- # route.send(method)
- # }
- def build_nested_route_set(keys, &block)
- graph = Multimap.new
- @routes.each_with_index do |route, index|
- catch(:skip) do
- k = keys.map { |key| block.call(key, index) }
- Utils.pop_trailing_nils!(k)
- k.map! { |key| key || /.+/ }
- graph[*k] = route
- end
- end
- graph
- end
-
- def build_recognition_graph
- build_nested_route_set(@recognition_keys) { |k, i|
- @recognition_key_analyzer.possible_keys[i][k]
- }
- end
-
- def build_recognition_keys
- @recognition_key_analyzer.report
- end
-
- def build_generation_graph
- build_nested_route_set(@generation_keys) { |k, i|
- throw :skip unless @routes[i].significant_params?
-
- if k = @generation_key_analyzer.possible_keys[i][k]
- k.to_s
- else
- nil
- end
- }
- end
-
- def build_generation_keys
- @generation_key_analyzer.report
- end
-
- def extract_params!(*args)
- case args.length
- when 4
- named_route, params, recall, options = args
- when 3
- if args[0].is_a?(Hash)
- params, recall, options = args
- else
- named_route, params, recall = args
- end
- when 2
- if args[0].is_a?(Hash)
- params, recall = args
- else
- named_route, params = args
- end
- when 1
- if args[0].is_a?(Hash)
- params = args[0]
- else
- named_route = args[0]
- end
- else
- raise ArgumentError
- end
-
- named_route ||= nil
- params ||= {}
- recall ||= {}
- options ||= {}
-
- [named_route, params.dup, recall.dup, options.dup]
- end
-
- def stubbed_request_class
- @stubbed_request_class ||= begin
- klass = Class.new(@request_class)
- klass.public_instance_methods.each do |method|
- next if method =~ /^__|object_id/
- klass.class_eval <<-RUBY
- def #{method}(*args, &block)
- @_stubbed_values[:#{method}] || super
- end
- RUBY
- end
- klass.class_eval { attr_accessor :_stubbed_values }
- klass
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb
deleted file mode 100644
index d0d8797008..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require 'rack/mount/strexp/parser'
-
-module Rack::Mount
- class Strexp
- class << self
- # Parses segmented string expression and converts it into a Regexp
- #
- # Strexp.compile('foo')
- # # => %r{\Afoo\Z}
- #
- # Strexp.compile('foo/:bar', {}, ['/'])
- # # => %r{\Afoo/(?<bar>[^/]+)\Z}
- #
- # Strexp.compile(':foo.example.com')
- # # => %r{\A(?<foo>.+)\.example\.com\Z}
- #
- # Strexp.compile('foo/:bar', {:bar => /[a-z]+/}, ['/'])
- # # => %r{\Afoo/(?<bar>[a-z]+)\Z}
- #
- # Strexp.compile('foo(.:extension)')
- # # => %r{\Afoo(\.(?<extension>.+))?\Z}
- #
- # Strexp.compile('src/*files')
- # # => %r{\Asrc/(?<files>.+)\Z}
- def compile(str, requirements = {}, separators = [], anchor = true)
- return Regexp.compile(str) if str.is_a?(Regexp)
-
- requirements = requirements ? requirements.dup : {}
- normalize_requirements!(requirements, separators)
-
- parser = StrexpParser.new
- parser.anchor = anchor
- parser.requirements = requirements
-
- begin
- re = parser.scan_str(str)
- rescue Racc::ParseError => e
- raise RegexpError, e.message
- end
-
- Regexp.compile(re)
- end
- alias_method :new, :compile
-
- private
- def normalize_requirements!(requirements, separators)
- requirements.each do |key, value|
- if value.is_a?(Regexp)
- if regexp_has_modifiers?(value)
- requirements[key] = value
- else
- requirements[key] = value.source
- end
- else
- requirements[key] = Regexp.escape(value)
- end
- end
- requirements.default ||= separators.any? ?
- "[^#{separators.join}]+" : '.+'
- requirements
- end
-
- def regexp_has_modifiers?(regexp)
- regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb
deleted file mode 100644
index cfe05afc61..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.rb
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# DO NOT MODIFY!!!!
-# This file is automatically generated by Racc 1.4.6
-# from Racc grammer file "".
-#
-
-require 'racc/parser.rb'
-
-require 'rack/mount/utils'
-require 'rack/mount/strexp/tokenizer'
-
-module Rack
- module Mount
- class StrexpParser < Racc::Parser
-
-
-if Regin.regexp_supports_named_captures?
- REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
-else
- REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
-end
-
-attr_accessor :anchor, :requirements
-##### State transition tables begin ###
-
-racc_action_table = [
- 1, 2, 3, 9, 4, 1, 2, 3, 12, 4,
- 1, 2, 3, 11, 4, 1, 2, 3, nil, 4 ]
-
-racc_action_check = [
- 0, 0, 0, 5, 0, 3, 3, 3, 9, 3,
- 8, 8, 8, 8, 8, 6, 6, 6, nil, 6 ]
-
-racc_action_pointer = [
- -2, nil, nil, 3, nil, 3, 13, nil, 8, 8,
- nil, nil, nil ]
-
-racc_action_default = [
- -8, -4, -5, -8, -7, -8, -1, -3, -8, -8,
- -2, -6, 13 ]
-
-racc_goto_table = [
- 6, 5, 10, 8, 10 ]
-
-racc_goto_check = [
- 2, 1, 3, 2, 3 ]
-
-racc_goto_pointer = [
- nil, 1, 0, -4 ]
-
-racc_goto_default = [
- nil, nil, nil, 7 ]
-
-racc_reduce_table = [
- 0, 0, :racc_error,
- 1, 8, :_reduce_1,
- 2, 9, :_reduce_2,
- 1, 9, :_reduce_none,
- 1, 10, :_reduce_4,
- 1, 10, :_reduce_5,
- 3, 10, :_reduce_6,
- 1, 10, :_reduce_7 ]
-
-racc_reduce_n = 8
-
-racc_shift_n = 13
-
-racc_token_table = {
- false => 0,
- :error => 1,
- :PARAM => 2,
- :GLOB => 3,
- :LPAREN => 4,
- :RPAREN => 5,
- :CHAR => 6 }
-
-racc_nt_base = 7
-
-racc_use_result_var = true
-
-Racc_arg = [
- racc_action_table,
- racc_action_check,
- racc_action_default,
- racc_action_pointer,
- racc_goto_table,
- racc_goto_check,
- racc_goto_default,
- racc_goto_pointer,
- racc_nt_base,
- racc_reduce_table,
- racc_token_table,
- racc_shift_n,
- racc_reduce_n,
- racc_use_result_var ]
-
-Racc_token_to_s_table = [
- "$end",
- "error",
- "PARAM",
- "GLOB",
- "LPAREN",
- "RPAREN",
- "CHAR",
- "$start",
- "target",
- "expr",
- "token" ]
-
-Racc_debug_parser = false
-
-##### State transition tables end #####
-
-# reduce 0 omitted
-
-def _reduce_1(val, _values, result)
- result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}"
- result
-end
-
-def _reduce_2(val, _values, result)
- result = val.join
- result
-end
-
-# reduce 3 omitted
-
-def _reduce_4(val, _values, result)
- name = val[0].to_sym
- requirement = requirements[name]
- result = REGEXP_NAMED_CAPTURE % [name, requirement]
-
- result
-end
-
-def _reduce_5(val, _values, result)
- name = val[0].to_sym
- requirement = requirements[name]
- result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement]
-
- result
-end
-
-def _reduce_6(val, _values, result)
- result = "(?:#{val[1]})?"
- result
-end
-
-def _reduce_7(val, _values, result)
- result = Regexp.escape(val[0])
- result
-end
-
-def _reduce_none(val, _values, result)
- val[0]
-end
-
- end # class StrexpParser
- end # module Mount
-end # module Rack
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y
deleted file mode 100644
index ffbd9fae11..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/parser.y
+++ /dev/null
@@ -1,34 +0,0 @@
-class Rack::Mount::StrexpParser
-rule
- target: expr { result = anchor ? "\\A#{val.join}\\Z" : "\\A#{val.join}" }
-
- expr: expr token { result = val.join }
- | token
-
- token: PARAM {
- name = val[0].to_sym
- requirement = requirements[name]
- result = REGEXP_NAMED_CAPTURE % [name, requirement]
- }
- | GLOB {
- name = val[0].to_sym
- requirement = requirements[name]
- result = REGEXP_NAMED_CAPTURE % [name, '.+' || requirement]
- }
- | LPAREN expr RPAREN { result = "(?:#{val[1]})?" }
- | CHAR { result = Regexp.escape(val[0]) }
-end
-
----- header ----
-require 'rack/mount/utils'
-require 'rack/mount/strexp/tokenizer'
-
----- inner
-
-if Regin.regexp_supports_named_captures?
- REGEXP_NAMED_CAPTURE = '(?<%s>%s)'.freeze
-else
- REGEXP_NAMED_CAPTURE = '(?:<%s>%s)'.freeze
-end
-
-attr_accessor :anchor, :requirements
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb
deleted file mode 100644
index 0ff7f67661..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-#--
-# DO NOT MODIFY!!!!
-# This file is automatically generated by rex 1.0.5.beta1
-# from lexical definition file "lib/rack/mount/strexp/tokenizer.rex".
-#++
-
-require 'racc/parser'
-class Rack::Mount::StrexpParser < Racc::Parser
- require 'strscan'
-
- class ScanError < StandardError ; end
-
- attr_reader :lineno
- attr_reader :filename
- attr_accessor :state
-
- def scan_setup(str)
- @ss = StringScanner.new(str)
- @lineno = 1
- @state = nil
- end
-
- def action
- yield
- end
-
- def scan_str(str)
- scan_setup(str)
- do_parse
- end
- alias :scan :scan_str
-
- def load_file( filename )
- @filename = filename
- open(filename, "r") do |f|
- scan_setup(f.read)
- end
- end
-
- def scan_file( filename )
- load_file(filename)
- do_parse
- end
-
-
- def next_token
- return if @ss.eos?
-
- text = @ss.peek(1)
- @lineno += 1 if text == "\n"
- token = case @state
- when nil
- case
- when (text = @ss.scan(/\\(\(|\)|:|\*)/))
- action { [:CHAR, @ss[1]] }
-
- when (text = @ss.scan(/\:([a-zA-Z_]\w*)/))
- action { [:PARAM, @ss[1]] }
-
- when (text = @ss.scan(/\*([a-zA-Z_]\w*)/))
- action { [:GLOB, @ss[1]] }
-
- when (text = @ss.scan(/\(/))
- action { [:LPAREN, text] }
-
- when (text = @ss.scan(/\)/))
- action { [:RPAREN, text] }
-
- when (text = @ss.scan(/./))
- action { [:CHAR, text] }
-
- else
- text = @ss.string[@ss.pos .. -1]
- raise ScanError, "can not match: '" + text + "'"
- end # if
-
- else
- raise ScanError, "undefined state: '" + state.to_s + "'"
- end # case state
- token
- end # def next_token
-
-end # class
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex
deleted file mode 100644
index 473bd096e1..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/strexp/tokenizer.rex
+++ /dev/null
@@ -1,12 +0,0 @@
-class Rack::Mount::StrexpParser
-macro
- RESERVED \(|\)|:|\*
- ALPHA_U [a-zA-Z_]
-rule
- \\({RESERVED}) { [:CHAR, @ss[1]] }
- \:({ALPHA_U}\w*) { [:PARAM, @ss[1]] }
- \*({ALPHA_U}\w*) { [:GLOB, @ss[1]] }
- \( { [:LPAREN, text] }
- \) { [:RPAREN, text] }
- . { [:CHAR, text] }
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb
deleted file mode 100644
index aa23b1162f..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/utils.rb
+++ /dev/null
@@ -1,148 +0,0 @@
-begin
- require 'regin'
-rescue LoadError
- $: << File.expand_path(File.join(File.dirname(__FILE__), 'vendor/regin'))
- require 'regin'
-end
-
-require 'uri'
-
-module Rack::Mount
- # Private utility methods used throughout Rack::Mount.
- #--
- # This module is a trash can. Try to move these functions into
- # more appropriate contexts.
- #++
- module Utils
- # Normalizes URI path.
- #
- # Strips off trailing slash and ensures there is a leading slash.
- #
- # normalize_path("/foo") # => "/foo"
- # normalize_path("/foo/") # => "/foo"
- # normalize_path("foo") # => "/foo"
- # normalize_path("") # => "/"
- def normalize_path(path)
- path = "/#{path}"
- path.squeeze!('/')
- path.sub!(%r{/+\Z}, '')
- path = '/' if path == ''
- path
- end
- module_function :normalize_path
-
- # Removes trailing nils from array.
- #
- # pop_trailing_nils!([1, 2, 3]) # => [1, 2, 3]
- # pop_trailing_nils!([1, 2, 3, nil, nil]) # => [1, 2, 3]
- # pop_trailing_nils!([nil]) # => []
- def pop_trailing_nils!(ary)
- while ary.length > 0 && ary.last.nil?
- ary.pop
- end
- ary
- end
- module_function :pop_trailing_nils!
-
- RESERVED_PCHAR = ':@&=+$,;%'
- SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
- if RUBY_VERSION >= '1.9'
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
- else
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
- end
-
- Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
-
- def escape_uri(uri)
- Parser.escape(uri.to_s, UNSAFE_PCHAR)
- end
- module_function :escape_uri
-
- if ''.respond_to?(:force_encoding)
- def unescape_uri(uri)
- Parser.unescape(uri).force_encoding('utf-8')
- end
- else
- def unescape_uri(uri)
- URI.unescape(uri)
- end
- end
- module_function :unescape_uri
-
- # Taken from Rack 1.1.x to build nested query strings
- def build_nested_query(value, prefix = nil) #:nodoc:
- case value
- when Array
- value.map { |v|
- build_nested_query(v, "#{prefix}[]")
- }.join("&")
- when Hash
- value.map { |k, v|
- build_nested_query(v, prefix ? "#{prefix}[#{Rack::Utils.escape(k)}]" : Rack::Utils.escape(k))
- }.join("&")
- when String
- raise ArgumentError, "value must be a Hash" if prefix.nil?
- "#{prefix}=#{Rack::Utils.escape(value)}"
- else
- prefix
- end
- end
- module_function :build_nested_query
-
- # Determines whether the regexp must match the entire string.
- #
- # regexp_anchored?(/^foo$/) # => true
- # regexp_anchored?(/foo/) # => false
- # regexp_anchored?(/^foo/) # => false
- # regexp_anchored?(/foo$/) # => false
- def regexp_anchored?(regexp)
- regexp.source =~ /\A(\\A|\^).*(\\Z|\$)\Z/m ? true : false
- end
- module_function :regexp_anchored?
-
- def normalize_extended_expression(regexp)
- return regexp unless regexp.options & Regexp::EXTENDED != 0
- source = regexp.source
- source.gsub!(/#.+$/, '')
- source.gsub!(/\s+/, '')
- source.gsub!(/\\\//, '/')
- Regexp.compile(source)
- end
- module_function :normalize_extended_expression
-
- def parse_regexp(regexp)
- cache = @@_parse_regexp_cache ||= {}
-
- if expression = cache[regexp]
- return expression
- end
-
- unless regexp.is_a?(RegexpWithNamedGroups)
- regexp = RegexpWithNamedGroups.new(regexp)
- end
-
- expression = Regin.parse(regexp)
-
- unless Regin.regexp_supports_named_captures?
- tag_captures = Proc.new do |group|
- case group
- when Regin::Group
- # TODO: dup instead of mutating
- group.instance_variable_set('@name', regexp.names[group.index]) if group.index
- tag_captures.call(group.expression)
- when Regin::Expression
- group.each { |child| tag_captures.call(child) }
- end
- end
- tag_captures.call(expression)
- end
-
- cache[regexp] = expression.freeze
- expression
- rescue Racc::ParseError, Regin::Parser::ScanError
- []
- end
- module_function :parse_regexp
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb
deleted file mode 100644
index 0b49b49280..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multimap.rb
+++ /dev/null
@@ -1,569 +0,0 @@
-require 'forwardable'
-require 'multiset'
-
-# Multimap is a generalization of a map or associative array
-# abstract data type in which more than one value may be associated
-# with and returned for a given key.
-#
-# == Example
-#
-# require 'multimap'
-# map = Multimap.new
-# map["a"] = 100
-# map["b"] = 200
-# map["a"] = 300
-# map["a"] # -> [100, 300]
-# map["b"] # -> [200]
-# map.keys # -> #<Multiset: {a, a, b}>
-class Multimap
- extend Forwardable
-
- include Enumerable
-
- # call-seq:
- # Multimap[ [key =>|, value]* ] => multimap
- #
- # Creates a new multimap populated with the given objects.
- #
- # Multimap["a", 100, "b", 200] #=> {"a"=>[100], "b"=>[200]}
- # Multimap["a" => 100, "b" => 200] #=> {"a"=>[100], "b"=>[200]}
- def self.[](*args)
- default = []
-
- if args.size == 2 && args.last.is_a?(Hash)
- default = args.shift
- elsif !args.first.is_a?(Hash) && args.size % 2 == 1
- default = args.shift
- end
-
- if args.size == 1 && args.first.is_a?(Hash)
- args[0] = args.first.inject({}) { |hash, (key, value)|
- unless value.is_a?(default.class)
- value = (default.dup << value)
- end
- hash[key] = value
- hash
- }
- else
- index = 0
- args.map! { |value|
- unless index % 2 == 0 || value.is_a?(default.class)
- value = (default.dup << value)
- end
- index += 1
- value
- }
- end
-
- map = new
- map.instance_variable_set(:@hash, Hash[*args])
- map.default = default
- map
- end
-
- # call-seq:
- # Multimap.new => multimap
- # Multimap.new(default) => multimap
- #
- # Returns a new, empty multimap.
- #
- # map = Multimap.new(Set.new)
- # h["a"] = 100
- # h["b"] = 200
- # h["a"] #=> [100].to_set
- # h["c"] #=> [].to_set
- def initialize(default = [])
- @hash = Hash.new(default)
- end
-
- def initialize_copy(original) #:nodoc:
- @hash = Hash.new(original.default.dup)
- original._internal_hash.each_pair do |key, container|
- @hash[key] = container.dup
- end
- end
-
- def_delegators :@hash, :clear, :default, :default=, :empty?,
- :fetch, :has_key?, :key?
-
- # Retrieves the <i>value</i> object corresponding to the
- # <i>*keys</i> object.
- def [](key)
- @hash[key]
- end
-
- # call-seq:
- # map[key] = value => value
- # map.store(key, value) => value
- #
- # Associates the value given by <i>value</i> with the key
- # given by <i>key</i>. Unlike a regular hash, multiple can be
- # assoicated with the same value.
- #
- # map = Multimap["a" => 100, "b" => 200]
- # map["a"] = 9
- # map["c"] = 4
- # map #=> {"a" => [100, 9], "b" => [200], "c" => [4]}
- def store(key, value)
- update_container(key) do |container|
- container << value
- container
- end
- end
- alias_method :[]=, :store
-
- # call-seq:
- # map.delete(key, value) => value
- # map.delete(key) => value
- #
- # Deletes and returns a key-value pair from <i>map</i>. If only
- # <i>key</i> is given, all the values matching that key will be
- # deleted.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.delete("b", 300) #=> 300
- # map.delete("a") #=> [100]
- def delete(key, value = nil)
- if value
- @hash[key].delete(value)
- else
- @hash.delete(key)
- end
- end
-
- # call-seq:
- # map.each { |key, value| block } => map
- #
- # Calls <i>block</i> for each key/value pair in <i>map</i>, passing
- # the key and value to the block as a two-element array.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each { |key, value| puts "#{key} is #{value}" }
- #
- # <em>produces:</em>
- #
- # a is 100
- # b is 200
- # b is 300
- def each
- each_pair do |key, value|
- yield [key, value]
- end
- end
-
- # call-seq:
- # map.each_association { |key, container| block } => map
- #
- # Calls <i>block</i> once for each key/container in <i>map</i>, passing
- # the key and container to the block as parameters.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each_association { |key, container| puts "#{key} is #{container}" }
- #
- # <em>produces:</em>
- #
- # a is [100]
- # b is [200, 300]
- def each_association(&block)
- @hash.each_pair(&block)
- end
-
- # call-seq:
- # map.each_container { |container| block } => map
- #
- # Calls <i>block</i> for each container in <i>map</i>, passing the
- # container as a parameter.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each_container { |container| puts container }
- #
- # <em>produces:</em>
- #
- # [100]
- # [200, 300]
- def each_container
- each_association do |_, container|
- yield container
- end
- end
-
- # call-seq:
- # map.each_key { |key| block } => map
- #
- # Calls <i>block</i> for each key in <i>hsh</i>, passing the key
- # as a parameter.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each_key { |key| puts key }
- #
- # <em>produces:</em>
- #
- # a
- # b
- # b
- def each_key
- each_pair do |key, _|
- yield key
- end
- end
-
- # call-seq:
- # map.each_pair { |key_value_array| block } => map
- #
- # Calls <i>block</i> for each key/value pair in <i>map</i>,
- # passing the key and value as parameters.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each_pair { |key, value| puts "#{key} is #{value}" }
- #
- # <em>produces:</em>
- #
- # a is 100
- # b is 200
- # b is 300
- def each_pair
- each_association do |key, values|
- values.each do |value|
- yield key, value
- end
- end
- end
-
- # call-seq:
- # map.each_value { |value| block } => map
- #
- # Calls <i>block</i> for each key in <i>map</i>, passing the
- # value as a parameter.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.each_value { |value| puts value }
- #
- # <em>produces:</em>
- #
- # 100
- # 200
- # 300
- def each_value
- each_pair do |_, value|
- yield value
- end
- end
-
- def ==(other) #:nodoc:
- case other
- when Multimap
- @hash == other._internal_hash
- else
- @hash == other
- end
- end
-
- def eql?(other) #:nodoc:
- case other
- when Multimap
- @hash.eql?(other._internal_hash)
- else
- @hash.eql?(other)
- end
- end
-
- def freeze #:nodoc:
- each_container { |container| container.freeze }
- default.freeze
- super
- end
-
- # call-seq:
- # map.has_value?(value) => true or false
- # map.value?(value) => true or false
- #
- # Returns <tt>true</tt> if the given value is present for any key
- # in <i>map</i>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.has_value?(300) #=> true
- # map.has_value?(999) #=> false
- def has_value?(value)
- values.include?(value)
- end
- alias_method :value?, :has_value?
-
- # call-seq:
- # map.index(value) => key
- #
- # Returns the key for a given value. If not found, returns
- # <tt>nil</tt>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.index(100) #=> "a"
- # map.index(200) #=> "b"
- # map.index(999) #=> nil
- def index(value)
- invert[value]
- end
-
- # call-seq:
- # map.delete_if {| key, value | block } -> map
- #
- # Deletes every key-value pair from <i>map</i> for which <i>block</i>
- # evaluates to <code>true</code>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.delete_if {|key, value| value >= 300 }
- # #=> Multimap["a" => 100, "b" => 200]
- #
- def delete_if
- each_association do |key, container|
- container.delete_if do |value|
- yield [key, value]
- end
- end
- self
- end
-
- # call-seq:
- # map.reject {| key, value | block } -> map
- #
- # Same as <code>Multimap#delete_if</code>, but works on (and returns) a
- # copy of the <i>map</i>. Equivalent to
- # <code><i>map</i>.dup.delete_if</code>.
- #
- def reject(&block)
- dup.delete_if(&block)
- end
-
- # call-seq:
- # map.reject! {| key, value | block } -> map or nil
- #
- # Equivalent to <code>Multimap#delete_if</code>, but returns
- # <code>nil</code> if no changes were made.
- #
- def reject!(&block)
- old_size = size
- delete_if(&block)
- old_size == size ? nil : self
- end
-
- # call-seq:
- # map.replace(other_map) => map
- #
- # Replaces the contents of <i>map</i> with the contents of
- # <i>other_map</i>.
- #
- # map = Multimap["a" => 100, "b" => 200]
- # map.replace({ "c" => 300, "d" => 400 })
- # #=> Multimap["c" => 300, "d" => 400]
- def replace(other)
- case other
- when Array
- @hash.replace(self.class[self.default, *other])
- when Hash
- @hash.replace(self.class[self.default, other])
- when self.class
- @hash.replace(other)
- else
- raise ArgumentError
- end
- end
-
- # call-seq:
- # map.invert => multimap
- #
- # Returns a new multimap created by using <i>map</i>'s values as keys,
- # and the keys as values.
- #
- # map = Multimap["n" => 100, "m" => 100, "d" => [200, 300]]
- # map.invert #=> Multimap[100 => ["n", "m"], 200 => "d", 300 => "d"]
- def invert
- h = self.class.new(default.dup)
- each_pair { |key, value| h[value] = key }
- h
- end
-
- # call-seq:
- # map.keys => multiset
- #
- # Returns a new +Multiset+ populated with the keys from this hash. See also
- # <tt>Multimap#values</tt> and <tt>Multimap#containers</tt>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
- # map.keys #=> Multiset.new(["a", "b", "b", "c"])
- def keys
- keys = Multiset.new
- each_key { |key| keys << key }
- keys
- end
-
- # Returns true if the given key is present in Multimap.
- def include?(key)
- keys.include?(key)
- end
- alias_method :member?, :include?
-
- # call-seq:
- # map.length => fixnum
- # map.size => fixnum
- #
- # Returns the number of key-value pairs in the map.
- #
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
- # map.length #=> 4
- # map.delete("a") #=> 100
- # map.length #=> 3
- def size
- values.size
- end
- alias_method :length, :size
-
- # call-seq:
- # map.merge(other_map) => multimap
- #
- # Returns a new multimap containing the contents of <i>other_map</i> and
- # the contents of <i>map</i>.
- #
- # map1 = Multimap["a" => 100, "b" => 200]
- # map2 = Multimap["a" => 254, "c" => 300]
- # map2.merge(map2) #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
- # map1 #=> Multimap["a" => 100, "b" => 200]
- def merge(other)
- dup.update(other)
- end
-
- # call-seq:
- # map.merge!(other_map) => multimap
- # map.update(other_map) => multimap
- #
- # Adds each pair from <i>other_map</i> to <i>map</i>.
- #
- # map1 = Multimap["a" => 100, "b" => 200]
- # map2 = Multimap["b" => 254, "c" => 300]
- #
- # map1.merge!(map2)
- # #=> Multimap["a" => 100, "b" => [200, 254], "c" => 300]
- def update(other)
- case other
- when self.class
- other.each_pair { |key, value| store(key, value) }
- when Hash
- update(self.class[self.default, other])
- else
- raise ArgumentError
- end
- self
- end
- alias_method :merge!, :update
-
- # call-seq:
- # map.select { |key, value| block } => multimap
- #
- # Returns a new Multimap consisting of the pairs for which the
- # block returns true.
- #
- # map = Multimap["a" => 100, "b" => 200, "c" => 300]
- # map.select { |k,v| k > "a" } #=> Multimap["b" => 200, "c" => 300]
- # map.select { |k,v| v < 200 } #=> Multimap["a" => 100]
- def select
- inject(self.class.new) { |map, (key, value)|
- map[key] = value if yield([key, value])
- map
- }
- end
-
- # call-seq:
- # map.to_a => array
- #
- # Converts <i>map</i> to a nested array of [<i>key,
- # value</i>] arrays.
- #
- # map = Multimap["a" => 100, "b" => [200, 300], "c" => 400]
- # map.to_a #=> [["a", 100], ["b", 200], ["b", 300], ["c", 400]]
- def to_a
- ary = []
- each_pair do |key, value|
- ary << [key, value]
- end
- ary
- end
-
- # call-seq:
- # map.to_hash => hash
- #
- # Converts <i>map</i> to a basic hash.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.to_hash #=> { "a" => [100], "b" => [200, 300] }
- def to_hash
- @hash.dup
- end
-
- # call-seq:
- # map.containers => array
- #
- # Returns a new array populated with the containers from <i>map</i>. See
- # also <tt>Multimap#keys</tt> and <tt>Multimap#values</tt>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.containers #=> [[100], [200, 300]]
- def containers
- containers = []
- each_container { |container| containers << container }
- containers
- end
-
- # call-seq:
- # map.values => array
- #
- # Returns a new array populated with the values from <i>map</i>. See
- # also <tt>Multimap#keys</tt> and <tt>Multimap#containers</tt>.
- #
- # map = Multimap["a" => 100, "b" => [200, 300]]
- # map.values #=> [100, 200, 300]
- def values
- values = []
- each_value { |value| values << value }
- values
- end
-
- # Return an array containing the values associated with the given keys.
- def values_at(*keys)
- @hash.values_at(*keys)
- end
-
- def marshal_dump #:nodoc:
- @hash
- end
-
- def marshal_load(hash) #:nodoc:
- @hash = hash
- end
-
- def to_yaml(opts = {}) #:nodoc:
- YAML::quick_emit(self, opts) do |out|
- out.map(taguri, to_yaml_style) do |map|
- @hash.each do |k, v|
- map.add(k, v)
- end
- map.add('__default__', @hash.default)
- end
- end
- end
-
- def yaml_initialize(tag, val) #:nodoc:
- default = val.delete('__default__')
- @hash = val
- @hash.default = default
- self
- end
-
- protected
- def _internal_hash #:nodoc:
- @hash
- end
-
- def update_container(key) #:nodoc:
- container = @hash[key]
- container = container.dup if container.equal?(default)
- container = yield(container)
- @hash[key] = container
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb
deleted file mode 100644
index 119bf12646..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/multiset.rb
+++ /dev/null
@@ -1,185 +0,0 @@
-require 'set'
-
-# Multiset implements a collection of unordered values and
-# allows duplicates.
-#
-# == Example
-#
-# require 'multiset'
-# s1 = Multiset.new [1, 2] # -> #<Multiset: {1, 2}>
-# s1.add(2) # -> #<Multiset: {1, 2, 2}>
-# s1.merge([2, 6]) # -> #<Multiset: {1, 2, 2, 2, 3}>
-# s1.multiplicity(2) # -> 3
-# s1.multiplicity(3) # -> 1
-class Multiset < Set
- def initialize(*args, &block) #:nodoc:
- @hash = Hash.new(0)
- super
- end
-
- # Returns the number of times an element belongs to the multiset.
- def multiplicity(e)
- @hash[e]
- end
-
- # Returns the total number of elements in a multiset, including
- # repeated memberships
- def cardinality
- @hash.inject(0) { |s, (e, m)| s += m }
- end
- alias_method :size, :cardinality
- alias_method :length, :cardinality
-
- # Converts the set to an array. The order of elements is uncertain.
- def to_a
- inject([]) { |ary, (key, _)| ary << key }
- end
-
- # Returns true if the set is a superset of the given set.
- def superset?(set)
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
- return false if cardinality < set.cardinality
- set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
- end
-
- # Returns true if the set is a proper superset of the given set.
- def proper_superset?(set)
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
- return false if cardinality <= set.cardinality
- set.all? { |o| set.multiplicity(o) <= multiplicity(o) }
- end
-
- # Returns true if the set is a subset of the given set.
- def subset?(set)
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
- return false if set.cardinality < cardinality
- all? { |o| multiplicity(o) <= set.multiplicity(o) }
- end
-
- # Returns true if the set is a proper subset of the given set.
- def proper_subset?(set)
- set.is_a?(self.class) or raise ArgumentError, "value must be a set"
- return false if set.cardinality <= cardinality
- all? { |o| multiplicity(o) <= set.multiplicity(o) }
- end
-
- # Calls the given block once for each element in the set, passing
- # the element as parameter. Returns an enumerator if no block is
- # given.
- def each
- @hash.each_pair do |key, multiplicity|
- multiplicity.times do
- yield(key)
- end
- end
- self
- end
-
- # Adds the given object to the set and returns self. Use +merge+ to
- # add many elements at once.
- def add(o)
- @hash[o] ||= 0
- @hash[o] += 1
- self
- end
- alias << add
-
- undef :add?
-
- # Deletes all the identical object from the set and returns self.
- # If +n+ is given, it will remove that amount of identical objects
- # from the set. Use +subtract+ to delete many different items at
- # once.
- def delete(o, n = nil)
- if n
- @hash[o] ||= 0
- @hash[o] -= n if @hash[o] > 0
- @hash.delete(o) if @hash[o] == 0
- else
- @hash.delete(o)
- end
- self
- end
-
- undef :delete?
-
- # Deletes every element of the set for which block evaluates to
- # true, and returns self.
- def delete_if
- each { |o| delete(o) if yield(o) }
- self
- end
-
- # Merges the elements of the given enumerable object to the set and
- # returns self.
- def merge(enum)
- enum.each { |o| add(o) }
- self
- end
-
- # Deletes every element that appears in the given enumerable object
- # and returns self.
- def subtract(enum)
- enum.each { |o| delete(o, 1) }
- self
- end
-
- # Returns a new set containing elements common to the set and the
- # given enumerable object.
- def &(enum)
- s = dup
- n = self.class.new
- enum.each { |o|
- if s.include?(o)
- s.delete(o, 1)
- n.add(o)
- end
- }
- n
- end
- alias intersection &
-
- # Returns a new set containing elements exclusive between the set
- # and the given enumerable object. (set ^ enum) is equivalent to
- # ((set | enum) - (set & enum)).
- def ^(enum)
- n = self.class.new(enum)
- each { |o| n.include?(o) ? n.delete(o, 1) : n.add(o) }
- n
- end
-
- # Returns true if two sets are equal. Two multisets are equal if
- # they have the same cardinalities and each element has the same
- # multiplicity in both sets. The equality of each element inside
- # the multiset is defined according to Object#eql?.
- def eql?(set)
- return true if equal?(set)
- set = self.class.new(set) unless set.is_a?(self.class)
- return false unless cardinality == set.cardinality
- superset?(set) && subset?(set)
- end
- alias_method :==, :eql?
-
- def marshal_dump #:nodoc:
- @hash
- end
-
- def marshal_load(hash) #:nodoc:
- @hash = hash
- end
-
- def to_yaml(opts = {}) #:nodoc:
- YAML::quick_emit(self, opts) do |out|
- out.map(taguri, to_yaml_style) do |map|
- @hash.each do |k, v|
- map.add(k, v)
- end
- end
- end
- end
-
- def yaml_initialize(tag, val) #:nodoc:
- @hash = val
- self
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb
deleted file mode 100644
index 4eb088b91a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/multimap/nested_multimap.rb
+++ /dev/null
@@ -1,158 +0,0 @@
-require 'multimap'
-
-# NestedMultimap allows values to be assoicated with a nested
-# set of keys.
-class NestedMultimap < Multimap
- # call-seq:
- # multimap[*keys] = value => value
- # multimap.store(*keys, value) => value
- #
- # Associates the value given by <i>value</i> with multiple key
- # given by <i>keys</i>.
- #
- # map = NestedMultimap.new
- # map["a"] = 100
- # map["a", "b"] = 101
- # map["a"] = 102
- # map #=> {"a"=>{"b"=>[100, 101, 102], default => [100, 102]}}
- def store(*args)
- keys = args
- value = args.pop
-
- raise ArgumentError, 'wrong number of arguments (1 for 2)' unless value
-
- if keys.length > 1
- update_container(keys.shift) do |container|
- container = self.class.new(container) unless container.is_a?(self.class)
- container[*keys] = value
- container
- end
- elsif keys.length == 1
- super(keys.first, value)
- else
- self << value
- end
- end
- alias_method :[]=, :store
-
- # call-seq:
- # multimap << obj => multimap
- #
- # Pushes the given object on to the end of all the containers.
- #
- # map = NestedMultimap["a" => [100], "b" => [200, 300]]
- # map << 300
- # map["a"] #=> [100, 300]
- # map["c"] #=> [300]
- def <<(value)
- @hash.each_value { |container| container << value }
- self.default << value
- self
- end
-
- # call-seq:
- # multimap[*keys] => value
- # multimap[key1, key2, key3] => value
- #
- # Retrieves the <i>value</i> object corresponding to the
- # <i>*keys</i> object.
- def [](*keys)
- i, l, r, k = 0, keys.length, self, self.class
- while r.is_a?(k)
- r = i < l ? r._internal_hash[keys[i]] : r.default
- i += 1
- end
- r
- end
-
- # call-seq:
- # multimap.each_association { |key, container| block } => multimap
- #
- # Calls <i>block</i> once for each key/container in <i>map</i>, passing
- # the key and container to the block as parameters.
- #
- # map = NestedMultimap.new
- # map["a"] = 100
- # map["a", "b"] = 101
- # map["a"] = 102
- # map["c"] = 200
- # map.each_association { |key, container| puts "#{key} is #{container}" }
- #
- # <em>produces:</em>
- #
- # ["a", "b"] is [100, 101, 102]
- # "c" is [200]
- def each_association
- super() do |key, container|
- if container.respond_to?(:each_association)
- container.each_association do |nested_key, value|
- yield [key, nested_key].flatten, value
- end
- else
- yield key, container
- end
- end
- end
-
- # call-seq:
- # multimap.each_container_with_default { |container| block } => map
- #
- # Calls <i>block</i> for every container in <i>map</i> including
- # the default, passing the container as a parameter.
- #
- # map = NestedMultimap.new
- # map["a"] = 100
- # map["a", "b"] = 101
- # map["a"] = 102
- # map.each_container_with_default { |container| puts container }
- #
- # <em>produces:</em>
- #
- # [100, 101, 102]
- # [100, 102]
- # []
- def each_container_with_default(&block)
- @hash.each_value do |container|
- iterate_over_container(container, &block)
- end
- iterate_over_container(default, &block)
- self
- end
-
- # call-seq:
- # multimap.containers_with_default => array
- #
- # Returns a new array populated with all the containers from
- # <i>map</i> including the default.
- #
- # map = NestedMultimap.new
- # map["a"] = 100
- # map["a", "b"] = 101
- # map["a"] = 102
- # map.containers_with_default #=> [[100, 101, 102], [100, 102], []]
- def containers_with_default
- containers = []
- each_container_with_default { |container| containers << container }
- containers
- end
-
- def inspect #:nodoc:
- super.gsub(/\}$/, ", default => #{default.inspect}}")
- end
-
- private
- def iterate_over_container(container)
- if container.respond_to?(:each_container_with_default)
- container.each_container_with_default do |value|
- yield value
- end
- else
- yield container
- end
- end
-end
-
-begin
- require 'nested_multimap_ext'
-rescue LoadError
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb
deleted file mode 100644
index d38922bcc6..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-module Regin
- autoload :Alternation, 'regin/alternation'
- autoload :Anchor, 'regin/anchor'
- autoload :Atom, 'regin/atom'
- autoload :Character, 'regin/character'
- autoload :CharacterClass, 'regin/character_class'
- autoload :Collection, 'regin/collection'
- autoload :Expression, 'regin/expression'
- autoload :Group, 'regin/group'
- autoload :Options, 'regin/options'
- autoload :Parser, 'regin/parser'
-
- class << self
- begin
- eval('foo = /(?<foo>.*)/').named_captures
-
- # Returns true if the interpreter is using the Oniguruma Regexp lib
- # and supports named captures.
- #
- # /(?<foo>bar)/
- def regexp_supports_named_captures?
- true
- end
- rescue SyntaxError, NoMethodError
- def regexp_supports_named_captures? #:nodoc:
- false
- end
- end
-
- # Parses Regexp and returns a Expression data structure.
- def parse(regexp)
- Parser.parse_regexp(regexp)
- end
-
- # Recompiles Regexp by parsing it and turning it back into a Regexp.
- #
- # (In the future Regin will perform some Regexp optimizations
- # such as removing unnecessary captures and options)
- def compile(source)
- regexp = Regexp.compile(source)
- expression = parse(regexp)
- Regexp.compile(expression.to_s(true), expression.flags)
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb
deleted file mode 100644
index ce4f52bfdb..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/alternation.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-module Regin
- class Alternation < Collection
- def initialize(*args)
- args, options = extract_options(args)
-
- if args.length == 1 && args.first.instance_of?(Array)
- super(args.first)
- else
- super(args)
- end
-
- if options.key?(:ignorecase)
- @array.map! { |e| e.dup(:ignorecase => options[:ignorecase]) }
- end
- end
-
- # Returns true if expression could be treated as a literal string.
- #
- # Alternation groups are never literal.
- def literal?
- false
- end
-
- def flags
- 0
- end
-
- def dup(options = {})
- self.class.new(to_a, options)
- end
-
- def to_s(parent = false)
- map { |e| e.to_s(parent) }.join('|')
- end
-
- def inspect #:nodoc:
- to_s.inspect
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb
deleted file mode 100644
index 05520dd5e0..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/anchor.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-module Regin
- class Anchor < Atom
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb
deleted file mode 100644
index eb1923a5a1..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/atom.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-module Regin
- class Atom
- attr_reader :value, :ignorecase
-
- def initialize(value, options = {})
- @value = value
- @ignorecase = options[:ignorecase]
- end
-
- def option_names
- %w( ignorecase )
- end
-
- # Returns true if expression could be treated as a literal string.
- def literal?
- false
- end
-
- def casefold?
- ignorecase ? true : false
- end
-
- def dup(options = {})
- original_options = option_names.inject({}) do |h, m|
- h[m.to_sym] = send(m)
- h
- end
- self.class.new(value, original_options.merge(options))
- end
-
- def to_s(parent = false)
- "#{value}"
- end
-
- def inspect #:nodoc:
- "#<#{self.class.to_s.sub('Regin::', '')} #{to_s.inspect}>"
- end
-
- def ==(other) #:nodoc:
- case other
- when String
- other == to_s
- else
- eql?(other)
- end
- end
-
- def eql?(other) #:nodoc:
- other.instance_of?(self.class) &&
- self.value.eql?(other.value) &&
- (!!self.ignorecase).eql?(!!other.ignorecase)
- end
-
- def freeze #:nodoc:
- value.freeze
- super
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb
deleted file mode 100644
index 12a9199d2a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-module Regin
- class Character < Atom
- attr_reader :quantifier
-
- def initialize(value, options = {})
- @quantifier = options[:quantifier]
- super
- end
-
- def option_names
- %w( quantifier ) + super
- end
-
- # Returns true if expression could be treated as a literal string.
- #
- # A Character is literal is there is no quantifier attached to it.
- def literal?
- quantifier.nil? && !ignorecase
- end
-
- def to_s(parent = false)
- if !parent && ignorecase
- "(?i-mx:#{value})#{quantifier}"
- else
- "#{value}#{quantifier}"
- end
- end
-
- def to_regexp(anchored = false)
- re = to_s(true)
- re = "\\A#{re}\\Z" if anchored
- Regexp.compile(re, ignorecase)
- end
-
- def match(char)
- to_regexp(true).match(char)
- end
-
- def include?(char)
- if ignorecase
- value.downcase == char.downcase
- else
- value == char
- end
- end
-
- def eql?(other) #:nodoc:
- super && quantifier.eql?(other.quantifier)
- end
-
- def freeze #:nodoc:
- quantifier.freeze if quantifier
- super
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb
deleted file mode 100644
index caed5ef9d0..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/character_class.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module Regin
- class CharacterClass < Character
- def initialize(value, options = {})
- @negate = options[:negate]
- super
- end
-
- def option_names
- %w( negate ) + super
- end
-
- attr_reader :negate
-
- def negated?
- negate ? true : false
- end
-
- # Returns true if expression could be treated as a literal string.
- #
- # A CharacterClass is never literal.
- def literal?
- false
- end
-
- def bracketed?
- value != '.' && value !~ /^\\[dDsSwW]$/
- end
-
- def to_s(parent = false)
- if bracketed?
- if !parent && ignorecase
- "(?i-mx:[#{negate && '^'}#{value}])#{quantifier}"
- else
- "[#{negate && '^'}#{value}]#{quantifier}"
- end
- else
- super
- end
- end
-
- def include?(char)
- re = quantifier ? to_s.sub(/#{Regexp.escape(quantifier)}$/, '') : to_s
- Regexp.compile("\\A#{re}\\Z").match(char)
- end
-
- def eql?(other) #:nodoc:
- super && negate == other.negate
- end
-
- def freeze #:nodoc:
- negate.freeze if negate
- super
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb
deleted file mode 100644
index b60353268a..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/collection.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-module Regin
- class Collection
- include Enumerable
-
- def initialize(*args)
- @array = Array.new(*args)
- end
-
- def each
- @array.each{ |item| yield item }
- end
-
- def [](i)
- @array[i]
- end
-
- def length
- @array.length
- end
- alias_method :size, :length
-
- def first
- @array.first
- end
-
- def last
- @array.last
- end
-
- def +(other)
- ary = other.is_a?(self.class) ? other.internal_array : other
- self.class.new(@array + ary)
- end
-
- def to_regexp(anchored = false)
- re = to_s(true)
- re = "\\A#{re}\\Z" if anchored
- Regexp.compile(re, flags)
- end
-
- def match(char)
- to_regexp.match(char)
- end
-
- def include?(char)
- any? { |e| e.include?(char) }
- end
-
- def ==(other) #:nodoc:
- case other
- when String
- other == to_s
- when Array
- other == @array
- else
- eql?(other)
- end
- end
-
- def eql?(other) #:nodoc:
- other.instance_of?(self.class) && @array.eql?(other.internal_array)
- end
-
- def freeze #:nodoc:
- each { |e| e.freeze }
- @array.freeze
- super
- end
-
- protected
- def internal_array #:nodoc:
- @array
- end
-
- def extract_options(args)
- if args.last.is_a?(Hash)
- return args[0..-2], args.last
- else
- return args, {}
- end
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb
deleted file mode 100644
index 18e4965097..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/expression.rb
+++ /dev/null
@@ -1,126 +0,0 @@
-module Regin
- class Expression < Collection
- attr_reader :ignorecase, :multiline, :extended
-
- def initialize(*args)
- args, options = extract_options(args)
-
- @multiline = @ignorecase = @extended = nil
-
- if args.length == 1 && args.first.instance_of?(Array)
- super(args.first)
- else
- args = args.map { |e| e.instance_of?(String) ? Character.new(e) : e }
- super(args)
- end
-
- self.multiline = options[:multiline] if options.key?(:multiline)
- self.ignorecase = options[:ignorecase] if options.key?(:ignorecase)
- self.extended = options[:extended] if options.key?(:extended)
- end
-
- # Returns true if expression could be treated as a literal string.
- #
- # A Expression is literal if all its elements are literal.
- def literal?
- !ignorecase && all? { |e| e.literal? }
- end
-
- def anchored?
- anchored_to_start? && anchored_to_end?
- end
-
- def anchored_to_start?
- first.is_a?(Anchor) && first == '\A'
- end
-
- def anchored_to_end?
- last.is_a?(Anchor) && last == '\Z'
- end
-
- def anchored_to_line?
- anchored_to_start_of_line? && anchored_to_end_of_line?
- end
-
- def anchored_to_start_of_line?
- anchored_to_start? || (first.is_a?(Anchor) && first == '^')
- end
-
- def anchored_to_end_of_line?
- anchored_to_end? || (last.is_a?(Anchor) && last == '$')
- end
-
- def options?
- options.any?(true)
- end
-
- def flags
- options.to_i
- end
-
- def +(other)
- ary = other.is_a?(self.class) ? other.internal_array : other
- ary = @array + ary + [options.to_h(true)]
- self.class.new(*ary)
- end
-
- def dup(options = {})
- expression = super()
- expression.multiline = options[:multiline] if options.key?(:multiline)
- expression.ignorecase = options[:ignorecase] if options.key?(:ignorecase)
- expression.extended = options[:extended] if options.key?(:extended)
- expression
- end
-
- def to_s(parent = false)
- if parent || !options?
- map { |e| e.to_s(parent) }.join
- else
- with, without = [], []
- multiline ? (with << 'm') : (without << 'm')
- ignorecase ? (with << 'i') : (without << 'i')
- extended ? (with << 'x') : (without << 'x')
-
- with = with.join
- without = without.any? ? "-#{without.join}" : ''
-
- "(?#{with}#{without}:#{map { |e| e.to_s(true) }.join})"
- end
- end
-
- def inspect #:nodoc:
- "#<Expression #{to_s.inspect}>"
- end
-
- def casefold?
- ignorecase
- end
-
- def eql?(other) #:nodoc:
- super &&
- !!self.multiline == !!other.multiline &&
- !!self.ignorecase == !!other.ignorecase &&
- !!self.extended == !!other.extended
- end
-
- protected
- def options
- Options.new(multiline, ignorecase, extended)
- end
-
- def multiline=(multiline)
- @multiline = multiline
- end
-
- def ignorecase=(ignorecase)
- if @ignorecase.nil?
- @array.map! { |e| e.dup(:ignorecase => ignorecase) }
- @ignorecase = ignorecase
- end
- end
-
- def extended=(extended)
- @extended = extended
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb
deleted file mode 100644
index d682148bd9..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/group.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-module Regin
- class Group
- attr_reader :expression, :quantifier, :capture, :index, :name
-
- def initialize(expression, options = {})
- @quantifier = @index = @name = nil
- @capture = true
- @expression = expression.dup(options)
-
- @quantifier = options[:quantifier] if options.key?(:quantifier)
- @capture = options[:capture] if options.key?(:capture)
- @index = options[:index] if options.key?(:index)
- @name = options[:name] if options.key?(:name)
- end
-
- def option_names
- %w( quantifier capture index name )
- end
-
- # Returns true if expression could be treated as a literal string.
- #
- # A Group is literal if its expression is literal and it has no quantifier.
- def literal?
- quantifier.nil? && expression.literal?
- end
-
- def to_s(parent = false)
- if !expression.options?
- "(#{capture ? '' : '?:'}#{expression.to_s(parent)})#{quantifier}"
- elsif capture == false
- "#{expression.to_s}#{quantifier}"
- else
- "(#{expression.to_s})#{quantifier}"
- end
- end
-
- def to_regexp(anchored = false)
- re = to_s
- re = "\\A#{re}\\Z" if anchored
- Regexp.compile(re)
- end
-
- def dup(options = {})
- original_options = option_names.inject({}) do |h, m|
- h[m.to_sym] = send(m)
- h
- end
- self.class.new(expression, original_options.merge(options))
- end
-
- def inspect #:nodoc:
- to_s.inspect
- end
-
- def match(char)
- to_regexp.match(char)
- end
-
- def include?(char)
- expression.include?(char)
- end
-
- def capture?
- capture
- end
-
- def ==(other) #:nodoc:
- case other
- when String
- other == to_s
- else
- eql?(other)
- end
- end
-
- def eql?(other) #:nodoc:
- other.is_a?(self.class) &&
- self.expression == other.expression &&
- self.quantifier == other.quantifier &&
- self.capture == other.capture &&
- self.index == other.index &&
- self.name == other.name
- end
-
- def freeze #:nodoc:
- expression.freeze if expression
- super
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb
deleted file mode 100644
index 03ba29d9a5..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/options.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-module Regin
- class Options
- def self.from_int(flags)
- multiline = flags & Regexp::MULTILINE != 0
- ignorecase = flags & Regexp::IGNORECASE != 0
- extended = flags & Regexp::EXTENDED != 0
-
- new(multiline, ignorecase, extended)
- end
-
- attr_reader :multiline, :ignorecase, :extended
-
- def initialize(*args)
- if args.first.is_a?(Hash)
- @multiline = args[0][:multiline]
- @ignorecase = args[0][:ignorecase]
- @extended = args[0][:extended]
- else
- @multiline = args[0]
- @ignorecase = args[1]
- @extended = args[2]
- end
- end
-
- def any?(explicit = false)
- if explicit
- !multiline.nil? || !ignorecase.nil? || !extended.nil?
- else
- multiline || ignorecase || extended
- end
- end
-
- def to_h(explicit = false)
- if explicit
- options = {}
- options[:multiline] = multiline unless multiline.nil?
- options[:ignorecase] = ignorecase unless ignorecase.nil?
- options[:extended] = extended unless extended.nil?
- options
- else
- { :multiline => multiline,
- :ignorecase => ignorecase,
- :extended => extended }
- end
- end
-
- def to_i
- flag = 0
- flag |= Regexp::MULTILINE if multiline
- flag |= Regexp::IGNORECASE if ignorecase
- flag |= Regexp::EXTENDED if extended
- flag
- end
- end
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb
deleted file mode 100644
index 0bb9b87e9c..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/parser.rb
+++ /dev/null
@@ -1,415 +0,0 @@
-#
-# DO NOT MODIFY!!!!
-# This file is automatically generated by Racc 1.4.6
-# from Racc grammer file "".
-#
-
-require 'racc/parser.rb'
-module Regin
- class Parser < Racc::Parser #:nodoc: all
-
-def self.parse_regexp(regexp)
- options = Options.from_int(regexp.options)
-
- parser = new
- parser.options_stack << options.to_h
-
- expression = parser.scan_str(regexp.source)
- expression = expression.dup(options.to_h) if options.any?
- expression
-end
-
-attr_accessor :options_stack
-
-def initialize
- @capture_index = 0
- @capture_index_stack = []
- @options_stack = []
-end
-
-##### State transition tables begin ###
-
-racc_action_table = [
- 2, 18, 19, 19, 8, 10, 11, 13, 48, 19,
- 2, 45, 3, 5, 8, 10, 11, 13, 64, 47,
- 2, 55, 3, 5, 8, 10, 11, 13, 29, 19,
- 2, 16, 3, 5, 8, 10, 11, 13, 61, 19,
- 2, 63, 3, 5, 8, 10, 11, 13, 60, 36,
- 2, 34, 3, 5, 8, 10, 11, 13, 28, 49,
- 2, nil, 3, 5, 8, 10, 11, 13, nil, nil,
- 2, nil, 3, 5, 8, 10, 11, 13, nil, 26,
- 42, 43, 3, 5, 37, 38, 40, 21, 44, 37,
- 38, 40, 22, 23, 24, 14, nil, 15, 31, 32,
- 16, nil, 33, 46, 32, nil, nil, 33, 51, 37,
- 38, 40, 58, 37, 38, 40, 37, 38, 40, 37,
- 38, 40, 37, 38, 40, 37, 38, 40, 37, 38,
- 40 ]
-
-racc_action_check = [
- 0, 4, 27, 4, 0, 0, 0, 0, 36, 56,
- 49, 27, 0, 0, 49, 49, 49, 49, 56, 36,
- 43, 48, 49, 49, 43, 43, 43, 43, 15, 53,
- 6, 15, 43, 43, 6, 6, 6, 6, 53, 52,
- 42, 55, 6, 6, 42, 42, 42, 42, 52, 24,
- 35, 18, 42, 42, 35, 35, 35, 35, 14, 39,
- 19, nil, 35, 35, 19, 19, 19, 19, nil, nil,
- 13, nil, 19, 19, 13, 13, 13, 13, nil, 13,
- 26, 26, 13, 13, 54, 54, 54, 9, 26, 26,
- 26, 26, 9, 9, 9, 2, nil, 2, 17, 17,
- 2, nil, 17, 30, 30, nil, nil, 30, 41, 41,
- 41, 41, 50, 50, 50, 50, 44, 44, 44, 59,
- 59, 59, 58, 58, 58, 51, 51, 51, 62, 62,
- 62 ]
-
-racc_action_pointer = [
- -3, nil, 91, nil, 1, nil, 27, nil, nil, 75,
- nil, nil, nil, 67, 53, 22, nil, 93, 51, 57,
- nil, nil, nil, nil, 40, nil, 67, 0, nil, nil,
- 98, nil, nil, nil, nil, 47, -1, nil, nil, 46,
- nil, 87, 37, 17, 94, nil, nil, nil, 12, 7,
- 91, 103, 37, 27, 62, 21, 7, nil, 100, 97,
- nil, nil, 106, nil, nil, nil, nil, nil ]
-
-racc_action_default = [
- -37, -13, -37, -19, -37, -20, -2, -4, -11, -6,
- -12, -14, -7, -37, -37, -29, -28, -37, -37, -37,
- -3, -23, -21, -22, -37, -5, -37, -37, -8, -29,
- -37, -9, -27, -26, 68, -1, -37, -34, -35, -37,
- -36, -37, -37, -37, -37, -15, -10, -25, -37, -37,
- -37, -37, -37, -37, -37, -37, -37, -33, -37, -37,
- -17, -18, -37, -24, -16, -32, -31, -30 ]
-
-racc_goto_table = [
- 4, 41, 20, 35, 17, 39, 25, nil, nil, nil,
- nil, nil, nil, 27, nil, nil, 50, 30, nil, 54,
- nil, nil, nil, nil, nil, 57, 59, nil, nil, 62,
- nil, 20, nil, 65, 66, nil, nil, 67, nil, nil,
- nil, nil, 52, 53, nil, nil, nil, nil, nil, 56 ]
-
-racc_goto_check = [
- 1, 10, 3, 2, 7, 9, 5, nil, nil, nil,
- nil, nil, nil, 1, nil, nil, 10, 7, nil, 10,
- nil, nil, nil, nil, nil, 10, 10, nil, nil, 10,
- nil, 3, nil, 10, 10, nil, nil, 10, nil, nil,
- nil, nil, 1, 1, nil, nil, nil, nil, nil, 1 ]
-
-racc_goto_pointer = [
- nil, 0, -16, -4, nil, -3, nil, 2, nil, -21,
- -25 ]
-
-racc_goto_default = [
- nil, nil, 6, 7, 9, nil, 12, nil, 1, nil,
- nil ]
-
-racc_reduce_table = [
- 0, 0, :racc_error,
- 3, 26, :_reduce_1,
- 1, 26, :_reduce_2,
- 2, 27, :_reduce_3,
- 1, 27, :_reduce_4,
- 2, 28, :_reduce_5,
- 1, 28, :_reduce_none,
- 1, 29, :_reduce_none,
- 3, 29, :_reduce_8,
- 3, 29, :_reduce_9,
- 4, 29, :_reduce_10,
- 1, 29, :_reduce_11,
- 1, 29, :_reduce_12,
- 1, 29, :_reduce_13,
- 1, 29, :_reduce_14,
- 3, 31, :_reduce_15,
- 6, 31, :_reduce_16,
- 5, 31, :_reduce_17,
- 5, 31, :_reduce_18,
- 1, 33, :_reduce_none,
- 1, 33, :_reduce_none,
- 1, 30, :_reduce_none,
- 1, 30, :_reduce_none,
- 1, 30, :_reduce_none,
- 5, 30, :_reduce_24,
- 3, 30, :_reduce_25,
- 2, 32, :_reduce_26,
- 2, 32, :_reduce_27,
- 1, 32, :_reduce_none,
- 1, 32, :_reduce_none,
- 4, 34, :_reduce_30,
- 4, 34, :_reduce_31,
- 4, 34, :_reduce_32,
- 3, 34, :_reduce_33,
- 1, 35, :_reduce_34,
- 1, 35, :_reduce_35,
- 1, 35, :_reduce_36 ]
-
-racc_reduce_n = 37
-
-racc_shift_n = 68
-
-racc_token_table = {
- false => 0,
- :error => 1,
- :BAR => 2,
- :LBRACK => 3,
- :CTYPE => 4,
- :RBRACK => 5,
- :NEGATE => 6,
- :CCLASS => 7,
- :DOT => 8,
- :CHAR => 9,
- :LPAREN => 10,
- :RPAREN => 11,
- :QMARK => 12,
- :COLON => 13,
- :NAME => 14,
- :L_ANCHOR => 15,
- :R_ANCHOR => 16,
- :STAR => 17,
- :PLUS => 18,
- :LCURLY => 19,
- :RCURLY => 20,
- :MINUS => 21,
- :MULTILINE => 22,
- :IGNORECASE => 23,
- :EXTENDED => 24 }
-
-racc_nt_base = 25
-
-racc_use_result_var = true
-
-Racc_arg = [
- racc_action_table,
- racc_action_check,
- racc_action_default,
- racc_action_pointer,
- racc_goto_table,
- racc_goto_check,
- racc_goto_default,
- racc_goto_pointer,
- racc_nt_base,
- racc_reduce_table,
- racc_token_table,
- racc_shift_n,
- racc_reduce_n,
- racc_use_result_var ]
-
-Racc_token_to_s_table = [
- "$end",
- "error",
- "BAR",
- "LBRACK",
- "CTYPE",
- "RBRACK",
- "NEGATE",
- "CCLASS",
- "DOT",
- "CHAR",
- "LPAREN",
- "RPAREN",
- "QMARK",
- "COLON",
- "NAME",
- "L_ANCHOR",
- "R_ANCHOR",
- "STAR",
- "PLUS",
- "LCURLY",
- "RCURLY",
- "MINUS",
- "MULTILINE",
- "IGNORECASE",
- "EXTENDED",
- "$start",
- "expression",
- "subexpression",
- "quantified_atom",
- "atom",
- "quantifier",
- "group",
- "bracket_expression",
- "anchor",
- "options",
- "modifier" ]
-
-Racc_debug_parser = false
-
-##### State transition tables end #####
-
-# reduce 0 omitted
-
-def _reduce_1(val, _values, result)
- # TODO remove this conditional by breaking
- # it into another production
- if val[0][0].is_a?(Regin::Alternation)
- alt = val[0][0] + [Expression.new(val[2])]
- else
- alt = Alternation.new(val[0], Expression.new(val[2]))
- end
- result = Expression.new(alt)
-
- result
-end
-
-def _reduce_2(val, _values, result)
- result = Expression.new(val[0])
- result
-end
-
-def _reduce_3(val, _values, result)
- result = val[0] + [val[1]]
- result
-end
-
-def _reduce_4(val, _values, result)
- result = [val[0]]
- result
-end
-
-def _reduce_5(val, _values, result)
- result = val[0].dup(:quantifier => val[1])
- result
-end
-
-# reduce 6 omitted
-
-# reduce 7 omitted
-
-def _reduce_8(val, _values, result)
- result = CharacterClass.new(val[1])
- result
-end
-
-def _reduce_9(val, _values, result)
- result = CharacterClass.new(val[1])
- result
-end
-
-def _reduce_10(val, _values, result)
- result = CharacterClass.new(val[2], :negate => true)
- result
-end
-
-def _reduce_11(val, _values, result)
- result = CharacterClass.new(val[0])
- result
-end
-
-def _reduce_12(val, _values, result)
- result = CharacterClass.new('.')
- result
-end
-
-def _reduce_13(val, _values, result)
- result = Anchor.new(val[0])
- result
-end
-
-def _reduce_14(val, _values, result)
- result = Character.new(val[0])
- result
-end
-
-def _reduce_15(val, _values, result)
- result = Group.new(val[1], :index => @capture_index_stack.pop)
-
- result
-end
-
-def _reduce_16(val, _values, result)
- result = Group.new(val[4], val[2].merge(:capture => false))
- @options_stack.pop
-
- result
-end
-
-def _reduce_17(val, _values, result)
- result = Group.new(val[3], :capture => false);
-
- result
-end
-
-def _reduce_18(val, _values, result)
- result = Group.new(val[3], :name => val[2], :index => @capture_index_stack.pop);
-
- result
-end
-
-# reduce 19 omitted
-
-# reduce 20 omitted
-
-# reduce 21 omitted
-
-# reduce 22 omitted
-
-# reduce 23 omitted
-
-def _reduce_24(val, _values, result)
- result = val.join
- result
-end
-
-def _reduce_25(val, _values, result)
- result = val.join
- result
-end
-
-def _reduce_26(val, _values, result)
- result = val.join
- result
-end
-
-def _reduce_27(val, _values, result)
- result = val.join
- result
-end
-
-# reduce 28 omitted
-
-# reduce 29 omitted
-
-def _reduce_30(val, _values, result)
- @options_stack << result = { val[1] => false, val[2] => false, val[3] => false }
-
- result
-end
-
-def _reduce_31(val, _values, result)
- @options_stack << result = { val[0] => true, val[2] => false, val[3] => false }
-
- result
-end
-
-def _reduce_32(val, _values, result)
- @options_stack << result = { val[0] => true, val[1] => true, val[3] => false }
-
- result
-end
-
-def _reduce_33(val, _values, result)
- @options_stack << result = { val[0] => true, val[1] => true, val[2] => true }
-
- result
-end
-
-def _reduce_34(val, _values, result)
- result = :multiline
- result
-end
-
-def _reduce_35(val, _values, result)
- result = :ignorecase
- result
-end
-
-def _reduce_36(val, _values, result)
- result = :extended
- result
-end
-
-def _reduce_none(val, _values, result)
- val[0]
-end
-
- end # class Parser
-end # module Regin
-
-require 'regin/tokenizer'
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb
deleted file mode 100644
index 59e4ffb611..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/tokenizer.rb
+++ /dev/null
@@ -1,213 +0,0 @@
-#--
-# DO NOT MODIFY!!!!
-# This file is automatically generated by rex 1.0.5.beta1
-# from lexical definition file "lib/regin/tokenizer.rex".
-#++
-
-require 'racc/parser'
-class Regin::Parser < Racc::Parser
- require 'strscan'
-
- class ScanError < StandardError ; end
-
- attr_reader :lineno
- attr_reader :filename
- attr_accessor :state
-
- def scan_setup(str)
- @ss = StringScanner.new(str)
- @lineno = 1
- @state = nil
- end
-
- def action
- yield
- end
-
- def scan_str(str)
- scan_setup(str)
- do_parse
- end
- alias :scan :scan_str
-
- def load_file( filename )
- @filename = filename
- open(filename, "r") do |f|
- scan_setup(f.read)
- end
- end
-
- def scan_file( filename )
- load_file(filename)
- do_parse
- end
-
-
- def next_token
- return if @ss.eos?
-
- text = @ss.peek(1)
- @lineno += 1 if text == "\n"
- token = case @state
- when nil
- case
- when (text = @ss.scan(/\\[dDsSwW]/))
- action { [:CCLASS, text] }
-
- when (text = @ss.scan(/\^|\\A/))
- action { [:L_ANCHOR, text] }
-
- when (text = @ss.scan(/\$|\\Z/))
- action { [:R_ANCHOR, text] }
-
- when (text = @ss.scan(/<(\w+)>/))
- action { [:NAME, @ss[1]] }
-
- when (text = @ss.scan(/\(/))
- action {
- @capture_index_stack << @capture_index
- @capture_index += 1
- @state = :OPTIONS if @ss.peek(1) == '?';
- [:LPAREN, text]
- }
-
-
- when (text = @ss.scan(/\)/))
- action { [:RPAREN, text] }
-
- when (text = @ss.scan(/\[/))
- action { @state = :CCLASS; [:LBRACK, text] }
-
- when (text = @ss.scan(/\{/))
- action { [:LCURLY, text] }
-
- when (text = @ss.scan(/\}/))
- action { [:RCURLY, text] }
-
- when (text = @ss.scan(/\|/))
- action { [:BAR, text] }
-
- when (text = @ss.scan(/\./))
- action { [:DOT, text] }
-
- when (text = @ss.scan(/\?/))
- action { [:QMARK, text] }
-
- when (text = @ss.scan(/\+(?:\?)/))
- action { [:PLUS, text] }
-
- when (text = @ss.scan(/\*(?:\?)/))
- action { [:STAR, text] }
-
- when (text = @ss.scan(/\#/))
- action {
- if @options_stack[-1][:extended]
- @state = :COMMENT;
- next_token
- else
- [:CHAR, text]
- end
- }
-
-
- when (text = @ss.scan(/\s|\n/))
- action {
- if @options_stack[-1][:extended]
- next_token
- else
- [:CHAR, text]
- end
- }
-
-
- when (text = @ss.scan(/\\(.)/))
- action { [:CHAR, @ss[1]] }
-
- when (text = @ss.scan(/./))
- action { [:CHAR, text] }
-
- else
- text = @ss.string[@ss.pos .. -1]
- raise ScanError, "can not match: '" + text + "'"
- end # if
-
- when :CCLASS
- case
- when (text = @ss.scan(/\]/))
- action { @state = nil; [:RBRACK, text] }
-
- when (text = @ss.scan(/\^/))
- action { [:NEGATE, text] }
-
- when (text = @ss.scan(/:(alnum|alpha|ascii|blank|cntrl|digit|graph|lower|print|punct|space|upper|word|xdigit):/))
- action { [:CTYPE, text] }
-
- when (text = @ss.scan(/\\-/))
- action { [:CHAR, text] }
-
- when (text = @ss.scan(/\\(.)/))
- action { [:CHAR, @ss[1]] }
-
- when (text = @ss.scan(/./))
- action { [:CHAR, text] }
-
- else
- text = @ss.string[@ss.pos .. -1]
- raise ScanError, "can not match: '" + text + "'"
- end # if
-
- when :OPTIONS
- case
- when (text = @ss.scan(/\?/))
- action {
- @state = nil unless @ss.peek(1) =~ /-|m|i|x|:/
- [:QMARK, text]
- }
-
-
- when (text = @ss.scan(/\-/))
- action { [:MINUS, text] }
-
- when (text = @ss.scan(/m/))
- action { [:MULTILINE, text] }
-
- when (text = @ss.scan(/i/))
- action { [:IGNORECASE, text] }
-
- when (text = @ss.scan(/x/))
- action { [:EXTENDED, text] }
-
- when (text = @ss.scan(/\:/))
- action {
- @capture_index_stack.pop
- @capture_index -= 1
- @state = nil;
- [:COLON, text]
- }
-
-
- else
- text = @ss.string[@ss.pos .. -1]
- raise ScanError, "can not match: '" + text + "'"
- end # if
-
- when :COMMENT
- case
- when (text = @ss.scan(/\n/))
- action { @state = nil; next_token }
-
- when (text = @ss.scan(/./))
- action { next_token }
-
- else
- text = @ss.string[@ss.pos .. -1]
- raise ScanError, "can not match: '" + text + "'"
- end # if
-
- else
- raise ScanError, "undefined state: '" + state.to_s + "'"
- end # case state
- token
- end # def next_token
-
-end # class
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb
deleted file mode 100644
index 7ad2a5a25e..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/vendor/regin/regin/version.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module Regin
- Version = '0.3.3'
-end
diff --git a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb b/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb
deleted file mode 100644
index a3688b102e..0000000000
--- a/actionpack/lib/action_dispatch/vendor/rack-mount-0.6.6.pre/rack/mount/version.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Rack
- module Mount
- Version = '0.6.6.pre'
- end
-end
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 532d060c06..7eaf7d0534 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -3,7 +3,7 @@ module ActionPack
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 9f56cca869..c0d7423682 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -23,6 +23,7 @@
activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
+
require 'active_support/ruby/shim'
require 'active_support/core_ext/class/attribute_accessors'
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index ba3bdd0d18..b7ffa345cc 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -20,7 +20,6 @@ module ActionView #:nodoc:
autoload :NumberHelper
autoload :PrototypeHelper
autoload :RawOutputHelper
- autoload :RecordIdentificationHelper
autoload :RecordTagHelper
autoload :SanitizeHelper
autoload :ScriptaculousHelper
@@ -51,7 +50,6 @@ module ActionView #:nodoc:
include NumberHelper
include PrototypeHelper
include RawOutputHelper
- include RecordIdentificationHelper
include RecordTagHelper
include SanitizeHelper
include ScriptaculousHelper
diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
index cb5a1404ff..8e7cf2e701 100644
--- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb
@@ -10,7 +10,7 @@ module ActionView
# Full usage example:
#
# config/routes.rb:
- # Basecamp::Application.routes.draw do |map|
+ # Basecamp::Application.routes.draw do
# resources :posts
# root :to => "posts#index"
# end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index f9105ca364..89e95e8694 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -165,7 +165,7 @@ module ActionView
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if output_buffer && buf.respond_to?(:force_encoding)
+ buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer
yield
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index f097b9a5a3..8050669adb 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -800,7 +800,8 @@ module ActionView
start = options.delete(:start) || 0
stop = options.delete(:end) || 59
step = options.delete(:step) || 1
- leading_zeros = options.delete(:leading_zeros).nil? ? true : false
+ options.reverse_merge!({:leading_zeros => true})
+ leading_zeros = options.delete(:leading_zeros)
select_options = []
start.step(stop, step) do |i|
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6302491c2a..ebe055bebd 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -150,10 +150,6 @@ module ActionView
# here a named route directly as well. Defaults to the current action.
# * <tt>:html</tt> - Optional HTML attributes for the form tag.
#
- # Worth noting is that the +form_for+ tag is called in a ERb evaluation
- # block, not an ERb output block. So that's <tt><% %></tt>, not
- # <tt><%= %></tt>.
- #
# Also note that +form_for+ doesn't create an exclusive scope. It's still
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
@@ -219,7 +215,7 @@ module ActionView
# ...
# <% end %>
#
- # If your resource has associations defined, for example, you want to add comments
+ # If your resource has associations defined, for example, you want to add comments
# to the post given that the routes are set correctly:
#
# <%= form_for([@document, @comment]) do |f| %>
@@ -302,12 +298,12 @@ module ActionView
object_name = record_or_name_or_array
when Array
object = record_or_name_or_array.last
- object_name = options[:as] || ActionController::RecordIdentifier.singular_class_name(object)
+ object_name = options[:as] || ActiveModel::Naming.singular(object)
apply_form_for_options!(record_or_name_or_array, options)
args.unshift object
else
object = record_or_name_or_array
- object_name = options[:as] || ActionController::RecordIdentifier.singular_class_name(object)
+ object_name = options[:as] || ActiveModel::Naming.singular(object)
apply_form_for_options!([object], options)
args.unshift object
end
@@ -533,7 +529,7 @@ module ActionView
object = args.first
else
object = record_or_name_or_array
- object_name = ActionController::RecordIdentifier.singular_class_name(object)
+ object_name = ActiveModel::Naming.singular(object)
end
builder = options[:builder] || ActionView::Base.default_form_builder
@@ -587,8 +583,9 @@ module ActionView
# 'Accept <a href="/terms">Terms</a>.'
# end
def label(object_name, method, content_or_options = nil, options = nil, &block)
- if block_given?
- options = content_or_options if content_or_options.is_a?(Hash)
+ content_is_options = content_or_options.is_a?(Hash)
+ if content_is_options || block_given?
+ options = content_or_options if content_is_options
text = nil
else
text = content_or_options
@@ -676,7 +673,7 @@ module ActionView
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
#
def file_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -1009,9 +1006,9 @@ module ActionView
def value_before_type_cast(object, method_name)
unless object.nil?
- object.respond_to?(method_name + "_before_type_cast") ?
- object.send(method_name + "_before_type_cast") :
- object.send(method_name)
+ object.respond_to?(method_name) ?
+ object.send(method_name) :
+ object.send(method_name + "_before_type_cast")
end
end
@@ -1156,11 +1153,11 @@ module ActionView
end
when Array
object = record_or_name_or_array.last
- name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]"
args.unshift(object)
else
object = record_or_name_or_array
- name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
+ name = "#{object_name}#{index}[#{ActiveModel::Naming.singular(object)}]"
args.unshift(object)
end
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 6f9d14de8b..ee34452769 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -447,7 +447,7 @@ module ActionView
# wrap the output in an appropriate <tt><select></tt> tag.
def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil)
body = ''
- body << content_tag(:option, prompt, :value => "") if prompt
+ body << content_tag(:option, prompt, { :value => "" }, true) if prompt
grouped_options = grouped_options.sort if grouped_options.is_a?(Hash)
@@ -593,11 +593,11 @@ module ActionView
private
def add_options(option_tags, options, value = nil)
if options[:include_blank]
- option_tags = "<option value=\"\">#{options[:include_blank] if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
+ option_tags = "<option value=\"\">#{html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags
end
if value.blank? && options[:prompt]
prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select')
- option_tags = "<option value=\"\">#{prompt}</option>\n" + option_tags
+ option_tags = "<option value=\"\">#{html_escape(prompt)}</option>\n" + option_tags
end
option_tags.html_safe
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 4c1b751160..1ea870426a 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -1,6 +1,5 @@
require 'cgi'
require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
module ActionView
@@ -527,7 +526,7 @@ module ActionView
private
def html_options_for_form(url_for_options, options, *parameters_for_url)
- returning options.stringify_keys do |html_options|
+ options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
# responsability of the caller to escape all the values.
@@ -539,7 +538,7 @@ module ActionView
def extra_tags_for_form(html_options)
snowman_tag = tag(:input, :type => "hidden",
- :name => "_snowman", :value => "&#9731;".html_safe)
+ :name => "_e", :value => "&#9731;".html_safe)
method = html_options.delete("method").to_s
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 37e5d91d8b..f11027bc93 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -186,14 +186,7 @@ module ActionView
# number_with_delimiter(12345678.05, :locale => :fr) # => 12 345 678,05
# number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",")
# # => 98 765 432,98
- #
- # You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
- # +delimiter+ as its optional second and the +separator+ as its
- # optional third parameter:
- # number_with_delimiter(12345678, " ") # => 12 345 678
- # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
- def number_with_delimiter(number, *args)
- options = args.extract_options!
+ def number_with_delimiter(number, options = {})
options.symbolize_keys!
begin
@@ -207,14 +200,6 @@ module ActionView
end
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
-
- unless args.empty?
- ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
- 'instead of separate delimiter and precision arguments.', caller)
- options[:delimiter] ||= args[0] if args[0]
- options[:separator] ||= args[1] if args[1]
- end
-
options = options.reverse_merge(defaults)
parts = number.to_s.split('.')
@@ -249,13 +234,7 @@ module ActionView
# number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
- #
- # You can still use <tt>number_with_precision</tt> with the old API that accepts the
- # +precision+ as its optional second parameter:
- # number_with_precision(111.2345, 2) # => 111.23
- def number_with_precision(number, *args)
-
- options = args.extract_options!
+ def number_with_precision(number, options = {})
options.symbolize_keys!
number = begin
@@ -272,13 +251,6 @@ module ActionView
precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(precision_defaults)
- #Backwards compatibility
- unless args.empty?
- ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
- 'instead of a separate precision argument.', caller)
- options[:precision] ||= args[0] if args[0]
- end
-
options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
precision = options.delete :precision
significant = options.delete :significant
@@ -337,13 +309,7 @@ module ActionView
# <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision=>5) # => "500 MB"
- #
- # You can still use <tt>number_to_human_size</tt> with the old API that accepts the
- # +precision+ as its optional second parameter:
- # number_to_human_size(1234567, 1) # => 1 MB
- # number_to_human_size(483989, 2) # => 470 KB
- def number_to_human_size(number, *args)
- options = args.extract_options!
+ def number_to_human_size(number, options = {})
options.symbolize_keys!
number = begin
@@ -359,13 +325,7 @@ module ActionView
defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(human)
-
- unless args.empty?
- ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
- 'instead of a separate precision argument.', caller)
- options[:precision] ||= args[0] if args[0]
- end
-
+
options = options.reverse_merge(defaults)
#for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index 5c0ff5d59c..99f9363a9a 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -102,7 +102,7 @@ module ActionView
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
# Returns the JavaScript needed for a remote function.
- # Takes the same arguments as link_to_remote.
+ # See the link_to_remote documentation at http://github.com/rails/prototype_legacy_helper as it takes the same arguments.
#
# Example:
# # Generates: <select id="options" onchange="new Ajax.Updater('options',
@@ -139,7 +139,7 @@ module ActionView
function = "if (#{options[:condition]}) { #{function}; }" if options[:condition]
function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm]
- return function
+ return function.html_safe
end
# All the methods were moved to GeneratorMethods so that
diff --git a/actionpack/lib/action_view/helpers/record_identification_helper.rb b/actionpack/lib/action_view/helpers/record_identification_helper.rb
deleted file mode 100644
index 372f1cb8aa..0000000000
--- a/actionpack/lib/action_view/helpers/record_identification_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-module ActionView
- # = Action View Record Identification Helpers
- #
- # See ActionController::RecordIdentifier for documentation on these methods.
- module Helpers
- module RecordIdentificationHelper
- # See ActionController::RecordIdentifier.partial_path -- this is just a delegate to that for convenient access in the view.
- def partial_path(*args, &block)
- ActionController::RecordIdentifier.partial_path(*args, &block)
- end
-
- # See ActionController::RecordIdentifier.dom_class -- this is just a delegate to that for convenient access in the view.
- def dom_class(*args, &block)
- ActionController::RecordIdentifier.dom_class(*args, &block)
- end
-
- # See ActionController::RecordIdentifier.dom_id -- this is just a delegate to that for convenient access in the view.
- def dom_id(*args, &block)
- ActionController::RecordIdentifier.dom_id(*args, &block)
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 7433f08777..e4a9210cde 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -1,7 +1,11 @@
+require 'action_controller/record_identifier'
+
module ActionView
# = Action View Record Tag Helpers
module Helpers
module RecordTagHelper
+ include ActionController::RecordIdentifier
+
# Produces a wrapper DIV element with id and class parameters that
# relate to the specified Active Record object. Usage example:
#
diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb
index b47818a22a..d82005fa24 100644
--- a/actionpack/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/object/try'
require 'action_controller/vendor/html-scanner'
require 'action_view/helpers/tag_helper'
@@ -7,6 +8,7 @@ module ActionView
# The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements.
# These helper methods extend Action View making them callable within your template files.
module SanitizeHelper
+ extend ActiveSupport::Concern
# This +sanitize+ helper will html encode all tags and strip all attributes that
# aren't specifically allowed.
#
@@ -32,13 +34,13 @@ module ActionView
#
# Add table tags to the default allowed tags
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
# Remove tags to the default allowed tags
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.after_initialize do
# ActionView::Base.sanitized_allowed_tags.delete 'div'
# end
@@ -46,7 +48,7 @@ module ActionView
#
# Change allowed default attributes
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style'
# end
#
@@ -143,7 +145,7 @@ module ActionView
# Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to +sanitize+.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.full_sanitizer = MySpecialSanitizer.new
# end
#
@@ -154,7 +156,7 @@ module ActionView
# Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
# any object that responds to +sanitize+.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.link_sanitizer = MySpecialSanitizer.new
# end
#
@@ -165,7 +167,7 @@ module ActionView
# Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to +sanitize+.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.white_list_sanitizer = MySpecialSanitizer.new
# end
#
@@ -175,7 +177,7 @@ module ActionView
# Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_uri_attributes = 'lowsrc', 'target'
# end
#
@@ -185,7 +187,7 @@ module ActionView
# Adds to the Set of 'bad' tags for the +sanitize+ helper.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_bad_tags = 'embed', 'object'
# end
#
@@ -195,7 +197,7 @@ module ActionView
# Adds to the Set of allowed tags for the +sanitize+ helper.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
# end
#
@@ -205,7 +207,7 @@ module ActionView
# Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc'
# end
#
@@ -215,7 +217,7 @@ module ActionView
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_css_properties = 'expression'
# end
#
@@ -225,7 +227,7 @@ module ActionView
# Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_css_keywords = 'expression'
# end
#
@@ -235,7 +237,7 @@ module ActionView
# Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_shorthand_css_properties = 'expression'
# end
#
@@ -245,7 +247,7 @@ module ActionView
# Adds to the Set of allowed protocols for the +sanitize+ helper.
#
- # Rails::Initializer.run do |config|
+ # class Application < Rails::Application
# config.action_view.sanitized_allowed_protocols = 'ssh', 'feed'
# end
#
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 0be8a2c36e..c1de5c8cb3 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -10,6 +10,9 @@ module ActionView
# your views. These helper methods extend Action View making them callable
# within your template files.
module TextHelper
+ extend ActiveSupport::Concern
+
+ include SanitizeHelper
# The preferred method of outputting text in your views is to use the
# <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods
# do not operate as expected in an eRuby code block. If you absolutely must
@@ -61,27 +64,8 @@ module ActionView
#
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
- #
- # 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..."
- #
- # 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?
- ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' +
- 'length and omission arguments', caller)
-
- options[:length] = args[0] || 30
- options[:omission] = args[1] || "..."
- end
-
+ def truncate(text, options = {})
options.reverse_merge!(:length => 30)
-
text.truncate(options.delete(:length), options) if text
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 0e3cc54fd1..a5c6718c58 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -95,7 +95,7 @@ module ActionView
when String
options
when Hash
- options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
+ options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?)
super
when :back
controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
@@ -398,7 +398,7 @@ module ActionView
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
if condition
if block_given?
- block.arity <= 1 ? yield(name) : yield(name, options, html_options)
+ block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
else
name
end
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 56aebca957..137281e5e9 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -37,7 +37,7 @@ module ActionView
include ActionController::TemplateAssertions
include ActionView::Context
- include ActionController::PolymorphicRoutes
+ include ActionDispatch::Routing::PolymorphicRoutes
include ActionController::RecordIdentifier
include AbstractController::Helpers
diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb
index 3b5013a47a..19855490b4 100644
--- a/actionpack/test/abstract/abstract_controller_test.rb
+++ b/actionpack/test/abstract/abstract_controller_test.rb
@@ -250,5 +250,19 @@ module AbstractController
end
end
+ class Me6 < AbstractController::Base
+ self.action_methods
+
+ def index
+ end
+ end
+
+ class TestActionMethodsReloading < ActiveSupport::TestCase
+
+ test "action_methods should be reloaded after defining a new method" do
+ assert_equal ["index"], Me6.action_methods
+ end
+ end
+
end
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 765e111226..53cdd358b4 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -482,21 +482,6 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
assert_redirected_to :controller => 'admin/user'
end
- def test_assert_valid
- get :get_valid_record
- assert_deprecated { assert_valid assigns('record') }
- end
-
- def test_assert_valid_failing
- get :get_invalid_record
-
- begin
- assert_deprecated { assert_valid assigns('record') }
- assert false
- rescue ActiveSupport::TestCase::Assertion => e
- end
- end
-
def test_assert_response_uses_exception_message
@controller = AssertResponseWithUnexpectedErrorController.new
get :index
diff --git a/actionpack/test/controller/record_identifier_test.rb b/actionpack/test/controller/record_identifier_test.rb
index 6a84475758..835a0e970b 100644
--- a/actionpack/test/controller/record_identifier_test.rb
+++ b/actionpack/test/controller/record_identifier_test.rb
@@ -26,20 +26,6 @@ class Sheep
end
end
-class Comment::Nested < Comment; end
-
-class Test::Unit::TestCase
- protected
- def comments_url
- 'http://www.example.com/comments'
- end
-
- def comment_url(comment)
- "http://www.example.com/comments/#{comment.id}"
- end
-end
-
-
class RecordIdentifierTest < Test::Unit::TestCase
include ActionController::RecordIdentifier
@@ -76,30 +62,4 @@ class RecordIdentifierTest < Test::Unit::TestCase
def test_dom_class_with_prefix
assert_equal "custom_prefix_#{@singular}", dom_class(@record, :custom_prefix)
end
-
- def test_singular_class_name
- assert_equal @singular, singular_class_name(@record)
- end
-
- def test_singular_class_name_for_class
- assert_equal @singular, singular_class_name(@klass)
- end
-
- def test_plural_class_name
- assert_equal @plural, plural_class_name(@record)
- end
-
- def test_plural_class_name_for_class
- assert_equal @plural, plural_class_name(@klass)
- end
-
- def test_uncountable
- assert_equal true, uncountable?(@uncountable)
- assert_equal false, uncountable?(@klass)
- end
-
- private
- def method_missing(method, *args)
- RecordIdentifier.send(method, *args)
- end
end
diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb
index 0f64b77647..a24de62b19 100644
--- a/actionpack/test/controller/rescue_test.rb
+++ b/actionpack/test/controller/rescue_test.rb
@@ -79,6 +79,14 @@ class RescueController < ActionController::Base
render :text => 'no way'
end
+ rescue_from ActionView::TemplateError do
+ render :text => 'action_view templater error'
+ end
+
+ rescue_from IOError do
+ render :text => 'io error'
+ end
+
before_filter(:only => :before_filter_raises) { raise 'umm nice' }
def before_filter_raises
@@ -141,6 +149,14 @@ class RescueController < ActionController::Base
def missing_template
end
+
+ def io_error_in_view
+ raise ActionView::TemplateError.new(nil, {}, IOError.new('this is io error'))
+ end
+
+ def zero_division_error_in_view
+ raise ActionView::TemplateError.new(nil, {}, ZeroDivisionError.new('this is zero division error'))
+ end
protected
def deny_access
@@ -228,6 +244,17 @@ class ControllerInheritanceRescueControllerTest < ActionController::TestCase
end
class RescueControllerTest < ActionController::TestCase
+
+ def test_io_error_in_view
+ get :io_error_in_view
+ assert_equal 'io error', @response.body
+ end
+
+ def test_zero_division_error_in_view
+ get :zero_division_error_in_view
+ assert_equal 'action_view templater error', @response.body
+ end
+
def test_rescue_handler
get :not_authorized
assert_response :forbidden
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index a9d1c55c05..6c8f470fba 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/object/try'
require 'abstract_unit'
class ResourcesController < ActionController::Base
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index f9fc7a0976..13c9d9ee38 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'controller/fake_controllers'
+require 'active_support/ordered_hash'
class TestTest < ActionController::TestCase
class TestController < ActionController::Base
@@ -137,14 +138,14 @@ XML
end
def test_raw_post_handling
- params = {:page => {:name => 'page name'}, 'some key' => 123}
+ params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123]
post :render_raw_post, params.dup
assert_equal params.to_query, @response.body
end
def test_body_stream
- params = { :page => { :name => 'page name' }, 'some key' => 123 }
+ params = ActiveSupport::OrderedHash[:page, { :name => 'page name' }, 'some key', 123]
post :render_body, params.dup
@@ -461,6 +462,13 @@ XML
def test_assert_routing_in_module
assert_routing 'admin/user', :controller => 'admin/user', :action => 'index'
end
+
+ def test_assert_routing_with_glob
+ with_routing do |set|
+ set.draw { |map| match('*path' => "pages#show") }
+ assert_routing('/company/about', { :controller => 'pages', :action => 'show', :path => 'company/about' })
+ end
+ end
def test_params_passing
get :test_params, :page => {:name => "Page name", :month => '4', :year => '2004', :day => '6'}
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 170c5b8565..6a1a4f556f 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -66,6 +66,16 @@ class MiddlewareStackTest < ActiveSupport::TestCase
assert_equal BazMiddleware, @stack[0].klass
end
+ test "raise an error on invalid index" do
+ assert_raise RuntimeError do
+ @stack.insert("HiyaMiddleware", BazMiddleware)
+ end
+
+ assert_raise RuntimeError do
+ @stack.insert_after("HiyaMiddleware", BazMiddleware)
+ end
+ end
+
test "lazy evaluates middleware class" do
assert_difference "@stack.size" do
@stack.use "MiddlewareStackTest::BazMiddleware"
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index e5ee412021..c8947aac80 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -392,19 +392,19 @@ class RequestTest < ActiveSupport::TestCase
[{'baz'=>[{'foo'=>'baz'}, "1"]}, {'baz'=>[{'foo'=>'[FILTERED]'}, "1"]}, [/foo/]]]
test_hashes.each do |before_filter, after_filter, filter_words|
- request = stub_request('action_dispatch.parameter_filter' => filter_words)
- assert_equal after_filter, request.send(:process_parameter_filter, before_filter)
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
+ assert_equal after_filter, parameter_filter.filter(before_filter)
filter_words << 'blah'
filter_words << lambda { |key, value|
value.reverse! if key =~ /bargain/
}
- request = stub_request('action_dispatch.parameter_filter' => filter_words)
+ parameter_filter = ActionDispatch::Http::ParameterFilter.new(filter_words)
before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
- assert_equal after_filter, request.send(:process_parameter_filter, before_filter)
+ assert_equal after_filter, parameter_filter.filter(before_filter)
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 2a014bf976..3f090b7254 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -1,3 +1,4 @@
+require 'erb'
require 'abstract_unit'
require 'controller/fake_controllers'
@@ -56,7 +57,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
match 'account/proc/:name', :to => redirect {|params| "/#{params[:name].pluralize}" }
match 'account/proc_req' => redirect {|params, req| "/#{req.method}" }
- match 'account/google' => redirect('http://www.google.com/')
+ match 'account/google' => redirect('http://www.google.com/', :status => 302)
match 'openid/login', :via => [:get, :post], :to => "openid#login"
@@ -245,7 +246,8 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
namespace :account do
match 'shorthand'
- match 'description', :to => "description", :as => "description"
+ match 'description', :to => :description, :as => "description"
+ match ':action/callback', :action => /twitter|github/, :to => "callbacks", :as => :callback
resource :subscription, :credit, :credit_card
root :to => "account#index"
@@ -500,9 +502,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_login_redirect
with_test_routes do
get '/account/login'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/login', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/login'
end
end
@@ -510,18 +510,14 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
with_test_routes do
assert_equal '/account/logout', logout_redirect_path
get '/account/logout'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/logout', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/logout'
end
end
def test_namespace_redirect
with_test_routes do
get '/private'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/private/index', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/private/index'
end
end
@@ -585,27 +581,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
def test_redirect_modulo
with_test_routes do
get '/account/modulo/name'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/names', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/names'
end
end
def test_redirect_proc
with_test_routes do
get '/account/proc/person'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/people', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/people'
end
end
def test_redirect_proc_with_request
with_test_routes do
get '/account/proc_req'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com/GET', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com/GET'
end
end
@@ -1159,7 +1149,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_convention_match_with_no_scope
+ def test_match_shorthand_with_no_scope
with_test_routes do
assert_equal '/account/overview', account_overview_path
get '/account/overview'
@@ -1167,7 +1157,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_convention_match_inside_namespace
+ def test_match_shorthand_inside_namespace
with_test_routes do
assert_equal '/account/shorthand', account_shorthand_path
get '/account/shorthand'
@@ -1175,6 +1165,17 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
+ def test_scoped_controller_with_namespace_and_action
+ with_test_routes do
+ assert_equal '/account/twitter/callback', account_callback_path("twitter")
+ get '/account/twitter/callback'
+ assert_equal 'account/callbacks#twitter', @response.body
+
+ get '/account/whatever/callback'
+ assert_equal 'Not Found', @response.body
+ end
+ end
+
def test_convention_match_nested_and_with_leading_slash
with_test_routes do
assert_equal '/account/nested/overview', account_nested_overview_path
@@ -1191,12 +1192,10 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- def test_redirect_with_complete_url
+ def test_redirect_with_complete_url_and_status
with_test_routes do
get '/account/google'
- assert_equal 301, @response.status
- assert_equal 'http://www.google.com/', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.google.com/', 302
end
end
@@ -1204,9 +1203,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
previous_host, self.host = self.host, 'www.example.com:3000'
with_test_routes do
get '/account/login'
- assert_equal 301, @response.status
- assert_equal 'http://www.example.com:3000/login', @response.headers['Location']
- assert_equal 'Moved Permanently', @response.body
+ verify_redirect 'http://www.example.com:3000/login'
end
ensure
self.host = previous_host
@@ -1887,8 +1884,18 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
end
end
- private
- def with_test_routes
- yield
- end
+private
+ def with_test_routes
+ yield
+ end
+
+ def verify_redirect(url, status=301)
+ assert_equal status, @response.status
+ assert_equal url, @response.headers['Location']
+ assert_equal expected_redirect_body(url), @response.body
+ end
+
+ def expected_redirect_body(url)
+ %(<html><body>You are being <a href="#{ERB::Util.h(url)}">redirected</a>.</body></html>)
+ end
end
diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb
index f0e01bfff0..3864821ef0 100644
--- a/actionpack/test/dispatch/session/cookie_store_test.rb
+++ b/actionpack/test/dispatch/session/cookie_store_test.rb
@@ -44,7 +44,12 @@ class CookieStoreTest < ActionController::IntegrationTest
session[:foo] = 'bye!' * 1024
head :ok
end
-
+
+ def change_session_id
+ request.session_options[:id] = nil
+ get_session_id
+ end
+
def rescue_action(e) raise end
end
@@ -212,6 +217,19 @@ class CookieStoreTest < ActionController::IntegrationTest
end
end
+ def test_setting_session_id_to_nil_is_respected
+ with_test_route_set do
+ cookies[SessionKey] = SignedBar
+
+ get "/get_session_id"
+ sid = response.body
+ assert_equal sid.size, 36
+
+ get "/change_session_id"
+ assert_not_equal sid, response.body
+ end
+ end
+
def test_session_store_with_expire_after
with_test_route_set(:expire_after => 5.hours) do
# First request accesses the session
diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb
index 9f3d68a639..f7c42c7f22 100644
--- a/actionpack/test/template/capture_helper_test.rb
+++ b/actionpack/test/template/capture_helper_test.rb
@@ -114,7 +114,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def view_with_controller
- returning(TestController.new.view_context) do |view|
+ TestController.new.view_context.tap do |view|
view.output_buffer = ActionView::OutputBuffer.new
end
end
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 4b9e41803f..be66710ae5 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -4,6 +4,16 @@ require 'controller/fake_models'
class FormHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormHelper
+ class Developer
+ def name_before_type_cast
+ "David"
+ end
+
+ def name
+ "Santiago"
+ end
+ end
+
def form_for(*)
@output_buffer = super
end
@@ -110,6 +120,13 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
+ def test_label_with_locales_and_options
+ old_locale, I18n.locale = I18n.locale, :label
+ assert_dom_equal('<label for="post_body" class="post_body">Write entire text here</label>', label(:post, :body, :class => 'post_body'))
+ ensure
+ I18n.locale = old_locale
+ end
+
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -200,6 +217,11 @@ class FormHelperTest < ActionView::TestCase
assert_equal object_name, "post[]"
end
+ def test_file_field_has_no_size
+ expected = '<input id="user_avatar" name="user[avatar]" type="file" />'
+ assert_dom_equal expected, file_field("user", "avatar")
+ end
+
def test_hidden_field
assert_dom_equal '<input id="post_title" name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title")
@@ -228,6 +250,13 @@ class FormHelperTest < ActionView::TestCase
text_field("user", "email", :type => "email")
end
+ def test_text_field_from_a_user_defined_method
+ @developer = Developer.new
+ assert_dom_equal(
+ '<input id="developer_name" name="developer[name]" size="30" type="text" value="Santiago" />', text_field("developer", "name")
+ )
+ end
+
def test_check_box
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
@@ -598,7 +627,7 @@ class FormHelperTest < ActionView::TestCase
def test_form_for_with_symbol_object_name
form_for(@post, :as => "other_name", :html => { :id => 'create-post' }) do |f|
- concat f.label(:title)
+ concat f.label(:title, :class => 'post_title')
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
@@ -606,7 +635,7 @@ class FormHelperTest < ActionView::TestCase
end
expected = whole_form("/posts/123", "create-post", "other_name_edit", :method => "put") do
- "<label for='other_name_title'>Title</label>" +
+ "<label for='other_name_title' class='post_title'>Title</label>" +
"<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" +
"<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='other_name[secret]' value='0' type='hidden' />" +
@@ -1484,7 +1513,7 @@ class FormHelperTest < ActionView::TestCase
def snowman(method = nil)
txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_e" type="hidden" value="&#9731;" />}
txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
txt << %{</div>}
end
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 65b5f5ccc1..d14e5020c7 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -210,6 +210,12 @@ class FormOptionsHelperTest < ActionView::TestCase
assert grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]]).html_safe?
end
+ def test_grouped_options_for_select_with_prompt_returns_html_escaped_string
+ assert_dom_equal(
+ "<option value=\"\">&lt;Choose One&gt;</option><optgroup label=\"Hats\"><option value=\"Baseball Cap\">Baseball Cap</option>\n<option value=\"Cowboy Hat\">Cowboy Hat</option></optgroup>",
+ grouped_options_for_select([["Hats", ["Baseball Cap","Cowboy Hat"]]], nil, '<Choose One>'))
+ end
+
def test_optgroups_with_with_options_with_hash
assert_dom_equal(
"<optgroup label=\"Europe\"><option value=\"Denmark\">Denmark</option>\n<option value=\"Germany\">Germany</option></optgroup><optgroup label=\"North America\"><option value=\"United States\">United States</option>\n<option value=\"Canada\">Canada</option></optgroup>",
@@ -367,6 +373,15 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_blank_as_string_escaped
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">&lt;None&gt;</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :include_blank => '<None>')
+ )
+ end
+
def test_select_with_default_prompt
@post = Post.new
@post.category = ""
@@ -394,6 +409,14 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_given_prompt_escaped
+ @post = Post.new
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">&lt;The prompt&gt;</option>\n<option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ select("post", "category", %w( abe <mus> hest), :prompt => '<The prompt>')
+ )
+ end
+
def test_select_with_prompt_and_blank
@post = Post.new
@post.category = ""
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 8a0f352bc0..6c85952d40 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -12,7 +12,7 @@ class FormTagHelperTest < ActionView::TestCase
method = options[:method]
txt = %{<div style="margin:0;padding:0;display:inline">}
- txt << %{<input name="_snowman" type="hidden" value="&#9731;" />}
+ txt << %{<input name="_e" type="hidden" value="&#9731;" />}
txt << %{<input name="_method" type="hidden" value="#{method}" />} if method
txt << %{</div>}
end
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index c5c2a6b952..a8ca19931b 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -89,6 +89,15 @@ class JavaScriptHelperTest < ActionView::TestCase
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
end
+ def test_link_to_function_with_inner_block_does_not_raise_exception
+ html = link_to_function( "Greet me!" ) do |page|
+ page.replace_html 'header', (content_tag :h1 do
+ 'Greetings'
+ end)
+ end
+ assert_dom_equal %(<a href="#" onclick="Element.update(&quot;header&quot;, &quot;\\u003Ch1\\u003EGreetings\\u003C/h1\\u003E&quot;);; return false;">Greet me!</a>), html
+ end
+
def test_javascript_tag
self.output_buffer = 'foo'
diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb
index 14e81fc9dc..7f787b7b00 100644
--- a/actionpack/test/template/number_helper_test.rb
+++ b/actionpack/test/template/number_helper_test.rb
@@ -82,16 +82,6 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',')
end
- def test_number_with_delimiter_old_api
- silence_deprecation_warnings
- assert_equal '12 345 678', number_with_delimiter(12345678, " ")
- assert_equal '12-345-678.05', number_with_delimiter(12345678.05, '-')
- assert_equal '12.345.678,05', number_with_delimiter(12345678.05, '.', ',')
- assert_equal '12,345,678.05', number_with_delimiter(12345678.05, ',', '.')
- assert_equal '12 345 678-05', number_with_delimiter(12345678.05, ',', '.', :delimiter => ' ', :separator => '-')
- restore_deprecation_warnings
- end
-
def test_number_with_precision
assert_equal("111.235", number_with_precision(111.2346))
assert_equal("31.83", number_with_precision(31.825, :precision => 2))
@@ -146,15 +136,6 @@ class NumberHelperTest < ActionView::TestCase
assert_equal "12", number_with_precision("12.3", :precision => 0, :significant => true )
end
- def test_number_with_precision_old_api
- silence_deprecation_warnings
- assert_equal("31.8250", number_with_precision(31.825, 4))
- assert_equal("111.235", number_with_precision(111.2346, 3))
- assert_equal("111.00", number_with_precision(111, 2))
- assert_equal("111.000", number_with_precision(111, 2, :precision =>3))
- restore_deprecation_warnings
- end
-
def test_number_to_human_size
assert_equal '0 Bytes', number_to_human_size(0)
assert_equal '1 Byte', number_to_human_size(1)
@@ -202,14 +183,6 @@ class NumberHelperTest < ActionView::TestCase
assert_equal '1.000,1 TB', number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',')
end
- def test_number_to_human_size_old_api
- silence_deprecation_warnings
- assert_equal '1.3143 KB', number_to_human_size(kilobytes(1.3143), 4, :significant => false)
- assert_equal '10.45 KB', number_to_human_size(kilobytes(10.453), 4)
- assert_equal '10 KB', number_to_human_size(kilobytes(10.453), 4, :precision => 2)
- restore_deprecation_warnings
- end
-
def test_number_to_human
assert_equal '123', number_to_human(123)
assert_equal '1.23 Thousand', number_to_human(1234)
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index 0ff37f44c2..036a44730c 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -104,6 +104,12 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_equal javascript_tag(create_generator(&block).to_s, {:defer => 'true'}), update_page_tag({:defer => 'true'}, &block)
end
+ def test_remote_function
+ res = remote_function(:url => authors_path, :with => "'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')")
+ assert_equal "new Ajax.Request('/authors', {asynchronous:true, evalScripts:true, parameters:'author[name]='+$F('author_name')+'&author[dob]='+$F('author_dob')})", res
+ assert res.html_safe?
+ end
+
protected
def author_path(record)
"/authors/#{record.id}"
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 048f96c9a9..d59bbec4a9 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -1,6 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
-require 'active_support/ordered_options'
+require 'active_support/ordered_hash'
require 'controller/fake_controllers'
class UrlHelperTest < ActiveSupport::TestCase
@@ -31,17 +31,13 @@ class UrlHelperTest < ActiveSupport::TestCase
{}
end
- def abcd(hash = {})
- hash_for(:a => :b, :c => :d).merge(hash)
- end
-
- def hash_for(opts = {})
- {:controller => "foo", :action => "bar"}.merge(opts)
+ def hash_for(opts = [])
+ ActiveSupport::OrderedHash[*([:controller, "foo", :action, "bar"].concat(opts))]
end
alias url_hash hash_for
def test_url_for_does_not_escape_urls
- assert_equal "/?a=b&c=d", url_for(abcd)
+ assert_equal "/?a=b&c=d", url_for(hash_for([:a, :b, :c, :d]))
end
def test_url_for_with_back
@@ -128,7 +124,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_host_option
- hash = hash_for(:host => "www.example.com")
+ hash = hash_for([:host, "www.example.com"])
expected = %q{<a href="http://www.example.com/">Test Link</a>}
assert_dom_equal(expected, link_to('Test Link', hash))
end
@@ -294,7 +290,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_current_page_with_params_that_match
@request = request_for_url("/?order=desc&page=1")
- assert current_page?(hash_for(:order => "desc", :page => "1"))
+ assert current_page?(hash_for([:order, "desc", :page, "1"]))
assert current_page?("http://www.example.com/?order=desc&page=1")
end
@@ -316,20 +312,20 @@ class UrlHelperTest < ActiveSupport::TestCase
@request = request_for_url("/?order=desc&page=1")
assert_equal "Showing",
- link_to_unless_current("Showing", hash_for(:order=>'desc', :page=>'1'))
+ link_to_unless_current("Showing", hash_for([:order, 'desc', :page, '1']))
assert_equal "Showing",
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=1")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=asc">Showing</a>},
- link_to_unless_current("Showing", hash_for(:order => :asc))
+ link_to_unless_current("Showing", hash_for([:order, :asc]))
assert_equal %{<a href="http://www.example.com/?order=asc">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=asc")
@request = request_for_url("/?order=desc")
assert_equal %{<a href="/?order=desc&amp;page=2\">Showing</a>},
- link_to_unless_current("Showing", hash_for(:order => "desc", :page => 2))
+ link_to_unless_current("Showing", hash_for([:order, "desc", :page, 2]))
assert_equal %{<a href="http://www.example.com/?order=desc&amp;page=2">Showing</a>},
link_to_unless_current("Showing", "http://www.example.com/?order=desc&page=2")
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index a5e7f300d9..8374853231 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis]
+
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
diff --git a/activemodel/README b/activemodel/README.rdoc
index 6f162ef408..89cacbcab4 100644
--- a/activemodel/README
+++ b/activemodel/README.rdoc
@@ -1,21 +1,21 @@
-= Active Model - defined interfaces for Rails
-
-Prior to Rails 3.0, if a plugin or gem developer wanted to be able to have
-an object interact with Action Pack helpers, it was required to either
-copy chunks of code from Rails, or monkey patch entire helpers to make them
-handle objects that did not look like Active Record. This generated code
-duplication and fragile applications that broke on upgrades.
-
-Active Model is a solution for this problem.
-
-Active Model provides a known set of interfaces that your objects can implement
-to then present a common interface to the Action Pack helpers. You can include
-functionality from the following modules:
-
-* Adding attribute magic to your objects
-
- Add prefixes and suffixes to defined attribute methods...
-
+= Active Model -- model interfaces for Rails
+
+Active Model provides a known set of interfaces for usage in model classes.
+They allow for Action Pack helpers to interact with non-ActiveRecord models,
+for example. Active Model also helps building custom ORMs for use outside of
+the Rails framework.
+
+Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
+interact with Action Pack helpers, it was required to either copy chunks of
+code from Rails, or monkey patch entire helpers to make them handle objects
+that did not exacly conform to the Active Record interface. This would result
+in code duplication and fragile applications that broke on upgrades.
+
+Active Model solves this. You can include functionality from the following
+modules:
+
+* Add attribute magic to objects
+
class Person
include ActiveModel::AttributeMethods
@@ -23,17 +23,18 @@ functionality from the following modules:
define_attribute_methods [:name, :age]
attr_accessor :name, :age
-
+
def clear_attribute(attr)
send("#{attr}=", nil)
end
end
- ...gives you clear_name, clear_age.
+ person.clear_name
+ person.clear_age
{Learn more}[link:classes/ActiveModel/AttributeMethods.html]
-* Adding callbacks to your objects
+* Callbacks for certain operations
class Person
extend ActiveModel::Callbacks
@@ -45,26 +46,16 @@ functionality from the following modules:
end
end
end
-
- ...gives you before_create, around_create and after_create class methods that
- wrap your create method.
-
+
+ This generates +before_create+, +around_create+ and +after_create+
+ class methods that wrap your create method.
+
{Learn more}[link:classes/ActiveModel/CallBacks.html]
-* For classes that already look like an Active Record object
+* Tracking value changes
- class Person
- include ActiveModel::Conversion
- end
-
- ...returns the class itself when sent :to_model
-
- {Learn more}[link:classes/ActiveModel/Conversion.html]
+ The ActiveModel::Dirty module allows for tracking attribute changes:
-* Tracking changes in your object
-
- Provides all the value tracking features implemented by ActiveRecord...
-
person = Person.new
person.name # => nil
person.changed? # => false
@@ -75,14 +66,14 @@ functionality from the following modules:
person.name = 'robert'
person.save
person.previous_changes # => {'name' => ['bob, 'robert']}
-
+
{Learn more}[link:classes/ActiveModel/Dirty.html]
-* Adding +errors+ support to your object
+* Adding +errors+ interface to objects
- Provides the error messages to allow your object to interact with Action Pack
- helpers seamlessly...
-
+ Exposing error messages allows objects to interact with Action Pack
+ helpers seamlessly.
+
class Person
def initialize
@@ -102,51 +93,38 @@ functionality from the following modules:
end
- ... gives you...
-
person.errors.full_messages
# => ["Name Can not be nil"]
+
person.errors.full_messages
# => ["Name Can not be nil"]
{Learn more}[link:classes/ActiveModel/Errors.html]
-* Testing the compliance of your object
+* Model name introspection
- Use ActiveModel::Lint to test the compliance of your object to the
- basic ActiveModel API...
-
- {Learn more}[link:classes/ActiveModel/Lint/Tests.html]
-
-* Providing a human face to your object
-
- ActiveModel::Naming provides your model with the model_name convention
- and a human_name attribute...
-
class NamedPerson
extend ActiveModel::Naming
end
- ...gives you...
-
NamedPerson.model_name #=> "NamedPerson"
NamedPerson.model_name.human #=> "Named person"
{Learn more}[link:classes/ActiveModel/Naming.html]
-* Adding observer support to your objects
+* Observer support
- ActiveModel::Observers allows your object to implement the Observer
- pattern in a Rails App and take advantage of all the standard observer
- functions.
+ ActiveModel::Observers allows your object to implement the Observer
+ pattern in a Rails App and take advantage of all the standard observer
+ functions.
{Learn more}[link:classes/ActiveModel/Observer.html]
-* Making your object serializable
+* Making objects serializable
- ActiveModel::Serialization provides a standard interface for your object
- to provide to_json or to_xml serialization...
-
+ ActiveModel::Serialization provides a standard interface for your object
+ to provide +to_json+ or +to_xml+ serialization.
+
s = SerialPerson.new
s.serializable_hash # => {"name"=>nil}
s.to_json # => "{\"name\":null}"
@@ -154,36 +132,36 @@ functionality from the following modules:
{Learn more}[link:classes/ActiveModel/Serialization.html]
-* Integrating with Rail's internationalization (i18n) handling through
- ActiveModel::Translations...
+* Internationalization (i18n) support
class Person
extend ActiveModel::Translation
end
+
+ Person.human_attribute_name('my_attribute')
+ #=> "My attribute"
{Learn more}[link:classes/ActiveModel/Translation.html]
-* Providing a full Validation stack for your objects...
+* Validation support
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
-
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
end
end
-
person = Person.new
person.first_name = 'zoolander'
- person.valid? #=> false
+ person.valid? #=> false
{Learn more}[link:classes/ActiveModel/Validations.html]
-* Make custom validators
+* Custom validators
class Person
include ActiveModel::Validations
@@ -196,7 +174,7 @@ functionality from the following modules:
record.errors[:name] = "must exist" if record.name.blank?
end
end
-
+
p = ValidatorPerson.new
p.valid? #=> false
p.errors.full_messages #=> ["Name must exist"]
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 1dba664539..3fffc0d021 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,6 +1,6 @@
dir = File.dirname(__FILE__)
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake/testtask'
@@ -23,16 +23,16 @@ namespace :test do
end
-require 'rake/rdoctask'
+require 'rdoc/task'
# Generate the RDoc documentation
-Rake::RDocTask.new do |rdoc|
+RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = "doc"
rdoc.title = "Active Model"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include("README", "CHANGELOG")
+ rdoc.options << '--main' << 'README.rdoc'
+ rdoc.rdoc_files.include("README.rdoc", "CHANGELOG")
rdoc.rdoc_files.include("lib/**/*.rb")
end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 6bd6fe49ff..c483ecbc3c 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activemodel'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 817640b178..a43436e008 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -283,7 +283,7 @@ module ActiveModel
@attribute_methods_generated = true
end
- # Removes all the preiously dynamically defined methods from the class
+ # Removes all the previously dynamically defined methods from the class
def undefine_attribute_methods
generated_attribute_methods.module_eval do
instance_methods.each { |m| undef_method(m) }
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e7aad17021..8c10c54b54 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -19,7 +19,7 @@ module ActiveModel
#
# define_model_callbacks :create, :update
#
- # This will provide all three standard callbacks (before, around and after) around
+ # This will provide all three standard callbacks (before, around and after) for
# both the :create and :update methods. To implement, you need to wrap the methods
# you want callbacks on in a block so that the callbacks get a chance to fire:
#
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 2a1650faa9..d2bd160dc7 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -17,9 +17,9 @@ module ActiveModel
# end
#
# cm = ContactMessage.new
- # cm.to_model == self #=> true
- # cm.to_key #=> nil
- # cm.to_param #=> nil
+ # cm.to_model == self # => true
+ # cm.to_key # => nil
+ # cm.to_param # => nil
#
module Conversion
# If your object is already designed to implement all of the Active Model
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 4c80863e3a..2516377afd 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -37,12 +37,13 @@ module ActiveModel
# end
#
# def name=(val)
- # name_will_change!
+ # name_will_change! unless val == @name
# @name = val
# end
#
# def save
# @previously_changed = changes
+ # @changed_attributes.clear
# end
#
# end
@@ -77,13 +78,10 @@ module ActiveModel
# 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'
+ # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
+ # to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
+ # in-place attributes.
#
- # Before modifying an attribute in-place:
# person.name_will_change!
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 482b3dac47..272ddb1554 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -37,11 +37,11 @@ module ActiveModel
# send(attr)
# end
#
- # def ErrorsPerson.human_attribute_name(attr, options = {})
+ # def Person.human_attribute_name(attr, options = {})
# attr
# end
#
- # def ErrorsPerson.lookup_ancestors
+ # def Person.lookup_ancestors
# [self]
# end
#
@@ -83,20 +83,16 @@ module ActiveModel
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
- # p.errors[:name] #=> ["can not be nil"]
- # p.errors['name'] #=> ["can not be nil"]
+ # p.errors[:name] # => ["can not be nil"]
+ # p.errors['name'] # => ["can not be nil"]
def [](attribute)
- if errors = get(attribute.to_sym)
- errors
- else
- set(attribute.to_sym, [])
- end
+ get(attribute.to_sym) || set(attribute.to_sym, [])
end
# Adds to the supplied attribute the supplied error message.
#
# p.errors[:name] = "must be set"
- # p.errors[:name] #=> ['must be set']
+ # p.errors[:name] # => ['must be set']
def []=(attribute, error)
self[attribute.to_sym] << error
end
@@ -124,9 +120,9 @@ module ActiveModel
# Returns the number of error messages.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.size #=> 1
+ # p.errors.size # => 1
# p.errors.add(:name, "must be specified")
- # p.errors.size #=> 2
+ # p.errors.size # => 2
def size
values.flatten.size
end
@@ -135,16 +131,16 @@ module ActiveModel
#
# p.errors.add(:name, "can't be blank")
# p.errors.add(:name, "must be specified")
- # p.errors.to_a #=> ["name can't be blank", "name must be specified"]
+ # p.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
end
# Returns the number of error messages.
# p.errors.add(:name, "can't be blank")
- # p.errors.count #=> 1
+ # p.errors.count # => 1
# p.errors.add(:name, "must be specified")
- # p.errors.count #=> 2
+ # p.errors.count # => 2
def count
to_a.size
end
@@ -158,8 +154,8 @@ module ActiveModel
#
# p.errors.add(:name, "can't be blank")
# p.errors.add(:name, "must be specified")
- # p.errors.to_xml #=> Produces:
- #
+ # p.errors.to_xml
+ # # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# # <errors>
# # <error>name can't be blank</error>
@@ -169,9 +165,9 @@ module ActiveModel
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
end
- # Returns an array as JSON representation for this object.
+ # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
def as_json(options=nil)
- to_a
+ self
end
# Adds +message+ to the error messages on +attribute+, which will be returned on a call to
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 7c48472799..9fcb94d48a 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -1,3 +1,4 @@
+require 'set'
require 'active_model/mass_assignment_security/sanitizer'
module ActiveModel
@@ -36,4 +37,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index ca1e9f0ee8..b74d669f0a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -17,7 +17,10 @@ module ActiveModel
end
# Transform the model name into a more humane format, using I18n. By default,
- # it will underscore then humanize the class name (BlogPost.model_name.human #=> "Blog post").
+ # it will underscore then humanize the class name
+ #
+ # BlogPost.model_name.human # => "Blog post"
+ #
# Specify +options+ with additional translating options.
def human(options={})
return @human unless @klass.respond_to?(:lookup_ancestors) &&
@@ -45,8 +48,8 @@ module ActiveModel
# extend ActiveModel::Naming
# end
#
- # BookCover.model_name #=> "BookCover"
- # BookCover.model_name.human #=> "Book cover"
+ # BookCover.model_name # => "BookCover"
+ # BookCover.model_name.human # => "Book cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
@@ -57,6 +60,35 @@ module ActiveModel
def model_name
@_model_name ||= ActiveModel::Name.new(self)
end
+
+ # Returns the plural class name of a record or class. Examples:
+ #
+ # ActiveModel::Naming.plural(post) # => "posts"
+ # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
+ def self.plural(record_or_class)
+ model_name_from_record_or_class(record_or_class).plural
+ end
+
+ # Returns the singular class name of a record or class. Examples:
+ #
+ # ActiveModel::Naming.singular(post) # => "post"
+ # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
+ def self.singular(record_or_class)
+ model_name_from_record_or_class(record_or_class).singular
+ end
+
+ # Identifies whether the class name of a record or class is uncountable. Examples:
+ #
+ # ActiveModel::Naming.uncountable?(Sheep) # => true
+ # ActiveModel::Naming.uncountable?(Post) => false
+ def self.uncountable?(record_or_class)
+ plural(record_or_class) == singular(record_or_class)
+ end
+
+ private
+ def self.model_name_from_record_or_class(record_or_class)
+ (record_or_class.is_a?(Class) ? record_or_class : record_or_class.class).model_name
+ end
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 5670ec74cb..e675937f4d 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -61,6 +61,8 @@ module ActiveModel
# person.serializable_hash # => {"name"=>"Bob"}
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
+ #
+ # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
module Serialization
def serializable_hash(options = nil)
options ||= {}
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 918cd0ab76..e1dbc522de 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -19,8 +19,8 @@ module ActiveModel
# passed through +options+.
#
# The option <tt>ActiveModel::Base.include_root_in_json</tt> controls the
- # top-level behavior of to_json. It is true by default. When it is <tt>true</tt>,
- # to_json will emit a single root node named after the object's type. For example:
+ # top-level behavior of +to_json+. If true (the default) +to_json+ will
+ # emit a single root node named after the object's type. For example:
#
# konata = User.find(1)
# konata.to_json
@@ -32,11 +32,11 @@ module ActiveModel
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
# "created_at": "2006/08/01", "awesome": true}
#
- # The remainder of the examples in this section assume include_root_in_json is set to
- # <tt>false</tt>.
+ # The remainder of the examples in this section assume +include_root_in_json+
+ # is false.
#
- # Without any +options+, the returned JSON string will include all
- # the model's attributes. For example:
+ # Without any +options+, the returned JSON string will include all the model's
+ # attributes. For example:
#
# konata = User.find(1)
# konata.to_json
@@ -52,14 +52,14 @@ module ActiveModel
# konata.to_json(:except => [ :id, :created_at, :age ])
# # => {"name": "Konata Izumi", "awesome": true}
#
- # To include any methods on the model, use <tt>:methods</tt>.
+ # To include the result of some method calls on the model use <tt>:methods</tt>:
#
# konata.to_json(:methods => :permalink)
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
# "created_at": "2006/08/01", "awesome": true,
# "permalink": "1-konata-izumi"}
#
- # To include associations, use <tt>:include</tt>.
+ # To include associations use <tt>:include</tt>:
#
# konata.to_json(:include => :posts)
# # => {"id": 1, "name": "Konata Izumi", "age": 16,
@@ -67,7 +67,7 @@ module ActiveModel
# "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
# {"id": 2, author_id: 1, "title": "So I was thinking"}]}
#
- # 2nd level and higher order associations work as well:
+ # Second level and higher order associations work as well:
#
# konata.to_json(:include => { :posts => {
# :include => { :comments => {
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 0554677296..0facbd6ce1 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -14,7 +14,7 @@ module ActiveModel
# end
#
# TranslatedPerson.human_attribute_name('my_attribute')
- # #=> "My attribute"
+ # # => "My attribute"
#
# This also provides the required class methods for hooking into the
# Rails internationalization API, including being able to define a
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 5779ba3b29..3407c59e7a 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -24,20 +24,16 @@ module ActiveModel
# end
#
# Which provides you with the full standard validation stack that you
- # know from ActiveRecord.
+ # know from Active Record:
#
# person = Person.new
- # person.valid?
- # #=> true
- # person.invalid?
- # #=> false
+ # person.valid? # => true
+ # person.invalid? # => false
+ #
# person.first_name = 'zoolander'
- # person.valid?
- # #=> false
- # person.invalid?
- # #=> true
- # person.errors
- # #=> #<OrderedHash {:first_name=>["starts with z."]}>
+ # person.valid? # => false
+ # person.invalid? # => true
+ # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
#
# Note that ActiveModel::Validations automatically adds an +errors+ method
# to your instances initialized with a new ActiveModel::Errors object, so
@@ -122,11 +118,13 @@ module ActiveModel
# end
#
def validate(*args, &block)
- options = args.last
- if options.is_a?(Hash) && options.key?(:on)
+ options = args.extract_options!
+ if options.key?(:on)
+ options = options.dup
options[:if] = Array.wrap(options[:if])
options[:if] << "validation_context == :#{options[:on]}"
end
+ args << options
set_callback(:validate, *args, &block)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index c8a77ad666..a7af4f2b4d 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -40,8 +40,6 @@ module ActiveModel
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- default_message = options[MESSAGES[key]]
- options[:message] ||= default_message if default_message
valid_value = if key == :maximum
value.nil? || value.size.send(validity_check, check_value)
@@ -51,8 +49,13 @@ module ActiveModel
next if valid_value
- record.errors.add(attribute, MESSAGES[key],
- options.except(*RESERVED_OPTIONS).merge!(:count => check_value))
+ errors_options = options.except(*RESERVED_OPTIONS)
+ errors_options[:count] = check_value
+
+ default_message = options[MESSAGES[key]]
+ errors_options[:message] ||= default_message if default_message
+
+ record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 0674640925..3260e6bc5a 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -40,7 +40,7 @@ module ActiveModel
# validates :email, :presence => true, :email => true
# end
#
- # Validator classes my also exist within the class being validated
+ # Validator classes may also exist within the class being validated
# allowing custom modules of validators to be included as needed e.g.
#
# class Film
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 689c617177..163124d531 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -102,8 +102,8 @@ module ActiveModel #:nodoc:
#
# == Examples
#
- # PresenceValidator.kind #=> :presence
- # UniquenessValidator.kind #=> :uniqueness
+ # PresenceValidator.kind # => :presence
+ # UniquenessValidator.kind # => :uniqueness
#
def self.kind
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
@@ -111,7 +111,7 @@ module ActiveModel #:nodoc:
# Accepts options that will be made available through the +options+ reader.
def initialize(options)
- @options = options
+ @options = options.freeze
end
# Return the kind for this validator.
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index c36fc8b1f7..f2f4b15520 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -3,7 +3,7 @@ module ActiveModel
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index e1a35be384..858ae9cb69 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,10 +3,11 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods [:name]
+ define_attribute_methods [:name, :color]
def initialize
@name = nil
+ @color = nil
end
def name
@@ -17,13 +18,92 @@ class DirtyTest < ActiveModel::TestCase
name_will_change!
@name = val
end
+
+ def color
+ @color
+ end
+
+ def color=(val)
+ color_will_change! unless val == @color
+ @color = val
+ end
+
+ def save
+ @previously_changed = changes
+ @changed_attributes.clear
+ end
+ end
+
+ setup do
+ @model = DirtyModel.new
+ end
+
+ test "setting attribute will result in change" do
+ assert !@model.changed?
+ assert !@model.name_changed?
+ @model.name = "Ringo"
+ assert @model.changed?
+ assert @model.name_changed?
+ end
+
+ test "list of changed attributes" do
+ assert_equal [], @model.changed
+ @model.name = "Paul"
+ assert_equal ['name'], @model.changed
+ end
+
+ test "changes to attribute values" do
+ assert !@model.changes['name']
+ @model.name = "John"
+ assert_equal [nil, "John"], @model.changes['name']
end
test "changes accessible through both strings and symbols" do
- model = DirtyModel.new
- model.name = "David"
- assert_not_nil model.changes[:name]
- assert_not_nil model.changes['name']
+ @model.name = "David"
+ assert_not_nil @model.changes[:name]
+ assert_not_nil @model.changes['name']
+ end
+
+ test "attribute mutation" do
+ @model.instance_variable_set("@name", "Yam")
+ assert !@model.name_changed?
+ @model.name.replace("Hadad")
+ assert !@model.name_changed?
+ @model.name_will_change!
+ @model.name.replace("Baal")
+ assert @model.name_changed?
+ end
+
+ test "resetting attribute" do
+ @model.name = "Bob"
+ @model.reset_name!
+ assert_nil @model.name
+ #assert !@model.name_changed #Doesn't work yet
+ end
+
+ test "setting color to same value should not result in change being recorded" do
+ @model.color = "red"
+ assert @model.color_changed?
+ @model.save
+ assert !@model.color_changed?
+ assert !@model.changed?
+ @model.color = "red"
+ assert !@model.color_changed?
+ assert !@model.changed?
+ end
+
+ test "saving should reset model's changed status" do
+ @model.name = "Alf"
+ assert @model.changed?
+ @model.save
+ assert !@model.changed?
+ assert !@model.name_changed?
+ end
+
+ test "saving should preserve previous changes" do
+ @model.name = "Jericho Cane"
+ @model.save
+ assert_equal [nil, "Jericho Cane"], @model.previous_changes['name']
end
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
new file mode 100644
index 0000000000..79b45bb298
--- /dev/null
+++ b/activemodel/test/cases/errors_test.rb
@@ -0,0 +1,65 @@
+require "cases/helper"
+
+class ErrorsTest < ActiveModel::TestCase
+ class Person
+ extend ActiveModel::Naming
+ def initialize
+ @errors = ActiveModel::Errors.new(self)
+ end
+
+ attr_accessor :name
+ attr_reader :errors
+
+ def validate!
+ errors.add(:name, "can not be nil") if name == nil
+ end
+
+ def read_attribute_for_validation(attr)
+ send(attr)
+ end
+
+ def self.human_attribute_name(attr, options = {})
+ attr
+ end
+
+ def self.lookup_ancestors
+ [self]
+ end
+
+ end
+
+ test "method validate! should work" do
+ person = Person.new
+ person.validate!
+ assert_equal ["name can not be nil"], person.errors.full_messages
+ assert_equal ["can not be nil"], person.errors[:name]
+
+ end
+
+ test 'should be able to assign error' do
+ person = Person.new
+ person.errors[:name] = 'should not be nil'
+ assert_equal ["should not be nil"], person.errors[:name]
+ end
+
+ test 'should be able to add an error on an attribute' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal ["can not be blank"], person.errors[:name]
+ end
+
+ test 'should respond to size' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert_equal 1, person.errors.size
+ end
+
+ test 'to_a should return an array' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+
+ end
+
+end
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 367207aab3..015153ec7c 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -31,7 +31,7 @@ class SanitizerTest < ActiveModel::TestCase
log = StringIO.new
@sanitizer.logger = Logger.new(log)
@sanitizer.sanitize(original_attributes)
- assert (log.string =~ /admin/), "Should log removed attributes: #{log.string}"
+ assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}")
end
end
diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb
index 0f7a38b0bc..c25b0fdf00 100644
--- a/activemodel/test/cases/mass_assignment_security_test.rb
+++ b/activemodel/test/cases/mass_assignment_security_test.rb
@@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes,
'Running attr_protected twice in one class should merge the protections'
- assert (TightPerson.protected_attributes - TightPerson.attributes_protected_by_default).blank?
+ assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
- assert (TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default).blank?
+ assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default
assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
end
@@ -49,4 +49,4 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase
assert_equal sanitized, { }
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index dc39b84ed8..5a8bff378a 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -1,4 +1,6 @@
require 'cases/helper'
+require 'models/contact'
+require 'models/sheep'
require 'models/track_back'
class NamingTest < ActiveModel::TestCase
@@ -26,3 +28,40 @@ class NamingTest < ActiveModel::TestCase
assert_equal 'post/track_backs/track_back', @model_name.partial_path
end
end
+
+class NamingHelpersTest < Test::Unit::TestCase
+ def setup
+ @klass = Contact
+ @record = @klass.new
+ @singular = 'contact'
+ @plural = 'contacts'
+ @uncountable = Sheep
+ end
+
+ def test_singular
+ assert_equal @singular, singular(@record)
+ end
+
+ def test_singular_for_class
+ assert_equal @singular, singular(@klass)
+ end
+
+ def test_plural
+ assert_equal @plural, plural(@record)
+ end
+
+ def test_plural_for_class
+ assert_equal @plural, plural(@klass)
+ end
+
+ def test_uncountable
+ assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable"
+ assert !uncountable?(@klass), "Expected 'contact' to be countable"
+ end
+
+ private
+ def method_missing(method, *args)
+ ActiveModel::Naming.send(method, *args)
+ end
+end
+
diff --git a/activemodel/test/cases/serializeration/json_serialization_test.rb b/activemodel/test/cases/serializeration/json_serialization_test.rb
index 04b50e5bb8..1ac991a8f1 100644
--- a/activemodel/test/cases/serializeration/json_serialization_test.rb
+++ b/activemodel/test/cases/serializeration/json_serialization_test.rb
@@ -89,7 +89,7 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
- test "methds are called on object" do
+ test "methods are called on object" do
# Define methods on fixture.
def @contact.label; "Has cheezburger"; end
def @contact.favorite_quote; "Constraints are liberating"; end
@@ -102,4 +102,18 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"label":"Has cheezburger"}, methods_json
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
+
+ test "should return OrderedHash for errors" do
+ car = Automobile.new
+
+ # run the validation
+ car.valid?
+
+ hash = ActiveSupport::OrderedHash.new
+ hash[:make] = "can't be blank"
+ hash[:model] = "is too short (minimum is 2 characters)"
+ assert_equal hash.to_json, car.errors.to_json
+ end
+
+
end
diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb
index 012c5a2f37..1e6180a938 100644
--- a/activemodel/test/cases/validations/length_validation_test.rb
+++ b/activemodel/test/cases/validations/length_validation_test.rb
@@ -229,6 +229,20 @@ class LengthValidationTest < ActiveModel::TestCase
assert_equal ["hoo 5"], t.errors["title"]
end
+ def test_validates_length_of_custom_errors_for_both_too_short_and_too_long
+ Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long'
+
+ t = Topic.new(:title => 'a')
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ['too short'], t.errors['title']
+
+ t = Topic.new(:title => 'aaaaaa')
+ assert t.invalid?
+ assert t.errors[:title].any?
+ assert_equal ['too long'], t.errors['title']
+ end
+
def test_validates_length_of_custom_errors_for_is_with_message
Topic.validates_length_of( :title, :is=>5, :message=>"boo %{count}" )
t = Topic.new("title" => "uhohuhoh", "content" => "whatever")
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index e94d8ce88c..8d6bdeb6a5 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -170,9 +170,11 @@ class ValidationsTest < ActiveModel::TestCase
assert_match %r{<errors>}, xml
assert_match %r{<error>Title can't be blank</error>}, xml
assert_match %r{<error>Content can't be blank</error>}, xml
-
- json = t.errors.to_json
- assert_equal t.errors.to_a.to_json, json
+
+ hash = ActiveSupport::OrderedHash.new
+ hash[:title] = "can't be blank"
+ hash[:content] = "can't be blank"
+ assert_equal t.errors.to_json, hash.to_json
end
def test_validation_order
diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb
index 605e435f39..f4f3078473 100644
--- a/activemodel/test/models/contact.rb
+++ b/activemodel/test/models/contact.rb
@@ -1,4 +1,5 @@
class Contact
+ extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :name, :age, :created_at, :awesome, :preferences
diff --git a/activemodel/test/models/sheep.rb b/activemodel/test/models/sheep.rb
new file mode 100644
index 0000000000..175dbe6477
--- /dev/null
+++ b/activemodel/test/models/sheep.rb
@@ -0,0 +1,4 @@
+class Sheep
+ extend ActiveModel::Naming
+end
+ \ No newline at end of file
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a1a82fdff5..20b2286fc0 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,4 +1,6 @@
-*Rails 3.0.0 [RC1] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Changed update_attribute to not run callbacks and update the record directly in the database [Neeraj Singh]
* Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope [José Valim]
diff --git a/activerecord/README b/activerecord/README
deleted file mode 100644
index d68eb28a64..0000000000
--- a/activerecord/README
+++ /dev/null
@@ -1,351 +0,0 @@
-= Active Record -- Object-relation mapping put on rails
-
-Active Record connects business objects and database tables to create a persistable
-domain model where logic and data are presented in one wrapping. It's an implementation
-of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html]
-by the same name as described by Martin Fowler:
-
- "An object that wraps a row in a database table or view, encapsulates
- the database access, and adds domain logic on that data."
-
-Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
-lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
-the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
-gap of functionality between the data mapper and active record approach.
-
-A short rundown of the major features:
-
-* Automated mapping between classes and tables, attributes and columns.
-
- class Product < ActiveRecord::Base; end
-
- ...is automatically mapped to the table named "products", such as:
-
- CREATE TABLE products (
- id int(11) NOT NULL auto_increment,
- name varchar(255),
- PRIMARY KEY (id)
- );
-
- ...which again gives Product#name and Product#name=(new_name)
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Associations between objects controlled by simple meta-programming macros.
-
- class Firm < ActiveRecord::Base
- has_many :clients
- has_one :account
- belongs_to :conglomorate
- end
-
- {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
-
-
-* Aggregations of value objects controlled by simple meta-programming macros.
-
- class Account < ActiveRecord::Base
- composed_of :balance, :class_name => "Money",
- :mapping => %w(balance amount)
- composed_of :address,
- :mapping => [%w(address_street street), %w(address_city city)]
- end
-
- {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
-
-
-* Validation rules that can differ for new or existing objects.
-
- class Account < ActiveRecord::Base
- validates_presence_of :subdomain, :name, :email_address, :password
- validates_uniqueness_of :subdomain
- validates_acceptance_of :terms_of_service, :on => :create
- validates_confirmation_of :password, :email_address, :on => :create
- end
-
- {Learn more}[link:classes/ActiveRecord/Validations.html]
-
-* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
-
- class Person < ActiveRecord::Base
- def before_destroy # is called just before Person#destroy
- CreditCard.find(credit_card_id).destroy
- end
- end
-
- class Account < ActiveRecord::Base
- after_find :eager_load, 'self.class.announce(#{id})'
- end
-
- {Learn more}[link:classes/ActiveRecord/Callbacks.html]
-
-
-* Observers for the entire lifecycle
-
- class CommentObserver < ActiveRecord::Observer
- def after_create(comment) # is called just after Comment#save
- Notifications.deliver_new_comment("david@loudthinking.com", comment)
- end
- end
-
- {Learn more}[link:classes/ActiveRecord/Observer.html]
-
-
-* Inheritance hierarchies
-
- class Company < ActiveRecord::Base; end
- class Firm < Company; end
- class Client < Company; end
- class PriorityClient < Client; end
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Transactions
-
- # Database transaction
- Account.transaction do
- david.withdrawal(100)
- mary.deposit(100)
- end
-
- {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
-
-
-* Reflections on columns, associations, and aggregations
-
- reflection = Firm.reflect_on_association(:clients)
- reflection.klass # => Client (class)
- Firm.columns # Returns an array of column descriptors for the firms table
-
- {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
-
-
-* Direct manipulation (instead of service invocation)
-
- So instead of (Hibernate[http://www.hibernate.org/] example):
-
- long pkId = 1234;
- DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
- // something interesting involving a cat...
- sess.save(cat);
- sess.flush(); // force the SQL INSERT
-
- Active Record lets you:
-
- pkId = 1234
- cat = Cat.find(pkId)
- # something even more interesting involving the same cat...
- cat.save
-
- {Learn more}[link:classes/ActiveRecord/Base.html]
-
-
-* Database abstraction through simple adapters (~100 lines) with a shared connector
-
- ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
-
- ActiveRecord::Base.establish_connection(
- :adapter => "mysql",
- :host => "localhost",
- :username => "me",
- :password => "secret",
- :database => "activerecord"
- )
-
- {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for
- MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html].
-
-
-* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
-
- ActiveRecord::Base.logger = Logger.new(STDOUT)
- ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
-
-
-* Database agnostic schema management with Migrations
-
- class AddSystemSettings < ActiveRecord::Migration
- def self.up
- create_table :system_settings do |t|
- t.string :name
- t.string :label
- t.text :value
- t.string :type
- t.integer :position
- end
-
- SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
- end
-
- def self.down
- drop_table :system_settings
- end
- end
-
- {Learn more}[link:classes/ActiveRecord/Migration.html]
-
-== Simple example (1/2): Defining tables and classes (using MySQL)
-
-Data definitions are specified only in the database. Active Record queries the database for
-the column names (that then serves to determine which attributes are valid) on regular
-object instantiation through the new constructor and relies on the column names in the rows
-with the finders.
-
- # CREATE TABLE companies (
- # id int(11) unsigned NOT NULL auto_increment,
- # client_of int(11),
- # name varchar(255),
- # type varchar(100),
- # PRIMARY KEY (id)
- # )
-
-Active Record automatically links the "Company" object to the "companies" table
-
- class Company < ActiveRecord::Base
- has_many :people, :class_name => "Person"
- end
-
- class Firm < Company
- has_many :clients
-
- def people_with_all_clients
- clients.inject([]) { |people, client| people + client.people }
- end
- end
-
-The foreign_key is only necessary because we didn't use "firm_id" in the data definition
-
- class Client < Company
- belongs_to :firm, :foreign_key => "client_of"
- end
-
- # CREATE TABLE people (
- # id int(11) unsigned NOT NULL auto_increment,
- # name text,
- # company_id text,
- # PRIMARY KEY (id)
- # )
-
-Active Record will also automatically link the "Person" object to the "people" table
-
- class Person < ActiveRecord::Base
- belongs_to :company
- end
-
-== Simple example (2/2): Using the domain
-
-Picking a database connection for all the Active Records
-
- ActiveRecord::Base.establish_connection(
- :adapter => "mysql",
- :host => "localhost",
- :username => "me",
- :password => "secret",
- :database => "activerecord"
- )
-
-Create some fixtures
-
- firm = Firm.new("name" => "Next Angle")
- # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
- firm.save
-
- client = Client.new("name" => "37signals", "client_of" => firm.id)
- # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
- client.save
-
-Lots of different finders
-
- # SQL: SELECT * FROM companies WHERE id = 1
- next_angle = Company.find(1)
-
- # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
- next_angle = Firm.find(1)
-
- # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
- next_angle = Company.find(:first, :conditions => "name = 'Next Angle'")
-
- next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
-
-The supertype, Company, will return subtype instances
-
- Firm === next_angle
-
-All the dynamic methods added by the has_many macro
-
- next_angle.clients.empty? # true
- next_angle.clients.size # total number of clients
- all_clients = next_angle.clients
-
-Constrained finds makes access security easier when ID comes from a web-app
-
- # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
- thirty_seven_signals = next_angle.clients.find(2)
-
-Bi-directional associations thanks to the "belongs_to" macro
-
- thirty_seven_signals.firm.nil? # true
-
-
-== Philosophy
-
-Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
-object-relational mapping. The prime directive for this mapping has been to minimize
-the amount of code needed to build a real-world domain model. This is made possible
-by relying on a number of conventions that make it easy for Active Record to infer
-complex relations and structures from a minimal amount of explicit direction.
-
-Convention over Configuration:
-* No XML-files!
-* Lots of reflection and run-time extension
-* Magic is not inherently a bad word
-
-Admit the Database:
-* Lets you drop down to SQL for odd cases and performance
-* Doesn't attempt to duplicate or replace data definitions
-
-
-== Download
-
-The latest version of Active Record can be found at
-
-* http://rubyforge.org/project/showfiles.php?group_id=182
-
-Documentation can be found at
-
-* http://ar.rubyonrails.com
-
-
-== Installation
-
-The prefered method of installing Active Record is through its GEM file. You'll need to have
-RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
-then use:
-
- % [sudo] gem install activerecord-1.10.0.gem
-
-You can also install Active Record the old-fashioned way with the following command:
-
- % [sudo] ruby install.rb
-
-from its distribution directory.
-
-
-== License
-
-Active Record is released under the MIT license.
-
-
-== Support
-
-The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
-RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
-
- Feel free to submit commits or feature requests. If you send a patch,
- remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
-
-For other information, feel free to ask on the rubyonrails-talk
-(http://groups.google.com/group/rubyonrails-talk) mailing list.
diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc
new file mode 100644
index 0000000000..8dbd6c82b5
--- /dev/null
+++ b/activerecord/README.rdoc
@@ -0,0 +1,222 @@
+= Active Record -- Object-relational mapping put on rails
+
+Active Record connects classes to relational database tables to establish an
+almost zero-configuration persistence layer for applications. The library
+provides a base class that, when subclassed, sets up a mapping between the new
+class and an existing table in the database. In context of an application,
+these classes are commonly referred to as *models*. Models can also be
+connected to other models; this is done by defining *associations*.
+
+Active Record relies heavily on naming in that it uses class and association
+names to establish mappings between respective database tables and foreign key
+columns. Although these mappings can be defined explicitly, it's recommended
+to follow naming conventions, especially when getting started with the
+library.
+
+A short rundown of some of the major features:
+
+* Automated mapping between classes and tables, attributes and columns.
+
+ class Product < ActiveRecord::Base
+ end
+
+ The Product class is automatically mapped to the table named "products",
+ which might look like this:
+
+ CREATE TABLE products (
+ id int(11) NOT NULL auto_increment,
+ name varchar(255),
+ PRIMARY KEY (id)
+ );
+
+ This would also define the following accessors: `Product#name` and
+ `Product#name=(new_name)`
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Associations between objects defined by simple class methods.
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_one :account
+ belongs_to :conglomerate
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
+
+
+* Aggregations of value objects.
+
+ class Account < ActiveRecord::Base
+ composed_of :balance, :class_name => "Money",
+ :mapping => %w(balance amount)
+ composed_of :address,
+ :mapping => [%w(address_street street), %w(address_city city)]
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
+
+
+* Validation rules that can differ for new or existing objects.
+
+ class Account < ActiveRecord::Base
+ validates_presence_of :subdomain, :name, :email_address, :password
+ validates_uniqueness_of :subdomain
+ validates_acceptance_of :terms_of_service, :on => :create
+ validates_confirmation_of :password, :email_address, :on => :create
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Validations.html]
+
+
+* Callbacks available for the entire lifecycle (instantiation, saving, destroying, validating, etc.)
+
+ class Person < ActiveRecord::Base
+ before_destroy :invalidate_payment_plan
+ # the `invalidate_payment_plan` method gets called just before Person#destroy
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Callbacks.html]
+
+
+* Observers that react to changes in a model
+
+ class CommentObserver < ActiveRecord::Observer
+ def after_create(comment) # is called just after Comment#save
+ Notifications.deliver_new_comment("david@loudthinking.com", comment)
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Observer.html]
+
+
+* Inheritance hierarchies
+
+ class Company < ActiveRecord::Base; end
+ class Firm < Company; end
+ class Client < Company; end
+ class PriorityClient < Client; end
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Transactions
+
+ # Database transaction
+ Account.transaction do
+ david.withdrawal(100)
+ mary.deposit(100)
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
+
+
+* Reflections on columns, associations, and aggregations
+
+ reflection = Firm.reflect_on_association(:clients)
+ reflection.klass # => Client (class)
+ Firm.columns # Returns an array of column descriptors for the firms table
+
+ {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
+
+
+* Database abstraction through simple adapters
+
+ # connect to SQLite3
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3")
+
+ # connect to MySQL with authentication
+ ActiveRecord::Base.establish_connection(
+ :adapter => "mysql",
+ :host => "localhost",
+ :username => "me",
+ :password => "secret",
+ :database => "activerecord"
+ )
+
+ {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
+ MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html],
+ PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
+ SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
+
+
+* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
+ ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
+
+
+* Database agnostic schema management with Migrations
+
+ class AddSystemSettings < ActiveRecord::Migration
+ def self.up
+ create_table :system_settings do |t|
+ t.string :name
+ t.string :label
+ t.text :value
+ t.string :type
+ t.integer :position
+ end
+
+ SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ end
+
+ def self.down
+ drop_table :system_settings
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Migration.html]
+
+
+== Philosophy
+
+Active Record is an implementation of the object-relational mapping (ORM)
+pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
+name described by Martin Fowler:
+
+ "An object that wraps a row in a database table or view,
+ encapsulates the database access, and adds domain logic on that data."
+
+Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
+object-relational mapping. The prime directive for this mapping has been to minimize
+the amount of code needed to build a real-world domain model. This is made possible
+by relying on a number of conventions that make it easy for Active Record to infer
+complex relations and structures from a minimal amount of explicit direction.
+
+Convention over Configuration:
+* No XML-files!
+* Lots of reflection and run-time extension
+* Magic is not inherently a bad word
+
+Admit the Database:
+* Lets you drop down to SQL for odd cases and performance
+* Doesn't attempt to duplicate or replace data definitions
+
+
+== Download and installation
+
+The latest version of Active Record can be installed with Rubygems:
+
+ % [sudo] gem install activerecord
+
+Source code can be downloaded as part of the Rails project on GitHub
+
+* http://github.com/rails/rails/tree/master/activerecord/
+
+
+== License
+
+Active Record is released under the MIT license.
+
+
+== Support
+
+API documentation is at
+
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 392b717e0a..c1e90cc099 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
@@ -24,14 +24,14 @@ def run_without_aborting(*tasks)
abort "Errors running #{errors.join(', ')}" if errors.any?
end
-desc 'Run mysql, sqlite, and postgresql tests by default'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests by default'
task :default => :test
-desc 'Run mysql, sqlite, and postgresql tests'
+desc 'Run mysql, mysql2, sqlite, and postgresql tests'
task :test do
tasks = defined?(JRUBY_VERSION) ?
%w(test_jdbcmysql test_jdbcsqlite3 test_jdbcpostgresql) :
- %w(test_mysql test_sqlite3 test_postgresql)
+ %w(test_mysql test_mysql2 test_sqlite3 test_postgresql)
run_without_aborting(*tasks)
end
@@ -39,15 +39,15 @@ namespace :test do
task :isolated do
tasks = defined?(JRUBY_VERSION) ?
%w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) :
- %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql)
+ %w(isolated_test_mysql isolated_test_mysql2 isolated_test_sqlite3 isolated_test_postgresql)
run_without_aborting(*tasks)
end
end
-%w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
t.libs << "test" << connection_path
t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject {
|x| x =~ /\/adapters\//
@@ -59,7 +59,7 @@ end
task "isolated_test_#{adapter}" do
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
- adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
puts [adapter, adapter_short, connection_path].inspect
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
(Dir["test/cases/**/*_test.rb"].reject {
@@ -166,13 +166,13 @@ task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Record -- Object-relation mapping put on rails"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
rdoc.rdoc_files.include('dev-utils/*.rb')
@@ -224,9 +224,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/ar", "doc").upload
-end
diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec
index 5aea992801..67d521d56b 100644
--- a/activerecord/activerecord.gemspec
+++ b/activerecord/activerecord.gemspec
@@ -14,15 +14,15 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activerecord'
- s.files = Dir['CHANGELOG', 'README', 'examples/**/*', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
- s.extra_rdoc_files = %w( README )
- s.rdoc_options.concat ['--main', 'README']
+ s.extra_rdoc_files = %w( README.rdoc )
+ s.rdoc_options.concat ['--main', 'README.rdoc']
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
s.add_dependency('arel', '~> 0.4.0')
- s.add_dependency('tzinfo', '~> 0.3.16')
+ s.add_dependency('tzinfo', '~> 0.3.22')
end
diff --git a/activerecord/examples/performance.rb b/activerecord/examples/performance.rb
index f7d358337c..a985cfcb66 100644
--- a/activerecord/examples/performance.rb
+++ b/activerecord/examples/performance.rb
@@ -58,7 +58,7 @@ end
sqlfile = File.expand_path("../performance.sql", __FILE__)
if File.exists?(sqlfile)
- mysql_bin = %w[mysql mysql5].select { |bin| `which #{bin}`.length > 0 }
+ mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 }
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
else
puts 'Generating data...'
diff --git a/activerecord/install.rb b/activerecord/install.rb
deleted file mode 100644
index c87398b1f4..0000000000
--- a/activerecord/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by ways of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("active_record", "active_record.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-}
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index c45400d3d9..83a9ab46c5 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -9,11 +9,13 @@ module ActiveRecord
end unless self.new_record?
end
- # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
- # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
- # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
- # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
- # and how it can be turned back into attributes (when the entity is saved to the database). Example:
+ # Active Record implements aggregation through a macro-like class method called +composed_of+
+ # for representing attributes as value objects. It expresses relationships like "Account [is]
+ # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
+ # to the macro adds a description of how the value objects are created from the attributes of
+ # the entity object (when the entity is initialized either as a new object or from finding an
+ # existing object) and how it can be turned back into attributes (when the entity is saved to
+ # the database).
#
# class Customer < ActiveRecord::Base
# composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
@@ -68,9 +70,10 @@ module ActiveRecord
# end
# end
#
- # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
- # composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
- # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
+ # Now it's possible to access attributes from the database through the value objects instead. If
+ # you choose to name the composition the same as the attribute's name, it will be the only way to
+ # access that attribute. That's the case with our +balance+ attribute. You interact with the value
+ # objects just like you would any other attribute, though:
#
# customer.balance = Money.new(20) # sets the Money value object and the attribute
# customer.balance # => Money value object
@@ -79,8 +82,8 @@ module ActiveRecord
# customer.balance == Money.new(20) # => true
# customer.balance < Money.new(5) # => false
#
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
- # determine the order of the parameters. Example:
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order
+ # of the mappings will determine the order of the parameters.
#
# customer.address_street = "Hyancintvej"
# customer.address_city = "Copenhagen"
@@ -91,38 +94,43 @@ module ActiveRecord
#
# == Writing value objects
#
- # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
- # $5. Two Money objects both representing $5 should be equal (through methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking
- # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
- # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
+ # Value objects are immutable and interchangeable objects that represent a given value, such as
+ # a Money object representing $5. Two Money objects both representing $5 should be equal (through
+ # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
+ # unlike entity objects where equality is determined by identity. An entity class such as Customer can
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is
+ # determined by object or relational unique identifiers (such as primary keys). Normal
+ # ActiveRecord::Base classes are entity objects.
#
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
- # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchange_to method that
- # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
- # changed through means other than the writer method.
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have
+ # its amount changed after creation. Create a new Money object with the new value instead. This
+ # is exemplified by the Money#exchange_to method that returns a new value object instead of changing
+ # its own values. Active Record won't persist value objects that have been changed through means
+ # other than the writer method.
#
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
- # change it afterwards will result in a ActiveSupport::FrozenObjectError.
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
+ # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
#
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
- # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
+ # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
#
# == Custom constructors and converters
#
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value class passing each of the
- # mapped attributes, in the order specified by the <tt>:mapping</tt> option, as arguments. If the value class doesn't support
- # this convention then +composed_of+ allows a custom constructor to be specified.
+ # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
+ # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
+ # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
+ # a custom constructor to be specified.
#
- # When a new value is assigned to the value object the default assumption is that the new value is an instance of the value
- # class. Specifying a custom converter allows the new value to be automatically converted to an instance of value class if
- # necessary.
+ # When a new value is assigned to the value object the default assumption is that the new value
+ # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
+ # converted to an instance of value class if necessary.
#
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that should be aggregated using the
- # NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor for the value class is called +create+ and it
- # expects a CIDR address string as a parameter. New values can be assigned to the value object using either another
- # NetAddr::CIDR object, a string or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to
- # meet these requirements:
+ # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
+ # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
+ # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
+ # values can be assigned to the value object using either another NetAddr::CIDR object, a string
+ # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
+ # these requirements:
#
# class NetworkResource < ActiveRecord::Base
# composed_of :cidr,
@@ -149,9 +157,9 @@ module ActiveRecord
#
# == Finding records by a value object
#
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
- # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
- # +balance_currency+ equal to "USD":
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
+ # by specifying an instance of the value object in the conditions hash. The following example
+ # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
#
# Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
#
@@ -160,23 +168,28 @@ module ActiveRecord
# <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
#
# Options are:
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name can't be inferred
- # from the part id. So <tt>composed_of :address</tt> will by default be linked to the Address class, but
- # if the real class name is CompanyAddress, you'll have to specify it with this option.
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value object. Each mapping
- # is represented as an array where the first item is the name of the entity attribute and the second item is the
- # name the attribute in the value object. The order in which mappings are defined determine the order in which
- # attributes are sent to the value class constructor.
+ # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
+ # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
+ # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
+ # with this option.
+ # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
+ # object. Each mapping is represented as an array where the first item is the name of the
+ # entity attribute and the second item is the name the attribute in the value object. The
+ # order in which mappings are defined determine the order in which attributes are sent to the
+ # value class constructor.
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all mapped attributes.
+ # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
+ # mapped attributes.
# This defaults to +false+.
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that is called to
- # initialize the value object. The constructor is passed all of the mapped attributes, in the order that they
- # are defined in the <tt>:mapping option</tt>, as arguments and uses them to instantiate a <tt>:class_name</tt> object.
+ # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
+ # is called to initialize the value object. The constructor is passed all of the mapped attributes,
+ # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
+ # to instantiate a <tt>:class_name</tt> object.
# The default is <tt>:new</tt>.
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt> or a Proc that is
- # called when a new value is assigned to the value object. The converter is passed the single value that is used
- # in the assignment and is only called if the new value is not an instance of <tt>:class_name</tt>.
+ # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
+ # or a Proc that is called when a new value is assigned to the value object. The converter is
+ # passed the single value that is used in the assignment and is only called if the new value is
+ # not an instance of <tt>:class_name</tt>.
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index cbec5789fd..0f0fdc2e21 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -9,8 +9,8 @@ module ActiveRecord
# Implements the details of eager loading of Active Record associations.
# Application developers should not use this module directly.
#
- # ActiveRecord::Base is extended with this module. The source code in
- # ActiveRecord::Base references methods defined in this module.
+ # <tt>ActiveRecord::Base</tt> is extended with this module. The source code in
+ # <tt>ActiveRecord::Base</tt> references methods defined in this module.
#
# Note that 'eager loading' and 'preloading' are actually the same thing.
# However, there are two different eager loading strategies.
@@ -55,7 +55,7 @@ module ActiveRecord
# == Parameters
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
# i.e. +records+ itself may also contain arrays of records. In any case,
- # +preload_associations+ will preload the associations all records by
+ # +preload_associations+ will preload the all associations records by
# flattening +records+.
#
# +associations+ specifies one or more associations that you want to
@@ -110,15 +110,15 @@ module ActiveRecord
def preload_one_association(records, association, preload_options={})
class_to_reflection = {}
# Not all records have the same class, so group then preload
- # group on the reflection itself so that if various subclass share the same association then we do not split them
- # unnecessarily
- records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
+ # group on the reflection itself so that if various subclass share the same association then
+ # we do not split them unnecessarily
+ records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
# the following could call 'preload_belongs_to_association',
# 'preload_has_many_association', etc.
- send("preload_#{reflection.macro}_association", records, reflection, preload_options)
+ send("preload_#{reflection.macro}_association", _records, reflection, preload_options)
end
end
@@ -149,7 +149,8 @@ module ActiveRecord
seen_keys = {}
associated_records.each do |associated_record|
#this is a has_one or belongs_to: there should only be one record.
- #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
+ #Unfortunately we can't (in portable way) ask the database for
+ #'all records where foo_id in (x,y,z), but please
# only one row per distinct foo_id' so this where we enforce that
next if seen_keys[associated_record[key].to_s]
seen_keys[associated_record[key].to_s] = true
@@ -304,7 +305,8 @@ module ActiveRecord
polymorph_type = options[:foreign_type]
klasses_and_ids = {}
- # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back
+ # to their parent_records
records.each do |record|
if klass = record.send(polymorph_type)
klass_id = record.send(primary_key_name)
@@ -378,7 +380,7 @@ module ActiveRecord
:order => preload_options[:order] || options[:order]
}
- reflection.klass.unscoped.apply_finder_options(find_options).to_a
+ reflection.klass.scoped.apply_finder_options(find_options).to_a
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 65daa8ffbe..73c0900c8b 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/string/conversions'
+require 'active_support/core_ext/module/remove_method'
module ActiveRecord
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
@@ -113,7 +114,7 @@ module ActiveRecord
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
- # Clears out the association cache
+ # Clears out the association cache.
def clear_association_cache #:nodoc:
self.class.reflect_on_all_associations.to_a.each do |assoc|
instance_variable_set "@#{assoc.name}", nil
@@ -121,7 +122,7 @@ module ActiveRecord
end
private
- # Gets the specified association instance if it responds to :loaded?, nil otherwise.
+ # Returns the specified association instance if it responds to :loaded?, nil otherwise.
def association_instance_get(name)
ivar = "@#{name}"
if instance_variable_defined?(ivar)
@@ -135,10 +136,12 @@ module ActiveRecord
instance_variable_set("@#{name}", association)
end
- # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
- # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
- # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
- # methods. Example:
+ # Associations are a set of macro-like class methods for tying objects together through
+ # foreign keys. They express relationships like "Project has one Project Manager"
+ # or "Project belongs to a Portfolio". Each macro adds a number of methods to the
+ # class which are specialized according to the collection or association symbol and the
+ # options hash. It works much the same way as Ruby's own <tt>attr*</tt>
+ # methods.
#
# class Project < ActiveRecord::Base
# belongs_to :portfolio
@@ -147,7 +150,8 @@ module ActiveRecord
# has_and_belongs_to_many :categories
# end
#
- # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
+ # The project class now has the following methods (and more) to ease the traversal and
+ # manipulation of its relationships:
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
@@ -158,8 +162,9 @@ module ActiveRecord
#
# === A word of warning
#
- # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
- # adds a method with that name to its model, it will override the inherited method and break things.
+ # Don't create associations that have the same name as instance methods of
+ # <tt>ActiveRecord::Base</tt>. Since the association adds a method with that name to
+ # its model, it will override the inherited method and break things.
# For instance, +attributes+ and +connection+ would be bad choices for association names.
#
# == Auto-generated methods
@@ -269,8 +274,8 @@ module ActiveRecord
#
# == Is it a +belongs_to+ or +has_one+ association?
#
- # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class
- # declaring the +belongs_to+ relationship. Example:
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign
+ # key, which goes on the table for the class declaring the +belongs_to+ relationship.
#
# class User < ActiveRecord::Base
# # I reference an account.
@@ -299,36 +304,44 @@ module ActiveRecord
#
# == Unsaved objects and associations
#
- # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
- # aware of, mostly involving the saving of associated objects.
+ # You can manipulate objects and associations before they are saved to the database, but
+ # there is some special behavior you should be aware of, mostly involving the saving of
+ # associated objects.
#
- # Unless you set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
+ # You can set the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association. Setting it
# to +true+ will _always_ save the members, whereas setting it to +false+ will
# _never_ save the members.
#
# === One-to-one associations
#
- # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
- # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
- # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
- # is cancelled.
- # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below).
- # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
- # does not save the parent either.
+ # * Assigning an object to a +has_one+ association automatically saves that object and
+ # the object being replaced (if there is one), in order to update their primary
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment
+ # statement returns +false+ and the assignment is cancelled.
+ # * If you wish to assign an object to a +has_one+ association without saving it,
+ # use the <tt>association.build</tt> method (documented below).
+ # * Assigning an object to a +belongs_to+ association does not save the object, since
+ # the foreign key field belongs on the parent. It does not save the parent either.
#
# === Collections
#
- # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
- # (the owner of the collection) is not yet stored in the database.
- # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+.
- # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below).
- # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
+ # saves that object, except if the parent object (the owner of the collection) is not yet
+ # stored in the database.
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
+ # fails, then <tt>push</tt> returns +false+.
+ # * You can add an object to a collection without automatically saving it by using the
+ # <tt>collection.build</tt> method (documented below).
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
+ # saved when the parent is saved.
#
# === Association callbacks
#
- # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
- # triggered when you add an object to or remove an object from an association collection. Example:
+ # Similar to the normal callbacks that hook into the lifecycle of an Active Record object,
+ # you can also define callbacks that get triggered when you add an object to or remove an
+ # object from an association collection.
#
# class Project
# has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
@@ -341,19 +354,21 @@ module ActiveRecord
# It's possible to stack callbacks by passing them as an array. Example:
#
# class Project
- # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
+ # has_and_belongs_to_many :developers,
+ # :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
# end
#
# Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
#
- # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
- # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get
+ # added to the collection. Same with the +before_remove+ callbacks; if an exception is
+ # thrown the object doesn't get removed.
#
# === Association extensions
#
- # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
- # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
- # Example:
+ # The proxy objects that control the access to associations can be extended through anonymous
+ # modules. This is especially beneficial for adding new finders, creators, and other
+ # factory-type methods that are only used as part of this association.
#
# class Account < ActiveRecord::Base
# has_many :people do
@@ -368,7 +383,8 @@ module ActiveRecord
# person.first_name # => "David"
# person.last_name # => "Heinemeier Hansson"
#
- # If you need to share the same extensions between many associations, you can use a named extension module. Example:
+ # If you need to share the same extensions between many associations, you can use a named
+ # extension module.
#
# module FindOrCreateByNameExtension
# def find_or_create_by_name(name)
@@ -385,9 +401,10 @@ module ActiveRecord
# has_many :people, :extend => FindOrCreateByNameExtension
# end
#
- # If you need to use multiple named extension modules, you can specify an array of modules with the <tt>:extend</tt> option.
- # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
- # those earlier in the array. Example:
+ # If you need to use multiple named extension modules, you can specify an array of modules
+ # with the <tt>:extend</tt> option.
+ # In the case of name conflicts between methods in the modules, methods in modules later
+ # in the array supercede those earlier in the array.
#
# class Account < ActiveRecord::Base
# has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
@@ -398,12 +415,14 @@ module ActiveRecord
#
# * +proxy_owner+ - Returns the object the association is part of.
# * +proxy_reflection+ - Returns the reflection object that describes the association.
- # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or
+ # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
#
# === Association Join Models
#
- # Has Many associations can be configured with the <tt>:through</tt> option to use an explicit join model to retrieve the data. This
- # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
+ # Has Many associations can be configured with the <tt>:through</tt> option to use an
+ # explicit join model to retrieve the data. This operates similarly to a
+ # +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
# callbacks, and extra attributes on the join model. Consider the following schema:
#
# class Author < ActiveRecord::Base
@@ -417,7 +436,7 @@ module ActiveRecord
# end
#
# @author = Author.find :first
- # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to
# @author.books # selects all books by using the Authorship join model
#
# You can also go through a +has_many+ association on the join model:
@@ -438,7 +457,7 @@ module ActiveRecord
#
# @firm = Firm.find :first
# @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
- # @firm.invoices # selects all invoices by going through the Client join model.
+ # @firm.invoices # selects all invoices by going through the Client join model
#
# Similarly you can go through a +has_one+ association on the join model:
#
@@ -460,16 +479,18 @@ module ActiveRecord
# @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group
# @group.avatars # selects all avatars by going through the User join model.
#
- # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are
- # *read-only*. For example, the following would not work following the previous example:
+ # An important caveat with going through +has_one+ or +has_many+ associations on the
+ # join model is that these associations are *read-only*. For example, the following
+ # would not work following the previous example:
#
- # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around.
+ # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
# @group.avatars.delete(@group.avatars.last) # so would this
#
# === Polymorphic Associations
#
- # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
- # specify an interface that a +has_many+ association must adhere to.
+ # Polymorphic associations on models are not restricted on what types of models they
+ # can be associated with. Rather, they specify an interface that a +has_many+ association
+ # must adhere to.
#
# class Asset < ActiveRecord::Base
# belongs_to :attachable, :polymorphic => true
@@ -481,13 +502,16 @@ module ActiveRecord
#
# @asset.attachable = @post
#
- # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
- # an +attachable_id+ integer column and an +attachable_type+ string column.
+ # This works by using a type column in addition to a foreign key to specify the associated
+ # record. In the Asset example, you'd need an +attachable_id+ integer column and an
+ # +attachable_type+ string column.
#
- # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
- # for the associations to work as expected, ensure that you store the base model for the STI models in the
- # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
- # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
+ # Using polymorphic associations in combination with single table inheritance (STI) is
+ # a little tricky. In order for the associations to work as expected, ensure that you
+ # store the base model for the STI models in the type column of the polymorphic
+ # association. To continue with the asset example above, suppose there are guest posts
+ # and member posts that use the posts table for STI. In this case, there must be a +type+
+ # column in the posts table.
#
# class Asset < ActiveRecord::Base
# belongs_to :attachable, :polymorphic => true
@@ -510,9 +534,10 @@ module ActiveRecord
#
# == Caching
#
- # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
- # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
- # worrying too much about performance at the first go. Example:
+ # All of the methods are built on a simple caching principle that will keep the result
+ # of the last query around unless specifically instructed not to. The cache is even
+ # shared across methods to make it even cheaper to use the macro-added methods without
+ # worrying too much about performance at the first go.
#
# project.milestones # fetches milestones from the database
# project.milestones.size # uses the milestone cache
@@ -522,9 +547,10 @@ module ActiveRecord
#
# == Eager loading of associations
#
- # Eager loading is a way to find objects of a certain class and a number of named associations. This is
- # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
- # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
+ # Eager loading is a way to find objects of a certain class and a number of named associations.
+ # This is one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100
+ # posts that each need to display their author triggers 101 database queries. Through the
+ # use of eager loading, the 101 queries can be reduced to 2.
#
# class Post < ActiveRecord::Base
# belongs_to :author
@@ -539,44 +565,55 @@ module ActiveRecord
# puts "Last comment on: " + post.comments.first.created_on
# end
#
- # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's
+ # first just optimize it for retrieving the author:
#
# for post in Post.find(:all, :include => :author)
#
- # This references the name of the +belongs_to+ association that also used the <tt>:author</tt> symbol. After loading the posts, find
- # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
+ # This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
+ # symbol. After loading the posts, find will collect the +author_id+ from each one and load
+ # all the referenced authors with one query. Doing so will cut down the number of queries
+ # from 201 to 102.
#
# We can improve upon the situation further by referencing both associations in the finder with:
#
# for post in Post.find(:all, :include => [ :author, :comments ])
#
- # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
- # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below).
+ # This will load all comments with a single query. This reduces the total number of queries
+ # to 3. More generally the number of queries will be 1 plus the number of associations
+ # named (except if some of the associations are polymorphic +belongs_to+ - see below).
#
# To include a deep hierarchy of associations, use a hash:
#
# for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
#
- # That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match
- # symbols, arrays and hashes in any combination to describe the associations you want to load.
+ # That'll grab not only all the comments but all their authors and gravatar pictures.
+ # You can mix and match symbols, arrays and hashes in any combination to describe the
+ # associations you want to load.
#
- # All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
- # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
- # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
+ # All of this power shouldn't fool you into thinking that you can pull out huge amounts
+ # of data with no performance penalty just because you've reduced the number of queries.
+ # The database still needs to send all the data to Active Record and it still needs to
+ # be processed. So it's no catch-all for performance problems, but it's a great way to
+ # cut down on the number of queries in a situation as the one described above.
#
- # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
- # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables
+ # other than the main one. If this is the case Active Record falls back to the previously
+ # used LEFT OUTER JOIN based strategy. For example
#
# Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
#
- # This will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
- # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences.
- # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
- # and not just to the association. You must disambiguate column references for this fallback to happen, for example
+ # This will result in a single SQL query with joins along the lines of:
+ # <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and
+ # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions
+ # like this can have unintended consequences.
+ # In the above example posts with no approved comments are not returned at all, because
+ # the conditions apply to the SQL statement as a whole and not just to the association.
+ # You must disambiguate column references for this fallback to happen, for example
# <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
#
- # If you do want eager load only some members of an association it is usually more natural to <tt>:include</tt> an association
- # which has conditions defined on it:
+ # If you do want eager load only some members of an association it is usually more natural
+ # to <tt>:include</tt> an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
# has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
@@ -584,9 +621,11 @@ module ActiveRecord
#
# Post.find(:all, :include => :approved_comments)
#
- # This will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
+ # This will load posts and eager load the +approved_comments+ association, which contains
+ # only those comments that have been approved.
#
- # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored, returning all the associated objects:
+ # If you eager load an association with a specified <tt>:limit</tt> option, it will be ignored,
+ # returning all the associated objects:
#
# class Picture < ActiveRecord::Base
# has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
@@ -594,8 +633,8 @@ module ActiveRecord
#
# Picture.find(:first, :include => :most_recent_comments).most_recent_comments # => returns all associated comments.
#
- # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
- # before the actual model exists.
+ # When eager loaded, conditions are interpolated in the context of the model class, not
+ # the model instance. Conditions are lazily interpolated before the actual model exists.
#
# Eager loading is supported with polymorphic associations.
#
@@ -607,17 +646,21 @@ module ActiveRecord
#
# Address.find(:all, :include => :addressable)
#
- # This will execute one query to load the addresses and load the addressables with one query per addressable type.
- # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
- # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
- # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
- # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
+ # This will execute one query to load the addresses and load the addressables with one
+ # query per addressable type.
+ # For example if all the addressables are either of class Person or Company then a total
+ # of 3 queries will be executed. The list of addressable types to load is determined on
+ # the back of the addresses loaded. This is not supported if Active Record has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError.
+ # The reason is that the parent model's type is a column value so its corresponding table
+ # name cannot be put in the +FROM+/+JOIN+ clauses of that query.
#
# == Table Aliasing
#
- # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
- # the standard table name is used. The second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>. Indexes are appended
- # for any more successive uses of the table name.
+ # Active Record uses table aliasing in the case that a table is referenced multiple times
+ # in a join. If a table is referenced only once, the standard table name is used. The
+ # second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
+ # Indexes are appended for any more successive uses of the table name.
#
# Post.find :all, :joins => :comments
# # => SELECT ... FROM posts INNER JOIN comments ON ...
@@ -650,7 +693,8 @@ module ActiveRecord
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
#
- # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table names will take precedence over the eager associations:
+ # If you wish to specify your own custom joins using a <tt>:joins</tt> option, those table
+ # names will take precedence over the eager associations:
#
# Post.find :all, :joins => :comments, :joins => "inner join comments ..."
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
@@ -659,7 +703,8 @@ module ActiveRecord
# INNER JOIN comments special_comments_posts ...
# INNER JOIN comments ...
#
- # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
+ # Table aliases are automatically truncated according to the maximum length of table identifiers
+ # according to the specific database.
#
# == Modules
#
@@ -675,9 +720,10 @@ module ActiveRecord
# end
# end
#
- # When <tt>Firm#clients</tt> is called, it will in turn call <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
- # If you want to associate with a class in another module scope, this can be done by specifying the complete class name.
- # Example:
+ # When <tt>Firm#clients</tt> is called, it will in turn call
+ # <tt>MyApplication::Business::Client.find_all_by_firm_id(firm.id)</tt>.
+ # If you want to associate with a class in another module scope, this can be done by
+ # specifying the complete class name.
#
# module MyApplication
# module Business
@@ -693,8 +739,8 @@ module ActiveRecord
#
# == Bi-directional associations
#
- # When you specify an association there is usually an association on the associated model that specifies the same
- # relationship in reverse. For example, with the following models:
+ # When you specify an association there is usually an association on the associated model
+ # that specifies the same relationship in reverse. For example, with the following models:
#
# class Dungeon < ActiveRecord::Base
# has_many :traps
@@ -709,9 +755,11 @@ module ActiveRecord
# belongs_to :dungeon
# end
#
- # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are the inverse of each other and the
- # inverse of the +dungeon+ association on +EvilWizard+ is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
- # Active Record doesn't know anything about these inverse relationships and so no object loading optimisation is possible. For example:
+ # The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are
+ # the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
+ # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
+ # Active Record doesn't know anything about these inverse relationships and so no object
+ # loading optimisation is possible. For example:
#
# d = Dungeon.first
# t = d.traps.first
@@ -719,9 +767,11 @@ module ActiveRecord
# d.level = 10
# d.level == t.dungeon.level # => false
#
- # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are
- # actually different in-memory copies of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
- # Active Record about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to:
+ # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
+ # the same object data from the database, but are actually different in-memory copies
+ # of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
+ # Active Record about inverse relationships and it will optimise object loading. For
+ # example, if we changed our model definitions to:
#
# class Dungeon < ActiveRecord::Base
# has_many :traps, :inverse_of => :dungeon
@@ -736,8 +786,8 @@ module ActiveRecord
# belongs_to :dungeon, :inverse_of => :evil_wizard
# end
#
- # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same in-memory instance and our final <tt>d.level == t.dungeon.level</tt>
- # will return +true+.
+ # Then, from our code snippet above, +d+ and <tt>t.dungeon</tt> are actually the same
+ # in-memory instance and our final <tt>d.level == t.dungeon.level</tt> will return +true+.
#
# There are limitations to <tt>:inverse_of</tt> support:
#
@@ -747,13 +797,13 @@ module ActiveRecord
#
# == Type safety with <tt>ActiveRecord::AssociationTypeMismatch</tt>
#
- # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
- # get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
+ # If you attempt to assign an object to an association that doesn't match the inferred
+ # or specified <tt>:class_name</tt>, you'll get an <tt>ActiveRecord::AssociationTypeMismatch</tt>.
#
# == Options
#
- # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
- # possible.
+ # All of the association macros can be specialized through options. This makes cases
+ # more complex than the simple and guessable ones possible.
module ClassMethods
# Specifies a one-to-many association. The following methods for retrieval and query of
# collections of associated objects will be added:
@@ -827,20 +877,22 @@ module ActiveRecord
# === Supported options
# [:class_name]
# Specify the class name of the association. Use it only if that name can't be inferred
- # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
- # if the real class name is SpecialProduct, you'll have to specify it with this option.
+ # from the association name. So <tt>has_many :products</tt> will by default be linked
+ # to the Product class, but if the real class name is SpecialProduct, you'll have to
+ # specify it with this option.
# [:conditions]
# Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash
- # is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
- # or <tt>@blog.posts.build</tt>.
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from
+ # the association are scoped if a hash is used.
+ # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published
+ # posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
# such as <tt>last_name, first_name DESC</tt>.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
- # as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+
+ # association will use "person_id" as the default <tt>:foreign_key</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:dependent]
@@ -854,10 +906,12 @@ module ActiveRecord
#
# [:finder_sql]
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
- # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+
+ # is _not_ added.
# [:counter_sql]
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
- # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:extend]
# Specify a named module for extending the proxy. See "Association extensions".
# [:include]
@@ -865,25 +919,31 @@ module ActiveRecord
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
- # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt>
+ # returns. Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
- # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if
+ # you, for example, want to do a join but not include the joined columns. Do not forget
+ # to include the primary and foreign keys, otherwise it will raise an error.
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:through]
- # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
- # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. The collection of join models can be managed via the collection
- # API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly,
+ # Specifies a join model through which to perform the query. Options for <tt>:class_name</tt>
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
+ # can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>, <tt>has_one</tt>
+ # or <tt>has_many</tt> association on the join model. The collection of join models
+ # can be managed via the collection API. For example, new join models are created for
+ # newly associated objects, and if some are gone their rows are deleted (directly,
# no destroy callbacks are triggered).
# [:source]
- # Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
- # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
+ # Specifies the source association name used by <tt>has_many :through</tt> queries.
+ # Only use it if the name cannot be inferred from the association.
+ # <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
# <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
@@ -895,12 +955,14 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. true by default.
# [:autosave]
- # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If true, always save the associated objects or destroy them if marked for destruction,
+ # when saving the parent object.
# If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
# [:inverse_of]
- # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
- # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
+ # that is the inverse of this <tt>has_many</tt> association. Does not work in combination
+ # with <tt>:through</tt> or <tt>:as</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
@@ -974,19 +1036,20 @@ module ActiveRecord
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>rank = 5</tt>. Record creation from the association is scoped if a hash
- # is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create an enabled account with <tt>@company.create_account</tt>
- # or <tt>@company.build_account</tt>.
+ # is used. <tt>has_one :account, :conditions => {:enabled => true}</tt> will create
+ # an enabled account with <tt>@company.create_account</tt> or <tt>@company.build_account</tt>.
# [:order]
# Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
# such as <tt>last_name, first_name DESC</tt>.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
- # object's foreign key is set to +NULL+. Also, association is assigned.
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
+ # If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
+ # Also, association is assigned.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
- # as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
+ # will use "person_id" as the default <tt>:foreign_key</tt>.
# [:primary_key]
# Specify the method that returns the primary key used for the association. By default this is +id+.
# [:include]
@@ -994,15 +1057,18 @@ module ActiveRecord
# [:as]
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
+ # you want to do a join but not include the joined columns. Do not forget to include the
+ # primary and foreign keys, otherwise it will raise an error.
# [:through]
- # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
- # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a
- # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model.
+ # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>
+ # and <tt>:foreign_key</tt> are ignored, as the association uses the source reflection. You
+ # can only use a <tt>:through</tt> query through a <tt>has_one</tt> or <tt>belongs_to</tt>
+ # association on the join model.
# [:source]
- # Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be
- # inferred from the association. <tt>has_one :favorite, :through => :favorites</tt> will look for a
+ # Specifies the source association name used by <tt>has_one :through</tt> queries.
+ # Only use it if the name cannot be inferred from the association.
+ # <tt>has_one :favorite, :through => :favorites</tt> will look for a
# <tt>:favorite</tt> on Favorite, unless a <tt>:source</tt> is given.
# [:source_type]
# Specifies type of the source association used by <tt>has_one :through</tt> queries where the source
@@ -1012,17 +1078,19 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated object when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
- # If false, never save or destroy the associated object.
+ # If true, always save the associated object or destroy it if marked for destruction,
+ # when saving the parent object. If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
# [:inverse_of]
- # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
- # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
+ # Specifies the name of the <tt>belongs_to</tt> association on the associated object
+ # that is the inverse of this <tt>has_one</tt> association. Does not work in combination
+ # with <tt>:through</tt> or <tt>:as</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
- # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign
+ # # key value to NULL rather than destroying it
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
# has_one :attachment, :as => :attachable
@@ -1084,27 +1152,34 @@ module ActiveRecord
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
# SQL fragment, such as <tt>authorized = 1</tt>.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed
+ # if, for example, you want to do a join but not include the joined columns. Do not
+ # forget to include the primary and foreign keys, otherwise it will raise an error.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
- # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
- # will use a foreign key of "favorite_person_id".
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt>
+ # association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
+ # <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
+ # of "favorite_person_id".
# [:primary_key]
- # Specify the method that returns the primary key of associated object used for the association. By default this is id.
+ # Specify the method that returns the primary key of associated object used for the association.
+ # By default this is id.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
- # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
+ # This option should not be specified when <tt>belongs_to</tt> is used in conjunction with
+ # a <tt>has_many</tt> relationship on another class because of the potential to leave
# orphaned records behind.
# [:counter_cache]
# Caches the number of belonging objects on the associate class through the use of +increment_counter+
- # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
- # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
- # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
- # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
- # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
+ # and +decrement_counter+. The counter cache is incremented when an object of this
+ # class is created and decremented when it's destroyed. This requires that a column
+ # named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
+ # is used on the associate class (such as a Post class). You can also specify a custom counter
+ # cache column by providing a column name instead of a +true+/+false+ value to this
+ # option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes
+ # using +attr_readonly+.
# [:include]
# Specify second-order associations that should be eager loaded when this object is loaded.
# [:polymorphic]
@@ -1116,15 +1191,18 @@ module ActiveRecord
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
# [:autosave]
- # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
+ # If true, always save the associated object or destroy it if marked for destruction, when
+ # saving the parent object.
# If false, never save or destroy the associated object.
# By default, only save the associated object if it's a new record.
# [:touch]
- # If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
- # destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
+ # If true, the associated object will be touched (the updated_at/on attributes set to now)
+ # when this record is either saved or destroyed. If you specify a symbol, that attribute
+ # will be updated with the current time instead of the updated_at/on attribute.
# [:inverse_of]
- # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt>
- # association. Does not work in combination with the <tt>:polymorphic</tt> options.
+ # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
+ # object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
+ # combination with the <tt>:polymorphic</tt> options.
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
#
# Option examples:
@@ -1158,9 +1236,10 @@ module ActiveRecord
# Specifies a many-to-many relationship with another class. This associates two classes via an
# intermediate join table. Unless the join table is explicitly specified as an option, it is
# guessed using the lexical order of the class names. So a join between Developer and Project
- # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
- # is calculated using the <tt><</tt> operator for String. This means that if the strings are of different lengths,
- # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
+ # will give the default join table name of "developers_projects" because "D" outranks "P".
+ # Note that this precedence is calculated using the <tt><</tt> operator for String. This
+ # means that if the strings are of different lengths, and the strings are equal when compared
+ # up to the shortest length, then the longer string is considered of higher
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
@@ -1182,9 +1261,10 @@ module ActiveRecord
# end
# end
#
- # Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through
- # +has_and_belongs_to_many+ associations. Records returned from join tables with additional attributes will be marked as
- # readonly (because we can't save changes to the additional attributes). It's strongly recommended that you upgrade any
+ # Deprecated: Any additional fields added to the join table will be placed as attributes when
+ # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
+ # tables with additional attributes will be marked as readonly (because we can't save changes
+ # to the additional attributes). It's strongly recommended that you upgrade any
# associations with attributes to a real join model (see introduction).
#
# Adds the following methods for retrieval and query:
@@ -1224,7 +1304,8 @@ module ActiveRecord
# with +attributes+ and linked to this object through the join table, but has not yet been saved.
# [collection.create(attributes = {})]
# Returns a new object of the collection type that has been instantiated
- # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
+ # with +attributes+, linked to this object through the join table, and that has already been
+ # saved (if it passed the validation).
#
# (+collection+ is replaced with the symbol passed as the first argument, so
# <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>.)
@@ -1259,8 +1340,9 @@ module ActiveRecord
# MUST be declared underneath any +has_and_belongs_to_many+ declaration in order to work.
# [:foreign_key]
# Specify the foreign key used for the association. By default this is guessed to be the name
- # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
- # to Project will use "person_id" as the default <tt>:foreign_key</tt>.
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes
+ # a +has_and_belongs_to_many+ association to Project will use "person_id" as the
+ # default <tt>:foreign_key</tt>.
# [:association_foreign_key]
# Specify the foreign key used for the association on the receiving side of the association.
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
@@ -1268,7 +1350,8 @@ module ActiveRecord
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:conditions]
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
- # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
+ # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are
+ # scoped if a hash is used.
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
# or <tt>@blog.posts.build</tt>.
# [:order]
@@ -1280,7 +1363,8 @@ module ActiveRecord
# Overwrite the default generated SQL statement used to fetch the association with a manual statement
# [:counter_sql]
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
- # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by
+ # replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
# [:delete_sql]
# Overwrite the default generated SQL statement used to remove links between the associated
# classes with a manual statement.
@@ -1294,20 +1378,24 @@ module ActiveRecord
# [:group]
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
# [:having]
- # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns.
+ # Uses the <tt>HAVING</tt> SQL-clause.
# [:limit]
# An integer determining the limit on the number of rows that should be returned.
# [:offset]
- # An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip the first 4 rows.
# [:select]
- # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
- # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example,
+ # you want to do a join but not include the joined columns. Do not forget to include the primary
+ # and foreign keys, otherwise it will raise an error.
# [:readonly]
# If true, all the associated objects are readonly through the association.
# [:validate]
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
# [:autosave]
- # If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
+ # If true, always save the associated objects or destroy them if marked for destruction, when
+ # saving the parent object.
# If false, never save or destroy the associated objects.
# By default, only save associated objects that are new records.
#
@@ -1354,7 +1442,7 @@ module ActiveRecord
end
def association_accessor_methods(reflection, association_proxy_class)
- define_method(reflection.name) do |*params|
+ redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
association = association_instance_get(reflection.name)
@@ -1371,12 +1459,12 @@ module ActiveRecord
association.target.nil? ? nil : association
end
- define_method("loaded_#{reflection.name}?") do
+ redefine_method("loaded_#{reflection.name}?") do
association = association_instance_get(reflection.name)
association && association.loaded?
end
-
- define_method("#{reflection.name}=") do |new_value|
+
+ redefine_method("#{reflection.name}=") do |new_value|
association = association_instance_get(reflection.name)
if association.nil? || association.target != new_value
@@ -1386,8 +1474,8 @@ module ActiveRecord
association.replace(new_value)
association_instance_set(reflection.name, new_value.nil? ? nil : association)
end
-
- define_method("set_#{reflection.name}_target") do |target|
+
+ redefine_method("set_#{reflection.name}_target") do |target|
return if target.nil? and association_proxy_class == BelongsToAssociation
association = association_proxy_class.new(self, reflection)
association.target = target
@@ -1396,7 +1484,7 @@ module ActiveRecord
end
def collection_reader_method(reflection, association_proxy_class)
- define_method(reflection.name) do |*params|
+ redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
association = association_instance_get(reflection.name)
@@ -1409,8 +1497,8 @@ module ActiveRecord
association
end
-
- define_method("#{reflection.name.to_s.singularize}_ids") do
+
+ redefine_method("#{reflection.name.to_s.singularize}_ids") do
if send(reflection.name).loaded? || reflection.options[:finder_sql]
send(reflection.name).map(&:id)
else
@@ -1430,22 +1518,24 @@ module ActiveRecord
collection_reader_method(reflection, association_proxy_class)
if writer
- define_method("#{reflection.name}=") do |new_value|
+ redefine_method("#{reflection.name}=") do |new_value|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
association = send(reflection.name)
association.replace(new_value)
association
end
- define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
- ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
+ redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
+ pk_column = reflection.primary_key_column
+ ids = (new_value || []).reject { |nid| nid.blank? }
+ ids.map!{ |i| pk_column.type_cast(i) }
send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
end
end
end
def association_constructor_method(constructor, reflection, association_proxy_class)
- define_method("#{constructor}_#{reflection.name}") do |*params|
+ redefine_method("#{constructor}_#{reflection.name}") do |*params|
attributees = params.first unless params.empty?
replace_existing = params[1].nil? ? true : params[1]
association = association_instance_get(reflection.name)
@@ -1486,8 +1576,8 @@ module ActiveRecord
end
def add_touch_callbacks(reflection, touch_attribute)
- method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym
- define_method(method_name) do
+ method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
+ redefine_method(method_name) do
association = send(reflection.name)
if touch_attribute == true
@@ -1497,20 +1587,18 @@ module ActiveRecord
end
end
after_save(method_name)
+ after_touch(method_name)
after_destroy(method_name)
end
# Creates before_destroy callback methods that nullify, delete or destroy
# has_many associated objects, according to the defined :dependent rule.
- # If the association is marked as :dependent => :restrict, create a callback
- # that prevents deleting entirely.
#
- # See HasManyAssociation#delete_records. Dependent associations
- # delete children, otherwise foreign key is set to NULL.
- # See HasManyAssociation#delete_records. Dependent associations
- # delete children if the option is set to :destroy or :delete_all, set the
- # foreign key to NULL if the option is set to :nullify, and do not touch the
- # child records if the option is set to :restrict.
+ # See HasManyAssociation#delete_records for more information. In general
+ # - delete children if the option is set to :destroy or :delete_all
+ # - set the foreign key to NULL if the option is set to :nullify
+ # - do not delete the parent record if there is any child record if the
+ # option is set to :restrict
#
# The +extra_conditions+ parameter, which is not used within the main
# Active Record codebase, is meant to allow plugins to define extra
@@ -1761,7 +1849,7 @@ module ActiveRecord
def graft(*associations)
associations.each do |association|
join_associations.detect {|a| association == a} ||
- build(association.reflection.name, association.find_parent_in(self), association.join_class)
+ build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
end
self
end
@@ -1801,9 +1889,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
+ remove_uniq_by_reflection(reflection, records)
when Array
associations.each do |association|
remove_duplicate_results!(base, records, association)
@@ -1811,6 +1897,7 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
+ remove_uniq_by_reflection(reflection, records)
parent_records = []
records.each do |record|
@@ -1829,6 +1916,7 @@ module ActiveRecord
end
protected
+
def build(associations, parent = nil, join_class = Arel::InnerJoin)
parent ||= @joins.last
case associations
@@ -1851,6 +1939,12 @@ module ActiveRecord
end
end
+ def remove_uniq_by_reflection(reflection, records)
+ if reflection && reflection.collection?
+ records.each { |record| record.send(reflection.name).target.uniq! }
+ end
+ end
+
def build_join_association(reflection, parent)
JoinAssociation.new(reflection, self, parent)
end
@@ -1965,7 +2059,7 @@ module ActiveRecord
end
class JoinAssociation < JoinBase # :nodoc:
- attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
def initialize(reflection, join_dependency, parent = nil)
@@ -1982,6 +2076,7 @@ module ActiveRecord
@parent_table_name = parent.active_record.table_name
@aliased_table_name = aliased_table_name_for(table_name)
@join = nil
+ @join_class = Arel::InnerJoin
if reflection.macro == :has_and_belongs_to_many
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
@@ -2004,10 +2099,6 @@ module ActiveRecord
end
end
- def join_class
- @join_class ||= Arel::InnerJoin
- end
-
def with_join_class(join_class)
@join_class = join_class
self
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 615b7d2719..b5159eead3 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -183,10 +183,13 @@ module ActiveRecord
# descendant's +construct_sql+ method will have set :counter_sql automatically.
# Otherwise, construct options and pass them with scope to the target class's +count+.
def count(column_name = nil, options = {})
- if @reflection.options[:counter_sql]
+ column_name, options = nil, column_name if column_name.is_a?(Hash)
+
+ if @reflection.options[:counter_sql] && !options.blank?
+ raise ArgumentError, "If finder_sql/counter_sql is used then options cannot be passed"
+ elsif @reflection.options[:counter_sql]
@reflection.klass.count_by_sql(@counter_sql)
else
- column_name, options = nil, column_name if column_name.is_a?(Hash)
if @reflection.options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
@@ -215,9 +218,9 @@ module ActiveRecord
# are actually removed from the database, that depends precisely on
# +delete_records+. They are in any case removed from the collection.
def delete(*records)
- remove_records(records) do |records, old_records|
+ remove_records(records) do |_records, old_records|
delete_records(old_records) if old_records.any?
- records.each { |record| @target.delete(record) }
+ _records.each { |record| @target.delete(record) }
end
end
@@ -228,7 +231,7 @@ module ActiveRecord
# ignoring the +:dependent+ option.
def destroy(*records)
records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)}
- remove_records(records) do |records, old_records|
+ remove_records(records) do |_records, old_records|
old_records.each { |record| record.destroy }
end
@@ -393,11 +396,12 @@ module ActiveRecord
if @target.is_a?(Array) && @target.any?
@target = find_target.map do |f|
i = @target.index(f)
- t = @target.delete_at(i) if i
- if t && t.changed?
- t
+ if i
+ @target.delete_at(i).tap do |t|
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
+ t.attributes = f.attributes.except(*keys)
+ end
else
- f.mark_for_destruction if t && t.marked_for_destruction?
f
end
end + @target
@@ -415,15 +419,10 @@ module ActiveRecord
end
def method_missing(method, *args)
- case method.to_s
- when 'find_or_create'
- return find(:first, :conditions => args.first) || create(args.first)
- when /^find_or_create_by_(.*)$/
- rest = $1
- return send("find_by_#{rest}", *args) ||
- method_missing("create_by_#{rest}", *args)
- when /^create_by_(.*)$/
- return create Hash[$1.split('_and_').zip(args)]
+ match = DynamicFinderMatch.match(method)
+ if match && match.creator?
+ attributes = match.attribute_names
+ return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
end
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
@@ -479,7 +478,11 @@ module ActiveRecord
callback(:before_add, record)
yield(record) if block_given?
@target ||= [] unless loaded?
- @target << record unless @reflection.options[:uniq] && @target.include?(record)
+ if index = @target.index(record)
+ @target[index] = record
+ else
+ @target << record
+ end
callback(:after_add, record)
set_inverse_instance(record, @owner)
record
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index c2a6495db5..4558872a2b 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
else
raise_on_type_mismatch(record)
- if counter_cache_name && !@owner.new_record?
+ if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name]
@reflection.klass.increment_counter(counter_cache_name, record.id)
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
end
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index c989c3536d..bec123e7a2 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -45,17 +45,23 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = Arel::Table.new(@reflection.options[:join_table])
+ relation = Arel::Table.new(@reflection.options[:join_table])
+ timestamps = record_timestamp_columns(record)
+ timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
+
attributes = columns.inject({}) do |attrs, column|
- case column.name.to_s
+ name = column.name
+ case name.to_s
when @reflection.primary_key_name.to_s
- attrs[relation[column.name]] = owner_quoted_id
+ attrs[relation[name]] = @owner.id
when @reflection.association_foreign_key.to_s
- attrs[relation[column.name]] = record.quoted_id
+ attrs[relation[name]] = record.id
+ when *timestamps
+ attrs[relation[name]] = timezone
else
- if record.has_attribute?(column.name)
- value = @owner.send(:quote_value, record[column.name], column)
- attrs[relation[column.name]] = value unless value.nil?
+ if record.has_attribute?(name)
+ value = @owner.send(:quote_value, record[name], column)
+ attrs[relation[name]] = value unless value.nil?
end
end
attrs
@@ -100,9 +106,10 @@ module ActiveRecord
:limit => @reflection.options[:limit] } }
end
- # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
- # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
- # an id column. This will then overwrite the id column of the records coming back.
+ # Join tables with additional columns on top of the two foreign keys must be considered
+ # ambiguous unless a select clause has been explicitly defined. Otherwise you can get
+ # broken records back, if, for example, the join column also has an id column. This will
+ # then overwrite the id column of the records coming back.
def finding_with_ambiguous_select?(select_clause)
!select_clause && columns.size != 2
end
@@ -117,6 +124,14 @@ module ActiveRecord
build_record(attributes, &block)
end
end
+
+ def record_timestamp_columns(record)
+ if record.record_timestamps
+ record.send(:all_timestamp_attributes).map(&:to_s)
+ else
+ []
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index d74fb7c702..c33bc6aa47 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -24,7 +24,7 @@ module ActiveRecord
# If the association has a counter cache it gets that value. Otherwise
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
# there's one. Some configuration options like :group make it impossible
- # to do a SQL count, in those cases the array count will be used.
+ # to do an SQL count, in those cases the array count will be used.
#
# That does not depend on whether the collection has already been loaded
# or not. The +size+ method is the one that takes the loaded flag into
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 17f850756f..608b1c741a 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -24,9 +24,10 @@ module ActiveRecord
end
end
- # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
- # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero,
- # and you need to fetch that collection afterwards, it'll take one fewer SELECT query if you use #length.
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been
+ # loaded and calling collection.size if it has. If it's more likely than not that the collection does
+ # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer
+ # SELECT query if you use #length.
def size
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
return @target.size if loaded?
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
index 22e1033a9d..cabb33c4a8 100644
--- a/activerecord/lib/active_record/associations/through_association_scope.rb
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -35,7 +35,7 @@ module ActiveRecord
@owner.class.base_class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
elsif reflection.macro == :belongs_to
- { reflection.klass.primary_key => @owner[reflection.primary_key_name] }
+ { reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
else
{ reflection.primary_key_name => owner_quoted_id }
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 783d61383b..8f0aacba42 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -14,7 +14,8 @@ module ActiveRecord
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.
+ # 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, line = <<-EOV, __LINE__ + 1
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index e31acac050..7a2de3bf80 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -14,8 +14,8 @@ module ActiveRecord
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+.
+ # 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'
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 7517896235..2c7afe3c9f 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -4,14 +4,13 @@ module ActiveRecord
# = Active Record Autosave Association
#
# AutosaveAssociation is a module that takes care of automatically saving
- # your associations when the parent is saved. In addition to saving, it
- # also destroys any associations that were marked for destruction.
+ # associacted records when parent is saved. In addition to saving, it
+ # also destroys any associated records that were marked for destruction.
# (See mark_for_destruction and marked_for_destruction?)
#
# Saving of the parent, its associations, and the destruction of marked
# associations, all happen inside 1 transaction. This should never leave the
- # database in an inconsistent state after, for instance, mass assigning
- # attributes and saving them.
+ # database in an inconsistent state.
#
# If validations for any of the associations fail, their error messages will
# be applied to the parent.
@@ -21,8 +20,6 @@ module ActiveRecord
#
# === One-to-one Example
#
- # Consider a Post model with one Author:
- #
# class Post
# has_one :author, :autosave => true
# end
@@ -155,11 +152,12 @@ module ActiveRecord
CODE
end
- # Adds a validate and save callback for the association as specified by
+ # Adds validation and save callbacks for the association as specified by
# the +reflection+.
#
- # For performance reasons, we don't check whether to validate at runtime,
- # but instead only define the method and callback when needed. However,
+ # For performance reasons, we don't check whether to validate at runtime.
+ # However the validation and callback methods are lazy and those methods
+ # get created when they are invoked for the very first time. However,
# this can change, for instance, when using nested attributes, which is
# called _after_ the association has been defined. Since we don't want
# the callbacks to get defined multiple times, there are guards that
@@ -197,14 +195,15 @@ module ActiveRecord
end
end
- # Reloads the attributes of the object as usual and removes a mark for destruction.
+ # Reloads the attributes of the object as usual and clears <tt>marked_for_destruction</tt> flag.
def reload(options = nil)
@marked_for_destruction = false
super
end
# Marks this record to be destroyed as part of the parents save transaction.
- # This does _not_ actually destroy the record yet, rather it will be destroyed when <tt>parent.save</tt> is called.
+ # This does _not_ actually destroy the record instantly, rather child record will be destroyed
+ # when <tt>parent.save</tt> is called.
#
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
def mark_for_destruction
@@ -249,7 +248,7 @@ module ActiveRecord
end
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
- # turned on for the association specified by +reflection+.
+ # turned on for the association.
def validate_single_association(reflection)
if (association = association_instance_get(reflection.name)) && !association.target.nil?
association_valid?(reflection, association)
@@ -357,14 +356,9 @@ module ActiveRecord
end
end
- # Saves the associated record if it's new or <tt>:autosave</tt> is enabled
- # on the association.
+ # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
#
- # In addition, it will destroy the association if it was marked for
- # destruction with mark_for_destruction.
- #
- # This all happens inside a transaction, _if_ the Transactions module is included into
- # ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
+ # In addition, it will destroy the association if it was marked for destruction.
def save_belongs_to_association(reflection)
if (association = association_instance_get(reflection.name)) && !association.destroyed?
autosave = reflection.options[:autosave]
@@ -377,10 +371,6 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
self[reflection.primary_key_name] = association_id
- # TODO: Removing this code doesn't seem to matter...
- if reflection.options[:polymorphic]
- self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s
- end
end
saved if autosave
@@ -388,4 +378,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index c78060c956..8da4fbcba7 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -26,17 +26,19 @@ require 'active_record/log_subscriber'
module ActiveRecord #:nodoc:
# = Active Record
#
- # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
- # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
- # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
+ # Active Record objects don't specify their attributes directly, but rather infer them from
+ # the table definition with which they're linked. Adding, removing, and changing attributes
+ # and their type is done directly in the database. Any change is instantly reflected in the
+ # Active Record objects. The mapping that binds a given Active Record class to a certain
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
#
# See the mapping rules in table_name and the full example in link:files/README.html for more insight.
#
# == Creation
#
- # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
- # you're receiving the data from somewhere else, like an HTTP request. It works like this:
+ # Active Records accept constructor parameters either in a hash or as a block. The hash
+ # method is especially useful when you're receiving the data from somewhere else, like an
+ # HTTP request. It works like this:
#
# user = User.new(:name => "David", :occupation => "Code Artist")
# user.name # => "David"
@@ -75,14 +77,17 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
- # attacks if the <tt>user_name</tt> and +password+ parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
- # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
- # which will ensure that an attacker can't escape the query and fake the login (or worse).
+ # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
+ # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
+ # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
+ # before inserting them in the query, which will ensure that an attacker can't escape the
+ # query and fake the login (or worse).
#
- # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
- # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
- # the question marks with symbols and supplying a hash with values for the matching symbol keys:
+ # When using multiple parameters in the conditions, it can easily become hard to read exactly
+ # what the fourth or fifth question mark is supposed to represent. In those cases, you can
+ # resort to named bind variables instead. That's done by replacing the question marks with
+ # symbols and supplying a hash with values for the matching symbol keys:
#
# Company.where(
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
@@ -103,18 +108,19 @@ module ActiveRecord #:nodoc:
#
# Student.where(:grade => [9,11,12])
#
- # When joining tables, nested hashes or keys written in the form 'table_name.column_name' can be used to qualify the table name of a
- # particular condition. For instance:
+ # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
+ # can be used to qualify the table name of a particular condition. For instance:
#
# Student.joins(:schools).where(:schools => { :type => 'public' })
# Student.joins(:schools).where('schools.type' => 'public' )
#
# == Overwriting default accessors
#
- # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
- # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
- # name as the attribute) and calling <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually change things.
- # Example:
+ # All column values are automatically available through basic accessors on the Active Record
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
+ # the default accessors (using the same name as the attribute) and calling
+ # <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
+ # change things.
#
# class Song < ActiveRecord::Base
# # Uses an integer of seconds to hold the length of the song
@@ -128,8 +134,8 @@ module ActiveRecord #:nodoc:
# end
# end
#
- # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt> instead of <tt>write_attribute(:attribute, value)</tt> and
- # <tt>read_attribute(:attribute)</tt> as a shorter form.
+ # You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
+ # instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
#
# == Attribute query methods
#
@@ -147,34 +153,43 @@ module ActiveRecord #:nodoc:
#
# == Accessing attributes before they have been typecasted
#
- # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
- # That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
- # has a <tt>balance</tt> attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined
+ # typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
+ # accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
+ # you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
#
- # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
- # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
- # want.
+ # This is especially useful in validation situations where the user might supply a string for an
+ # integer field and you want to display the original string back in an error message. Accessing the
+ # attribute normally would typecast the string to 0, which isn't what you want.
#
# == Dynamic attribute-based finders
#
- # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
- # appending the name of an attribute to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
- # <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
+ # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
+ # by simple queries without turning to SQL. They work by appending the name of an attribute
+ # to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
+ # like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
+ # <tt>Payment.find_by_transaction_id</tt>. Instead of writing
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
- # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
+ # And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
+ # <tt>Person.find_all_by_last_name(last_name)</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
- # <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
- # <tt>Person.where(:user_name => user_name, :password => password).first</tt>, you just do
- # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
+ # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ #
+ # Person.where(:user_name => user_name, :password => password).first
+ # Person.find_by_user_name_and_password #with dynamic finder
+ #
+ # Person.where(:user_name => user_name, :password => password, :gender => 'male').first
+ # Payment.find_by_user_name_and_password_and_gender
#
- # It's even possible to call these dynamic finder methods on relations and named scopes. For example :
+ # It's even possible to call these dynamic finder methods on relations and named scopes.
#
# Payment.order("created_on").find_all_by_amount(50)
# Payment.pending.find_last_by_amount(100)
#
- # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
- # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
+ # The same dynamic finder style can be used to create the object if it doesn't already exist.
+ # This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
+ # it already exists and otherwise creates it, then returns it. Protected attributes won't be set
+ # unless they are given in a block.
#
# # No 'Summer' tag exists
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
@@ -185,23 +200,33 @@ module ActiveRecord #:nodoc:
# # Now 'Bob' exist and is an 'admin'
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
#
- # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be set unless they are given in a block. For example:
+ # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
+ # saving it first. Protected attributes won't be set unless they are given in a block.
#
# # No 'Winter' tag exists
# winter = Tag.find_or_initialize_by_name("Winter")
# winter.new_record? # true
#
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
- # a list of parameters. For example:
+ # a list of parameters.
#
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
#
- # That will either find an existing tag named "rails", or create a new one while setting the user that created it.
+ # That will either find an existing tag named "rails", or create a new one while setting the
+ # user that created it.
+ #
+ # Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
+ # using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
+ # but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
+ #
+ # User.scoped_by_user_name('David')
#
# == Saving arrays, hashes, and other non-mappable objects in text columns
#
- # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
- # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
+ # Active Record can serialize any object in text columns using YAML. To do so, you must
+ # specify this with a call to the class method +serialize+.
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
+ # any additional work.
#
# class User < ActiveRecord::Base
# serialize :preferences
@@ -210,8 +235,8 @@ module ActiveRecord #:nodoc:
# user = User.create(:preferences => { "background" => "black", "display" => large })
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
#
- # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
- # descendant of a class not in the hierarchy. Example:
+ # You can also specify a class option as the second parameter that'll raise an exception
+ # if a serialized object is retrieved as a descendant of a class not in the hierarchy.
#
# class User < ActiveRecord::Base
# serialize :preferences, Hash
@@ -222,52 +247,63 @@ module ActiveRecord #:nodoc:
#
# == Single table inheritance
#
- # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed
- # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
+ # Active Record allows inheritance by storing the name of the class in a column that by
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
+ # This means that an inheritance looking like this:
#
# class Company < ActiveRecord::Base; end
# class Firm < Company; end
# class Client < Company; end
# class PriorityClient < Client; end
#
- # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in the companies table with type = "Firm". You can then
- # fetch this row again using <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
+ # When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
+ # the companies table with type = "Firm". You can then fetch this row again using
+ # <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
#
- # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
- # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
+ # If you don't have a type column defined in your table, single-table inheritance won't
+ # be triggered. In that case, it'll work just like normal subclasses with no special magic
+ # for differentiating between them or reloading the right type with find.
#
# Note, all the attributes for all the cases are kept in the same table. Read more:
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
#
# == Connection to multiple databases in different models
#
- # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
- # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
- # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
+ # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
+ # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
+ # connection. But you can also set a class-specific connection. For example, if Course is an
+ # ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
# and Course and all of its subclasses will use this connection instead.
#
- # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
- # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
+ # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
+ # a Hash indexed by the class. If a connection is requested, the retrieve_connection method
+ # will go up the class-hierarchy until a connection is found in the connection pool.
#
# == Exceptions
#
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
# <tt>:adapter</tt> key.
- # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
+ # non-existent adapter
# (or a bad spelling of an existing one).
- # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
+ # specified in the association definition.
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
- # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt> before querying.
+ # * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
+ # before querying.
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
# nothing was found, please check its documentation for further details.
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
- # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of AttributeAssignmentError
+ # <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
+ # AttributeAssignmentError
# objects that should be inspected to determine which attributes triggered the errors.
- # * AttributeAssignmentError - An error occurred while doing a mass assignment through the <tt>attributes=</tt> method.
- # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
+ # <tt>attributes=</tt> method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
+ # triggered the error.
#
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
@@ -275,8 +311,9 @@ module ActiveRecord #:nodoc:
class Base
##
# :singleton-method:
- # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
- # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
+ # which is then passed on to any new database connections made and which can be retrieved on both
+ # a class and instance level by calling +logger+.
cattr_accessor :logger, :instance_writer => false
class << self
@@ -323,21 +360,24 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
- # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
- # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # Accessor for the prefix type that will be prepended to every primary key column name.
+ # The options are :table_name and :table_name_with_underscore. If the first is specified,
+ # the Product class will look for "productid" instead of "id" as the primary column. If the
+ # latter is specified, the Product class will look for "product_id" instead of "id". Remember
# that this is a global setting for all Active Records.
cattr_accessor :primary_key_prefix_type, :instance_writer => false
@@primary_key_prefix_type = nil
##
# :singleton-method:
- # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
- # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
- # for tables in a shared database. By default, the prefix is the empty string.
+ # Accessor for the name of the prefix string to prepend to every table name. So if set
+ # to "basecamp_", all table names will be named like "basecamp_projects", "basecamp_people",
+ # etc. This is a convenient way of creating a namespace for tables in a shared database.
+ # By default, the prefix is the empty string.
#
- # If you are organising your models within modules you can add a prefix to the models within a namespace by defining
- # a singleton method in the parent module called table_name_prefix which returns your chosen prefix.
+ # If you are organising your models within modules you can add a prefix to the models within
+ # a namespace by defining a singleton method in the parent module called table_name_prefix which
+ # returns your chosen prefix.
class_attribute :table_name_prefix, :instance_writer => false
self.table_name_prefix = ""
@@ -358,8 +398,8 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
- # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
- # This is set to :local by default.
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
+ # dates and times from the database. This is set to :local by default.
cattr_accessor :default_timezone, :instance_writer => false
@@default_timezone = :local
@@ -398,7 +438,7 @@ module ActiveRecord #:nodoc:
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :reorder, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
@@ -476,7 +516,8 @@ module ActiveRecord #:nodoc:
connection.select_value(sql, "#{name} Count").to_i
end
- # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
+ # Attributes listed as readonly will be used to create a new record but update operations will
+ # ignore these fields.
def attr_readonly(*attributes)
write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
end
@@ -505,15 +546,18 @@ module ActiveRecord #:nodoc:
serialized_attributes[attr_name.to_s] = class_name
end
- # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
+ # Returns a hash of all the attributes that have been specified for serialization as
+ # keys and their class restriction as values.
def serialized_attributes
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
end
- # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
- # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
- # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
- # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
+ # Guesses the table name (in forced lower-case) based on the name of the class in the
+ # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy
+ # looks like: Reply < Message < ActiveRecord::Base, then Message is used
+ # to guess the table name even when called on Reply. The rules used to do the guess
+ # are handled by the Inflector class in Active Support, which knows almost all common
+ # English inflections. You can add new inflections in config/initializers/inflections.rb.
#
# Nested classes are given table names prefixed by the singular form of
# the parent's table name. Enclosing modules are not considered.
@@ -561,8 +605,8 @@ module ActiveRecord #:nodoc:
(parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
end
- # Defines the column name for use with single table inheritance
- # -- can be set in subclasses like so: self.inheritance_column = "type_id"
+ # Defines the column name for use with single table inheritance. Use
+ # <tt>set_inheritance_column</tt> to set a different value.
def inheritance_column
@inheritance_column ||= "type".freeze
end
@@ -579,8 +623,8 @@ module ActiveRecord #:nodoc:
default
end
- # Sets the table name to use to the given value, or (if the value
- # is nil or false) to the value returned by the given block.
+ # Sets the table name. If the value is nil or false then the value returned by the given
+ # block is used.
#
# class Project < ActiveRecord::Base
# set_table_name "project"
@@ -803,7 +847,7 @@ module ActiveRecord #:nodoc:
end
def arel_table
- @arel_table ||= Arel::Table.new(table_name, :engine => arel_engine)
+ @arel_table ||= Arel::Table.new(table_name, arel_engine)
end
def arel_engine
@@ -923,15 +967,15 @@ module ActiveRecord #:nodoc:
end
end
- # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
- # that are turned into <tt>where(:user_name => user_name).first</tt> and <tt>where(:user_name => user_name, :password => :password).first</tt>
- # respectively. Also works for <tt>all</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>where(:amount => 50).all</tt>.
+ # Enables dynamic finders like <tt>User.find_by_user_name(user_name)</tt> and
+ # <tt>User.scoped_by_user_name(user_name). Refer to Dynamic attribute-based finders
+ # section at the top of this file for more detailed information.
#
- # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
- # is actually <tt>find_all_by_amount(amount, options)</tt>.
+ # It's even possible to use all the additional parameters to +find+. For example, the
+ # full interface for +find_all_by_amount+ is actually <tt>find_all_by_amount(amount, options)</tt>.
#
- # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
- # attempts to use it do not run through method_missing.
+ # Each dynamic finder using <tt>scoped_by_*</tt> is also defined in the class after it
+ # is first invoked, so that future attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
if match = DynamicFinderMatch.match(method_id)
attribute_names = match.attribute_names
@@ -991,8 +1035,8 @@ module ActiveRecord #:nodoc:
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> parameter is <tt>Relation</tt> while
+ # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
+ # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
# <tt>:create</tt> parameters are an attributes hash.
#
# class Article < ActiveRecord::Base
@@ -1030,15 +1074,14 @@ module ActiveRecord #:nodoc:
# class Article < ActiveRecord::Base
# def self.find_with_exclusive_scope
# with_scope(:find => where(:blog_id => 1).limit(1)) do
- # with_exclusive_scope(:find => limit(10))
+ # with_exclusive_scope(:find => limit(10)) do
# all # => SELECT * from articles LIMIT 10
# end
# end
# end
# end
#
- # *Note*: the +:find+ scope also has effect on update and deletion methods,
- # like +update_all+ and +delete_all+.
+ # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
@@ -1255,6 +1298,8 @@ MSG
replace_named_bind_variables(statement, values.first)
elsif statement.include?('?')
replace_bind_variables(statement, values)
+ elsif statement.blank?
+ statement
else
statement % values.collect { |value| connection.quote_string(value.to_s) }
end
@@ -1355,7 +1400,7 @@ MSG
# as it copies the object's attributes only, not its associations. The extent of a "deep" clone is
# application specific and is therefore left to the application to implement according to its need.
def initialize_copy(other)
- callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
+ _run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
@@ -1471,7 +1516,7 @@ MSG
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
- return unless new_attributes.is_a? Hash
+ return unless new_attributes.is_a?(Hash)
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
@@ -1605,10 +1650,11 @@ MSG
private
- # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
- # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
- # set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
- # Message class in that example.
+ # Sets the attribute used for single table inheritance to this class name if this is not the
+ # ActiveRecord::Base descendant.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to
+ # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself.
+ # No such attribute would be set for objects of the Message class in that example.
def ensure_proper_type
unless self.class.descends_from_active_record?
write_attribute(self.class.inheritance_column, self.class.sti_name)
@@ -1657,8 +1703,9 @@ MSG
# by calling new on the column type or aggregation type (through composed_of) object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
- # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
- # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
+ # f for Float, s for String, and a for Array. If all the values for a given attribute are empty, the
+ # attribute will be set to nil.
def assign_multiparameter_attributes(pairs)
execute_callstack_for_multiparameter_attributes(
extract_callstack_for_multiparameter_attributes(pairs)
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 637dac450b..aa92bf999f 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -26,8 +26,8 @@ module ActiveRecord
# <tt>after_rollback</tt>.
#
# That's a total of ten callbacks, which gives you immense power to react and prepare for each state in the
- # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each
- # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
+ # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar,
+ # except that each <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback.
#
# Examples:
# class CreditCard < ActiveRecord::Base
@@ -55,9 +55,9 @@ module ActiveRecord
#
# == Inheritable callback queues
#
- # Besides the overwritable callback methods, it's also possible to register callbacks through the use of the callback macros.
- # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
- # hierarchy. Example:
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the
+ # use of the callback macros. Their main advantage is that the macros add behavior into a callback
+ # queue that is kept intact down through an inheritance hierarchy.
#
# class Topic < ActiveRecord::Base
# before_destroy :destroy_author
@@ -67,9 +67,9 @@ module ActiveRecord
# before_destroy :destroy_readers
# end
#
- # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is run, both +destroy_author+ and
- # +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable
- # methods:
+ # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
+ # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
+ # where the +before_destroy+ methis is overriden:
#
# class Topic < ActiveRecord::Base
# def before_destroy() destroy_author end
@@ -79,20 +79,21 @@ module ActiveRecord
# def before_destroy() destroy_readers end
# end
#
- # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
- # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
- # when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
+ # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
+ # So, use the callback macros when you want to ensure that a certain callback is called for the entire
+ # hierarchy, and use the regular overwriteable methods when you want to leave it up to each descendant
+ # to decide whether they want to call +super+ and trigger the inherited callbacks.
#
- # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
- # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
- # be inherited.
+ # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
+ # callbacks before specifying the associations. Otherwise, you might trigger the loading of a
+ # child before the parent has registered the callbacks and they won't be inherited.
#
# == Types of callbacks
#
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
- # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
- # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
- # eval methods are deprecated.
+ # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
+ # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
+ # creating mix-ins), and inline eval methods are deprecated.
#
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
#
@@ -169,15 +170,15 @@ module ActiveRecord
# end
# end
#
- # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
- # which will then be evaluated within the binding of the callback. Example:
+ # The callback macros usually accept a symbol for the method they're supposed to run, but you can also
+ # pass a "method string", which will then be evaluated within the binding of the callback. Example:
#
# class Topic < ActiveRecord::Base
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
# end
#
- # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback is triggered. Also note that these
- # inline callbacks can be stacked just like the regular ones:
+ # Notice that single quotes (') are used so the <tt>#{id}</tt> part isn't evaluated until the callback
+ # is triggered. Also note that these inline callbacks can be stacked just like the regular ones:
#
# class Topic < ActiveRecord::Base
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
@@ -186,22 +187,24 @@ module ActiveRecord
#
# == The +after_find+ and +after_initialize+ exceptions
#
- # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder, such as <tt>Base.find(:all)</tt>, we've had
- # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and
- # +after_initialize+ will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
+ # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder,
+ # such as <tt>Base.find(:all)</tt>, we've had to implement a simple performance constraint (50% more speed
+ # on a simple test case). Unlike all the other callbacks, +after_find+ and +after_initialize+ will only be
+ # run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
# callback types will be called.
#
# == <tt>before_validation*</tt> returning statements
#
- # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and <tt>Base#save</tt> will return +false+.
- # If Base#save! is called it will raise a ActiveRecord::RecordInvalid exception.
- # Nothing will be appended to the errors object.
+ # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be
+ # aborted and <tt>Base#save</tt> will return +false+. If Base#save! is called it will raise a
+ # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object.
#
# == Canceling callbacks
#
- # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are cancelled. If an <tt>after_*</tt> callback returns
- # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
- # defined as methods on the model, which are called last.
+ # If a <tt>before_*</tt> callback returns +false+, all the later callbacks and the associated action are
+ # cancelled. If an <tt>after_*</tt> callback returns +false+, all the later callbacks are cancelled.
+ # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
+ # methods on the model, which are called last.
#
# == Transactions
#
@@ -217,7 +220,8 @@ module ActiveRecord
#
# == Debugging callbacks
#
- # To list the methods and procs registered with a particular callback, append <tt>_callback_chain</tt> to the callback name that you wish to list and send that to your class from the Rails console:
+ # To list the methods and procs registered with a particular callback, append <tt>_callback_chain</tt> to
+ # the callback name that you wish to list and send that to your class from the Rails console:
#
# >> Topic.after_save_callback_chain
# => [#<ActiveSupport::Callbacks::Callback:0x3f6a448
@@ -228,7 +232,7 @@ module ActiveRecord
extend ActiveSupport::Concern
CALLBACKS = [
- :after_initialize, :after_find, :before_validation, :after_validation,
+ :after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy
@@ -238,7 +242,7 @@ module ActiveRecord
extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
- define_model_callbacks :initialize, :find, :only => :after
+ define_model_callbacks :initialize, :find, :touch, :only => :after
define_model_callbacks :save, :create, :update, :destroy
end
@@ -256,6 +260,10 @@ module ActiveRecord
_run_destroy_callbacks { super }
end
+ def touch(*) #:nodoc:
+ _run_touch_callbacks { super }
+ end
+
def deprecated_callback_method(symbol) #:nodoc:
if respond_to?(symbol, true)
ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index c2d79a421d..02a8f4e214 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -103,8 +103,8 @@ module ActiveRecord
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
- def release_connection
- conn = @reserved_connections.delete(current_connection_id)
+ def release_connection(with_id = current_connection_id)
+ conn = @reserved_connections.delete(with_id)
checkin conn if conn
end
@@ -112,10 +112,11 @@ module ActiveRecord
# exists checkout a connection, yield it to the block, and checkin the
# connection when finished.
def with_connection
- fresh_connection = true unless @reserved_connections[current_connection_id]
+ connection_id = current_connection_id
+ fresh_connection = true unless @reserved_connections[connection_id]
yield connection
ensure
- release_connection if fresh_connection
+ release_connection(connection_id) if fresh_connection
end
# Returns true if a connection has already been opened.
@@ -161,8 +162,13 @@ module ActiveRecord
# Return any checked-out connections back to the pool by threads that
# are no longer alive.
def clear_stale_cached_connections!
- remove_stale_cached_threads!(@reserved_connections) do |name, conn|
- checkin conn
+ keys = @reserved_connections.keys - Thread.list.find_all { |t|
+ t.alive?
+ }.map { |thread| thread.object_id }
+
+ keys.each do |key|
+ checkin @reserved_connections[key]
+ @reserved_connections.delete(key)
end
end
@@ -232,20 +238,6 @@ module ActiveRecord
Thread.current.object_id
end
- # Remove stale threads from the cache.
- def remove_stale_cached_threads!(cache, &block)
- keys = Set.new(cache.keys)
-
- Thread.list.each do |thread|
- keys.delete(thread.object_id) if thread.alive?
- end
- keys.each do |key|
- next unless cache.has_key?(key)
- block.call(key, cache[key])
- cache.delete(key)
- end
- end
-
def checkout_new_connection
c = new_connection
@connections << c
@@ -290,14 +282,12 @@ module ActiveRecord
# ActiveRecord::Base.connection_handler. Active Record models use this to
# determine that connection pool that they should use.
class ConnectionHandler
+ attr_reader :connection_pools
+
def initialize(pools = {})
@connection_pools = pools
end
- def connection_pools
- @connection_pools ||= {}
- end
-
def establish_connection(name, spec)
@connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
end
@@ -345,9 +335,11 @@ module ActiveRecord
# re-establishing the connection.
def remove_connection(klass)
pool = @connection_pools[klass.name]
+ return nil unless pool
+
@connection_pools.delete_if { |key, value| value == pool }
- pool.disconnect! if pool
- pool.spec.config if pool
+ pool.disconnect!
+ pool.spec.config
end
def retrieve_connection_pool(klass)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index 23c42d670b..8e74eff0ab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -66,15 +66,9 @@ module ActiveRecord
unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
begin
- require 'rubygems'
- gem "activerecord-#{spec[:adapter]}-adapter"
require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
rescue LoadError
- begin
- require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
- rescue LoadError
- raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
- end
+ raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
end
adapter_method = "#{spec[:adapter]}_connection"
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 4118ea7b31..a130c330dd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -42,7 +42,7 @@ module ActiveRecord
65535
end
- # the maximum length of a SQL query
+ # the maximum length of an SQL query
def sql_query_length
1048575
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index d7b5bf8e31..e2b3773a99 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -30,7 +30,7 @@ module ActiveRecord
if value.acts_like?(:date) || value.acts_like?(:time)
"'#{quoted_date(value)}'"
else
- "'#{quote_string(value.to_yaml)}'"
+ "'#{quote_string(value.to_s)}'"
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7691b6a788..9118ceb33c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -23,7 +23,8 @@ module ActiveRecord
#
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
- # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in <tt>company_name varchar(60)</tt>.
+ # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
+ # <tt>company_name varchar(60)</tt>.
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values.
def initialize(name, default, sql_type = nil, null = true)
@@ -359,7 +360,8 @@ module ActiveRecord
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
- # Requests a maximum column length. This is number of characters for <tt>:string</tt> and <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
+ # Requests a maximum column length. This is number of characters for <tt>:string</tt> and
+ # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -462,8 +464,8 @@ module ActiveRecord
# TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
- # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of options, these will be
- # used when creating the <tt>_type</tt> column. So what can be written like this:
+ # column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
+ # options, these will be used when creating the <tt>_type</tt> column. So what can be written like this:
#
# create_table :taggings do |t|
# t.integer :tag_id, :tagger_id, :taggable_id
@@ -535,7 +537,7 @@ module ActiveRecord
end
end
- # Represents a SQL table in an abstract way for updating a table.
+ # Represents an SQL table in an abstract way for updating a table.
# Also see TableDefinition and SchemaStatements#create_table
#
# Available transformations are:
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index ffc3847a31..7dee68502f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -327,6 +327,8 @@ module ActiveRecord
#
# Note: SQLite doesn't support index length
def add_index(table_name, column_name, options = {})
+ options[:name] = options[:name].to_s if options.key?(:name)
+
column_names = Array.wrap(column_name)
index_name = index_name(table_name, :column => column_names)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index be8d1bd76b..d8c92d0ad3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -36,14 +36,12 @@ module ActiveRecord
define_callbacks :checkout, :checkin
- @@row_even = true
-
def initialize(connection, logger = nil) #:nodoc:
@active = nil
@connection, @logger = connection, logger
- @runtime = 0
@query_cache_enabled = false
@query_cache = {}
+ @instrumenter = ActiveSupport::Notifications.instrumenter
end
# Returns the human-readable name of the adapter. Use mixed case - one
@@ -92,11 +90,6 @@ module ActiveRecord
false
end
- def reset_runtime #:nodoc:
- rt, @runtime = @runtime, 0
- rt
- end
-
# QUOTING ==================================================
# Override to return the quoted table name. Defaults to column quoting.
@@ -199,12 +192,10 @@ module ActiveRecord
def log(sql, name)
name ||= "SQL"
- result = nil
- ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => name, :connection_id => self.object_id) do
- @runtime += Benchmark.ms { result = yield }
+ @instrumenter.instrument("sql.active_record",
+ :sql => sql, :name => name, :connection_id => object_id) do
+ yield
end
- result
rescue Exception => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.debug message if @logger
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
new file mode 100644
index 0000000000..568759775b
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -0,0 +1,639 @@
+# encoding: utf-8
+
+require 'mysql2' unless defined? Mysql2
+
+module ActiveRecord
+ class Base
+ def self.mysql2_connection(config)
+ config[:username] = 'root' if config[:username].nil?
+ client = Mysql2::Client.new(config.symbolize_keys)
+ options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
+ ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
+ end
+ end
+
+ module ConnectionAdapters
+ class Mysql2Column < Column
+ BOOL = "tinyint(1)"
+ def extract_default(default)
+ if sql_type =~ /blob/i || type == :text
+ if default.blank?
+ return null ? nil : ''
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ def has_default?
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
+ # Returns the Ruby class that corresponds to the abstract data type.
+ def klass
+ case type
+ when :integer then Fixnum
+ when :float then Float
+ when :decimal then BigDecimal
+ when :datetime then Time
+ when :date then Date
+ when :timestamp then Time
+ when :time then Time
+ when :text, :string then String
+ when :binary then String
+ when :boolean then Object
+ end
+ end
+
+ def type_cast(value)
+ return nil if value.nil?
+ case type
+ when :string then value
+ when :text then value
+ when :integer then value.to_i rescue value ? 1 : 0
+ when :float then value.to_f # returns self if it's already a Float
+ when :decimal then self.class.value_to_decimal(value)
+ when :datetime, :timestamp then value.class == Time ? value : self.class.string_to_time(value)
+ when :time then value.class == Time ? value : self.class.string_to_dummy_time(value)
+ when :date then value.class == Date ? value : self.class.string_to_date(value)
+ when :binary then value
+ when :boolean then self.class.value_to_boolean(value)
+ else value
+ end
+ end
+
+ def type_cast_code(var_name)
+ case type
+ when :string then nil
+ when :text then nil
+ when :integer then "#{var_name}.to_i rescue #{var_name} ? 1 : 0"
+ when :float then "#{var_name}.to_f"
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
+ when :datetime, :timestamp then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_time(#{var_name})"
+ when :time then "#{var_name}.class == Time ? #{var_name} : #{self.class.name}.string_to_dummy_time(#{var_name})"
+ when :date then "#{var_name}.class == Date ? #{var_name} : #{self.class.name}.string_to_date(#{var_name})"
+ when :binary then nil
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
+ else nil
+ end
+ end
+
+ private
+ def simplified_type(field_type)
+ return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
+ return :string if field_type =~ /enum/i or field_type =~ /set/i
+ return :integer if field_type =~ /year/i
+ return :binary if field_type =~ /bit/i
+ super
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ when /^bigint/i; 8
+ when /^int/i; 4
+ when /^mediumint/i; 3
+ when /^smallint/i; 2
+ when /^tinyint/i; 1
+ else
+ super
+ end
+ end
+
+ # MySQL misreports NOT NULL column default when none is given.
+ # We can't detect this for columns which may have a legitimate ''
+ # default (string) but we can for others (integer, datetime, boolean,
+ # and the rest).
+ #
+ # Test whether the column has default '', is not null, and is not
+ # a type allowing default ''.
+ def missing_default_forged_as_empty_string?(default)
+ type != :string && !null && default == ''
+ end
+ end
+
+ class Mysql2Adapter < AbstractAdapter
+ cattr_accessor :emulate_booleans
+ self.emulate_booleans = true
+
+ ADAPTER_NAME = 'Mysql2'
+ PRIMARY = "PRIMARY"
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ NATIVE_DATABASE_TYPES = {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int", :limit => 4 },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ configure_connection
+ end
+
+ def adapter_name
+ ADAPTER_NAME
+ end
+
+ def supports_migrations?
+ true
+ end
+
+ def supports_primary_key?
+ true
+ end
+
+ def supports_savepoints?
+ true
+ end
+
+ def native_database_types
+ NATIVE_DATABASE_TYPES
+ end
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quote_string(string)
+ @connection.escape(string)
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # CONNECTION MANAGEMENT ====================================
+
+ def active?
+ return false unless @connection
+ @connection.query 'select 1'
+ true
+ rescue Mysql2::Error
+ false
+ end
+
+ def reconnect!
+ disconnect!
+ connect
+ end
+
+ # this is set to true in 2.3, but we don't want it to be
+ def requires_reloading?
+ false
+ end
+
+ def disconnect!
+ unless @connection.nil?
+ @connection.close
+ @connection = nil
+ end
+ end
+
+ def reset!
+ disconnect!
+ connect
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # FIXME: re-enable the following once a "better" query_cache solution is in core
+ #
+ # The overrides below perform much better than the originals in AbstractAdapter
+ # because we're able to take advantage of mysql2's lazy-loading capabilities
+ #
+ # # Returns a record hash with the column names as keys and column values
+ # # as values.
+ # def select_one(sql, name = nil)
+ # result = execute(sql, name)
+ # result.each(:as => :hash) do |r|
+ # return r
+ # end
+ # end
+ #
+ # # Returns a single value from a record
+ # def select_value(sql, name = nil)
+ # result = execute(sql, name)
+ # if first = result.first
+ # first.first
+ # end
+ # end
+ #
+ # # Returns an array of the values of the first column in a select:
+ # # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
+ # def select_values(sql, name = nil)
+ # execute(sql, name).map { |row| row.first }
+ # end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil)
+ execute(sql, name).to_a
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
+ # made since we established the connection
+ @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
+ if name == :skip_logging
+ @connection.query(sql)
+ else
+ log(sql, name) { @connection.query(sql) }
+ end
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ super
+ id_value || @connection.last_id
+ end
+ alias :create :insert_sql
+
+ def update_sql(sql, name = nil)
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def add_limit_offset!(sql, options)
+ limit, offset = options[:limit], options[:offset]
+ if limit && offset
+ sql << " LIMIT #{offset.to_i}, #{sanitize_limit(limit)}"
+ elsif limit
+ sql << " LIMIT #{sanitize_limit(limit)}"
+ elsif offset
+ sql << " OFFSET #{offset.to_i}"
+ end
+ sql
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).inject("") do |structure, table|
+ table.delete('Table_type')
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
+ end
+ end
+
+ def recreate_database(name, options = {})
+ drop_database(name)
+ create_database(name, options)
+ end
+
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ def create_database(name, options = {})
+ if options[:collation]
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ else
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ end
+ end
+
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil)
+ tables = []
+ execute("SHOW TABLES", name).each do |field|
+ tables << field.first
+ end
+ tables
+ end
+
+ def drop_table(table_name, options = {})
+ super(table_name, options)
+ end
+
+ def indexes(table_name, name = nil)
+ indexes = []
+ current_index = nil
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == PRIMARY # skip the primary key
+ current_index = row[:Key_name]
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [])
+ end
+
+ indexes.last.columns << row[:Column_name]
+ end
+ indexes
+ end
+
+ def columns(table_name, name = nil)
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ columns = []
+ result = execute(sql, :skip_logging)
+ result.each(:symbolize_keys => true, :as => :hash) { |field|
+ columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ }
+ columns
+ end
+
+ def create_table(table_name, options = {})
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ add_column_position!(add_column_sql, options)
+ execute(add_column_sql)
+ end
+
+ def change_column_default(table_name, column_name, default)
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ column = column_for(table_name, column_name)
+
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+
+ change_column table_name, column_name, column.sql_type, :null => null
+ end
+
+ def change_column(table_name, column_name, type, options = {})
+ column = column_for(table_name, column_name)
+
+ unless options_include_default?(options)
+ options[:default] = column.default
+ end
+
+ unless options.has_key?(:null)
+ options[:null] = column.null
+ end
+
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name)
+ options = {}
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ options[:null] = column.null
+ else
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
+ end
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ add_column_options!(rename_column_sql, options)
+ execute(rename_column_sql)
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def add_column_position!(sql, options)
+ if options[:first]
+ sql << " FIRST"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
+ end
+ end
+
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ def pk_and_sequence_for(table)
+ keys = []
+ result = execute("describe #{quote_table_name(table)}")
+ result.each(:symbolize_keys => true, :as => :hash) do |row|
+ keys << row[:Field] if row[:Key] == "PRI"
+ end
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
+ def case_sensitive_equality_operator
+ "= BINARY"
+ end
+
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
+ protected
+ def quoted_columns_for_index(column_names, options = {})
+ length = options[:length] if options.is_a?(Hash)
+
+ quoted_column_names = case length
+ when Hash
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+ when Fixnum
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+ else
+ column_names.map {|name| quote_column_name(name) }
+ end
+ end
+
+ def translate_exception(exception, message)
+ return super unless exception.respond_to?(:error_number)
+
+ case exception.error_number
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
+ private
+ def connect
+ @connection = Mysql2::Client.new(@config)
+ configure_connection
+ end
+
+ def configure_connection
+ @connection.query_options.merge!(:as => :array)
+ encoding = @config[:encoding]
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+ end
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil)
+ execute(sql, name).each(:as => :hash)
+ end
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def version
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index aa3626a37e..ba0051de05 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -125,7 +125,7 @@ module ActiveRecord
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
# as boolean. If you wish to disable this emulation (which was the default
# behavior in versions 0.13.1 and earlier) you can add the following line
- # to your environment.rb file:
+ # to your application.rb file:
#
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
cattr_accessor :emulate_booleans
@@ -278,7 +278,8 @@ module ActiveRecord
rows
end
- # Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
+ # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
+ # the Result object after you're done using it.
def execute(sql, name = nil) #:nodoc:
if name == :skip_logging
@connection.query(sql)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 2fe2ae7136..6fae899e87 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -183,10 +183,14 @@ module ActiveRecord
# * <tt>:username</tt> - Defaults to nothing.
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
- # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
- # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO <encoding></tt> call on the connection.
- # * <tt>:min_messages</tt> - An optional client min messages that is used in a <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
- # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
+ # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
+ # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
+ # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
+ # <encoding></tt> call on the connection.
+ # * <tt>:min_messages</tt> - An optional client min messages that is used in a
+ # <tt>SET client_min_messages TO <min_messages></tt> call on the connection.
+ # * <tt>:allow_concurrency</tt> - If true, use async query methods so Ruby threads don't deadlock;
+ # otherwise, use blocking query methods.
class PostgreSQLAdapter < AbstractAdapter
ADAPTER_NAME = 'PostgreSQL'.freeze
@@ -218,6 +222,9 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
+ @table_alias_length = nil
+ @postgresql_version = nil
+
connect
@local_tz = execute('SHOW TIME ZONE').first["TimeZone"]
end
@@ -308,14 +315,16 @@ module ActiveRecord
# Quotes PostgreSQL-specific data types for SQL input.
def quote(value, column = nil) #:nodoc:
- if value.kind_of?(String) && column && column.type == :binary
+ return super unless column
+
+ if value.kind_of?(String) && column.type == :binary
"'#{escape_bytea(value)}'"
- elsif value.kind_of?(String) && column && column.sql_type == 'xml'
+ elsif value.kind_of?(String) && column.sql_type == 'xml'
"xml '#{quote_string(value)}'"
- elsif value.kind_of?(Numeric) && column && column.sql_type == 'money'
+ elsif value.kind_of?(Numeric) && column.sql_type == 'money'
# Not truly string input, so doesn't require (or allow) escape string syntax.
- "'#{value.to_s}'"
- elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
+ "'#{value}'"
+ elsif value.kind_of?(String) && column.sql_type =~ /^bit/
case value
when /^[01]*$/
"B'#{value}'" # Bit-string notation
@@ -370,7 +379,7 @@ module ActiveRecord
def supports_disable_referential_integrity?() #:nodoc:
version = query("SHOW server_version")[0][0].split('.')
- (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
+ version[0].to_i >= 8 && version[1].to_i >= 1
rescue
return false
end
@@ -431,17 +440,37 @@ module ActiveRecord
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
unescape_col = []
- for j in 0...res.nfields do
- # unescape string passed BYTEA field (OID == 17)
- unescape_col << ( res.ftype(j)==17 )
+ res.nfields.times do |j|
+ unescape_col << res.ftype(j)
end
ary = []
- for i in 0...res.ntuples do
+ res.ntuples.times do |i|
ary << []
- for j in 0...res.nfields do
+ res.nfields.times do |j|
data = res.getvalue(i,j)
- data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
+ case unescape_col[j]
+
+ # unescape string passed BYTEA field (OID == 17)
+ when BYTEA_COLUMN_TYPE_OID
+ data = unescape_bytea(data) if String === data
+
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ when MONEY_COLUMN_TYPE_OID
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ case data
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ data.gsub!(/[^-\d\.]/, '')
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
+ end
+ end
ary[i] << data
end
end
@@ -828,11 +857,12 @@ module ActiveRecord
# Maps logical Rails types to PostgreSQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
return super unless type.to_s == 'integer'
+ return 'integer' unless limit
case limit
- when 1..2; 'smallint'
- when 3..4, nil; 'integer'
- when 5..8; 'bigint'
+ when 1, 2; 'smallint'
+ when 3, 4; 'integer'
+ when 5..8; 'bigint'
else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
end
@@ -889,6 +919,8 @@ module ActiveRecord
private
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
+ # The internal PostgreSQL identifier of the BYTEA data type.
+ BYTEA_COLUMN_TYPE_OID = 17 #:nodoc:
# Connects to a PostgreSQL server and sets up the adapter depending on the
# connected server's characteristics.
@@ -941,51 +973,17 @@ module ActiveRecord
# conversions that are required to be performed here instead of in PostgreSQLColumn.
def select(sql, name = nil)
fields, rows = select_raw(sql, name)
- result = []
- for row in rows
- row_hash = {}
- fields.each_with_index do |f, i|
- row_hash[f] = row[i]
- end
- result << row_hash
+ rows.map do |row|
+ Hash[*fields.zip(row).flatten]
end
- result
end
def select_raw(sql, name = nil)
res = execute(sql, name)
results = result_as_array(res)
- fields = []
- rows = []
- if res.ntuples > 0
- fields = res.fields
- results.each do |row|
- hashed_row = {}
- row.each_index do |cell_index|
- # If this is a money type column and there are any currency symbols,
- # then strip them off. Indeed it would be prettier to do this in
- # PostgreSQLColumn.string_to_decimal but would break form input
- # fields that call value_before_type_cast.
- if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- case column = row[cell_index]
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- row[cell_index] = column.gsub(/[^-\d\.]/, '')
- when /^-?\D+[\d\.]+,\d{2}$/ # (2)
- row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
- end
- end
-
- hashed_row[fields[cell_index]] = column
- end
- rows << row
- end
- end
+ fields = res.fields
res.clear
- return fields, rows
+ return fields, results
end
# Returns the list of a table's column names, data types, and default values.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 117cf447df..82ad0a3b8e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -29,8 +29,8 @@ module ActiveRecord
end
end
- # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
- # from http://rubyforge.org/projects/sqlite-ruby/).
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby
+ # drivers (available both as gems and from http://rubyforge.org/projects/sqlite-ruby/).
#
# Options:
#
@@ -190,16 +190,21 @@ module ActiveRecord
def indexes(table_name, name = nil) #:nodoc:
execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
- index = IndexDefinition.new(table_name, row['name'])
- index.unique = row['unique'].to_i != 0
- index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
- index
+ IndexDefinition.new(
+ table_name,
+ row['name'],
+ row['unique'].to_i != 0,
+ execute("PRAGMA index_info('#{row['name']}')").map { |col|
+ col['name']
+ })
end
end
def primary_key(table_name) #:nodoc:
- column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
- column ? column['name'] : nil
+ column = table_structure(table_name).find { |field|
+ field['pk'].to_i == 1
+ }
+ column && column['name']
end
def remove_index!(table_name, index_name) #:nodoc:
@@ -278,10 +283,8 @@ module ActiveRecord
def select(sql, name = nil) #:nodoc:
execute(sql, name).map do |row|
record = {}
- row.each_key do |key|
- if key.is_a?(String)
- record[key.sub(/^"?\w+"?\./, '')] = row[key]
- end
+ row.each do |key, value|
+ record[key.sub(/^"?\w+"?\./, '')] = value if key.is_a?(String)
end
record
end
@@ -378,9 +381,9 @@ module ActiveRecord
def default_primary_key_type
if supports_autoincrement?
- 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'
else
- 'INTEGER PRIMARY KEY NOT NULL'.freeze
+ 'INTEGER PRIMARY KEY NOT NULL'
end
end
diff --git a/activerecord/lib/active_record/dynamic_finder_match.rb b/activerecord/lib/active_record/dynamic_finder_match.rb
index b39b291352..0dc965bd26 100644
--- a/activerecord/lib/active_record/dynamic_finder_match.rb
+++ b/activerecord/lib/active_record/dynamic_finder_match.rb
@@ -2,8 +2,8 @@ module ActiveRecord
# = Active Record Dynamic Finder Match
#
- # Provides dynamic attribute-based finders such as <tt>find_by_country</tt>
- # if, for example, the <tt>Person</tt> has an attribute with that name.
+ # Refer to ActiveRecord::Base documentation for Dynamic attribute-based finders for detailed info
+ #
class DynamicFinderMatch
def self.match(method)
df_match = self.new(method)
@@ -42,6 +42,10 @@ module ActiveRecord
@finder == :first && !@instantiator.nil?
end
+ def creator?
+ @finder == :first && @instantiator == :create
+ end
+
def bang?
@bang
end
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 7aa725d095..e9ac5516ec 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -30,7 +30,8 @@ module ActiveRecord
class SerializationTypeMismatch < ActiveRecordError
end
- # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
+ # Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt>
+ # misses adapter field).
class AdapterNotSpecified < ActiveRecordError
end
@@ -38,7 +39,8 @@ module ActiveRecord
class AdapterNotFound < ActiveRecordError
end
- # Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
+ # Raised when connection to the database could not been established (for example when <tt>connection=</tt>
+ # is given a nil object).
class ConnectionNotEstablished < ActiveRecordError
end
@@ -51,7 +53,8 @@ module ActiveRecord
class RecordNotSaved < ActiveRecordError
end
- # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old).
+ # Raised when SQL statement cannot be executed by the database (for example, it's often the case for
+ # MySQL when Ruby driver used is too old).
class StatementInvalid < ActiveRecordError
end
@@ -78,7 +81,8 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
- # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
+ # Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example,
+ # when using +find+ method)
# does not match number of expected variables.
#
# For example, in
@@ -165,4 +169,4 @@ module ActiveRecord
@errors = errors
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 82270c56b3..e44102b538 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -39,9 +39,10 @@ end
# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
#
-# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
-# by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
-# put your files in <tt><your-rails-app>/test/fixtures/</tt>). The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
+# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed
+# in the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is
+# automatically configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
+# The fixture file ends with the <tt>.yml</tt> file extension (Rails example:
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>). The format of a YAML fixture file looks like this:
#
# rubyonrails:
@@ -58,7 +59,8 @@ end
# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
# pleasure.
#
-# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
+# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
+# See http://yaml.org/type/omap.html
# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
# This is commonly needed for tree structures. Example:
#
@@ -79,7 +81,8 @@ end
# (Rails example: <tt><your-rails-app>/test/fixtures/web_sites.csv</tt>).
#
# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
-# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
+# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the
+# file is then comprised
# of the actual data (1 per line). Here's an example:
#
# id, name, url
@@ -99,15 +102,16 @@ end
#
# == Single-file fixtures
#
-# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
-# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
-# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically configured for Rails, so you can just
-# put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
+# This type of fixture was the original format for Active Record that has since been deprecated in
+# favor of the YAML and CSV formats.
+# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model)
+# to the directory appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
+# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/<your-model-name>/</tt> --
# like <tt><your-rails-app>/test/fixtures/web_sites/</tt> for the WebSite model).
#
# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
-# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension. Here's what the
-# above example might look like:
+# extensions, but if you are on a Windows machine, you might consider adding <tt>.txt</tt> as the extension.
+# Here's what the above example might look like:
#
# web_sites/google
# web_sites/yahoo.txt
@@ -133,7 +137,8 @@ end
# end
# end
#
-# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database, so this test will succeed.
+# By default, the <tt>test_helper module</tt> will load all of your fixtures into your test database,
+# so this test will succeed.
# The testing environment will automatically load the all fixtures into the database before each test.
# To ensure consistent data, the environment deletes the fixtures before running the load.
#
@@ -182,13 +187,15 @@ end
# This will create 1000 very simple YAML fixtures.
#
# Using ERb, you can also inject dynamic values into your fixtures with inserts like <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
-# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
-# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
+# This is however a feature to be used with some caution. The point of fixtures are that they're
+# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
+# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
+# in fixtures are to be considered a code smell.
#
# = Transactional fixtures
#
-# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
+# TestCases can use begin+rollback to isolate their changes to the database instead of having to
+# delete+insert for every test case.
#
# class FooTest < ActiveSupport::TestCase
# self.use_transactional_fixtures = true
@@ -205,15 +212,18 @@ end
# end
#
# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
-# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
+# then you may omit all fixtures declarations in your test cases since all the data's already there
+# and every case rolls back its changes.
#
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
-# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
+# access to fixture data for every table that has been loaded through fixtures (depending on the
+# value of +use_instantiated_fixtures+)
#
# When *not* to use transactional fixtures:
#
-# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
-# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
+# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
+# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
+# and rolled back in teardown. Thus, you won't be able to verify
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
# Use InnoDB, MaxDB, or NDB instead.
@@ -664,14 +674,13 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
end
def has_primary_key_column?
- @has_primary_key_column ||= model_class && primary_key_name &&
- model_class.columns.find { |c| c.name == primary_key_name }
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
end
def timestamp_column_names
- @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
- column_names.include?(name)
- end
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
end
def inheritance_column_name
@@ -872,7 +881,7 @@ module ActiveRecord
table_names.each do |table_name|
table_name = table_name.to_s.tr('./', '_')
- define_method(table_name) do |*fixtures|
+ redefine_method(table_name) do |*fixtures|
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
@fixture_cache[table_name] ||= {}
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index ceb0902fde..b6f87a57b8 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -124,6 +124,7 @@ module ActiveRecord
end
end
+ @destroyed = true
freeze
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 71065f9908..c7ae12977a 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -1,19 +1,35 @@
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
+ def self.runtime=(value)
+ Thread.current["active_record_sql_runtime"] = value
+ end
+
+ def self.runtime
+ Thread.current["active_record_sql_runtime"] ||= 0
+ end
+
+ def self.reset_runtime
+ rt, self.runtime = runtime, 0
+ rt
+ end
+
def initialize
super
@odd_or_even = false
end
def sql(event)
+ self.class.runtime += event.duration
+ return unless logger.debug?
+
name = '%s (%.1fms)' % [event.payload[:name], event.duration]
sql = event.payload[:sql].squeeze(' ')
if odd?
- name = color(name, :cyan, true)
+ name = color(name, CYAN, true)
sql = color(sql, nil, true)
else
- name = color(name, :magenta, true)
+ name = color(name, MAGENTA, true)
end
debug " #{name} #{sql}"
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4c5e1ae218..5e272f0ba4 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -284,7 +284,7 @@ module ActiveRecord
#
# config.active_record.timestamped_migrations = false
#
- # In environment.rb.
+ # In application.rb.
#
class Migration
@@verbose = true
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 849ec9c884..0e560418dc 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -26,7 +26,7 @@ module ActiveRecord
# You can define a \scope that applies to all finders using
# ActiveRecord::Base.default_scope.
def scoped(options = nil)
- if options.present?
+ if options
scoped.apply_finder_options(options)
else
current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
@@ -48,18 +48,21 @@ module ActiveRecord
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
#
- # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
- # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
- # <tt>Shirt.red.where(:size => 'small')</tt>. Also, just as with the association objects, named \scopes act like an Array,
- # implementing Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
+ # resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
+ # you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
+ # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
+ # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
# all behave as if Shirt.red really was an Array.
#
- # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
- # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
- # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
+ # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
+ # all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
+ # returns the number of garments for which these criteria obtain. Similarly with
+ # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
- # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to
- # <tt>has_many</tt> associations. If,
+ # All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
+ # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
# has_many :shirts
@@ -105,7 +108,7 @@ module ActiveRecord
extension ? relation.extending(extension) : relation
end
- singleton_class.send :define_method, name, &scopes[name]
+ singleton_class.send(:redefine_method, name, &scopes[name])
end
def named_scope(*args, &block)
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index cf8c5aaf84..e652296e2c 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -78,7 +78,7 @@ module ActiveRecord
# member.avatar_attributes = { :id => '2', :_destroy => '1' }
# member.avatar.marked_for_destruction? # => true
# member.save
- # member.reload.avatar #=> nil
+ # member.reload.avatar # => nil
#
# Note that the model will _not_ be destroyed until the parent is saved.
#
@@ -180,7 +180,7 @@ module ActiveRecord
#
# member.attributes = params['member']
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
- # member.posts.length #=> 2
+ # member.posts.length # => 2
# member.save
# member.reload.posts.length # => 1
#
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index d2ed643f35..78bac55bf2 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -67,8 +67,8 @@ module ActiveRecord
#
# == Configuration
#
- # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
- # <tt>config/environment.rb</tt> file.
+ # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration
+ # setting in your <tt>config/application.rb</tt> file.
#
# config.active_record.observers = :comment_observer, :signup_observer
#
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 828a8b41b6..71b46beaef 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -60,7 +60,7 @@ module ActiveRecord
# reflect that no changes should be made (since they can't be
# persisted). Returns the frozen instance.
#
- # The row is simply removed with a SQL +DELETE+ statement on the
+ # The row is simply removed with an SQL +DELETE+ statement on the
# record's primary key, and no callbacks are executed.
#
# To enforce the object's +before_destroy+ and +after_destroy+
@@ -91,8 +91,8 @@ module ActiveRecord
# like render <tt>:partial => @client.becomes(Company)</tt> to render that
# instance using the companies/company partial instead of clients/client.
#
- # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
- # instance will affect the other.
+ # Note: The new instance will share a link to the same attributes as the original class.
+ # So any change to the attributes in either instance will affect the other.
def becomes(klass)
became = klass.new
became.instance_variable_set("@attributes", @attributes)
@@ -102,35 +102,57 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record without going through the normal validation procedure
- # or callbacks. This is especially useful for boolean flags on existing records.
+ # Updates a single attribute and saves the record.
+ # This is especially useful for boolean flags on existing records. Also note that
+ #
+ # * The attribute being updated must be a column name.
+ # * Validation is skipped.
+ # * No callbacks are invoked.
+ # * updated_at/updated_on column is updated if that column is available.
+ # * Does not work on associations.
+ # * Does not work on attr_accessor attributes.
+ # * Does not work on new record. <tt>record.new_record?</tt> should return false for this method to work.
+ # * Updates only the attribute that is input to the method. If there are other changed attributes then
+ # those attributes are left alone. In that case even after this method has done its work <tt>record.changed?</tt>
+ # will return true.
+ #
def update_attribute(name, value)
- send("#{name}=", value)
- hash = { name => read_attribute(name) }
+ raise ActiveRecordError, "#{name.to_s} is marked as readonly" if self.class.readonly_attributes.include? name.to_s
+
+ changes = record_update_timestamps || {}
- if record_update_timestamps
- timestamp_attributes_for_update_in_model.each do |column|
- hash[column] = read_attribute(column)
- end
+ if name
+ name = name.to_s
+ send("#{name}=", value)
+ changes[name] = read_attribute(name)
end
- @changed_attributes.delete(name.to_s)
+ @changed_attributes.except!(*changes.keys)
primary_key = self.class.primary_key
- self.class.update_all(hash, { primary_key => self[primary_key] }) == 1
+ self.class.update_all(changes, { primary_key => self[primary_key] }) == 1
end
- # Updates all the attributes from the passed-in Hash and saves the record.
- # If the object is invalid, the saving will fail and false will be returned.
+ # Updates the attributes of the model from the passed-in hash and saves the
+ # record, all wrapped in a transaction. If the object is invalid, the saving
+ # will fail and false will be returned.
def update_attributes(attributes)
- self.attributes = attributes
- save
+ # The following transaction covers any possible database side-effects of the
+ # attributes assignment. For example, setting the IDs of a child collection.
+ with_transaction_returning_status do
+ self.attributes = attributes
+ save
+ end
end
- # Updates an object just like Base.update_attributes but calls save! instead
- # of save so an exception is raised if the record is invalid.
+ # Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
+ # of +save+, so an exception is raised if the record is invalid.
def update_attributes!(attributes)
- self.attributes = attributes
- save!
+ # The following transaction covers any possible database side-effects of the
+ # attributes assignment. For example, setting the IDs of a child collection.
+ with_transaction_returning_status do
+ self.attributes = attributes
+ save!
+ end
end
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
@@ -196,6 +218,19 @@ module ActiveRecord
self
end
+ # Saves the record with the updated_at/on attributes set to the current time.
+ # Please note that no validation is performed and no callbacks are executed.
+ # If an attribute name is passed, that attribute is updated along with
+ # updated_at/on attributes.
+ #
+ # Examples:
+ #
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ def touch(attribute = nil)
+ update_attribute(attribute, current_time_from_proper_timezone)
+ end
+
private
def create_or_update
raise ReadOnlyRecord if readonly?
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 2808e199fe..78fdb77216 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,16 +16,18 @@ module ActiveRecord
config.generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::QueryCache"
-
- config.app_middleware.insert_after "::ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache"
rake_tasks do
load "active_record/railties/databases.rake"
end
+ # When loading console, force ActiveRecord to be loaded to avoid cross
+ # references when loading a constant for the first time.
+ console do
+ ActiveRecord::Base
+ end
+
initializer "active_record.initialize_timezone" do
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
@@ -72,6 +74,13 @@ module ActiveRecord
end
end
+ initializer "active_record.add_concurrency_middleware" do |app|
+ if app.config.allow_concurrency
+ app.config.middleware.insert_after "::ActionDispatch::Callbacks",
+ "ActiveRecord::ConnectionAdapters::ConnectionManagement"
+ end
+ end
+
config.after_initialize do
ActiveSupport.on_load(:active_record) do
instantiate_observers
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index aed1c59b00..bc6ca936c0 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -11,9 +11,9 @@ module ActiveRecord
def cleanup_view_runtime
if ActiveRecord::Base.connected?
- db_rt_before_render = ActiveRecord::Base.connection.reset_runtime
+ db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
runtime = super
- db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
+ db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime
self.db_runtime = db_rt_before_render + db_rt_after_render
runtime - db_rt_after_render
else
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 5024787c3c..ae605d3e7a 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -274,7 +274,7 @@ namespace :db do
task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ]
desc 'Load the seed data from db/seeds.rb'
- task :seed => :environment do
+ task :seed => 'db:abort_if_pending_migrations' do
seed_file = File.join(Rails.root, 'db', 'seeds.rb')
load(seed_file) if File.exist?(seed_file)
end
@@ -339,7 +339,7 @@ namespace :db do
end
namespace :structure do
- desc "Dump the database structure to a SQL file"
+ desc "Dump the database structure to an SQL file"
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[Rails.env]["adapter"]
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index a82e5d7ed1..7f47a812eb 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -3,14 +3,14 @@ module ActiveRecord
module Reflection # :nodoc:
extend ActiveSupport::Concern
- # Reflection allows you to interrogate Active Record classes and objects
+ # Reflection enables to interrogate Active Record classes and objects
# about their associations and aggregations. This information can,
- # for example, be used in a form builder that took an Active Record object
- # and created input fields for all of the attributes depending on their type
- # and displayed the associations to other objects.
+ # for example, be used in a form builder that takes an Active Record object
+ # and creates input fields for all of the attributes depending on their type
+ # and displays the associations to other objects.
#
- # You can find the interface for the AggregateReflection and AssociationReflection
- # classes in the abstract MacroReflection class.
+ # MacroReflection class has info for AggregateReflection and AssociationReflection
+ # classes.
module ClassMethods
def create_reflection(macro, name, options, active_record)
case macro
@@ -24,7 +24,7 @@ module ActiveRecord
reflection
end
- # Returns a hash containing all AssociationReflection objects for the current class
+ # Returns a hash containing all AssociationReflection objects for the current class.
# Example:
#
# Invoice.reflections
@@ -39,9 +39,9 @@ module ActiveRecord
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
end
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
+ # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
#
- # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
+ # Account.reflect_on_aggregation(:balance) #=> the balance AggregateReflection
#
def reflect_on_aggregation(aggregation)
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
@@ -50,7 +50,7 @@ module ActiveRecord
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
- # <tt>:belongs_to</tt>) for that as the first parameter.
+ # <tt>:belongs_to</tt>) as the first parameter.
#
# Example:
#
@@ -62,9 +62,9 @@ module ActiveRecord
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
end
- # Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
+ # Returns the AssociationReflection object for the +association+ (use the symbol).
#
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
#
def reflect_on_association(association)
@@ -78,8 +78,7 @@ module ActiveRecord
end
- # Abstract base class for AggregateReflection and AssociationReflection that
- # describes the interface available for both of those classes. Objects of
+ # Abstract base class for AggregateReflection and AssociationReflection. Objects of
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
attr_reader :active_record
@@ -88,34 +87,36 @@ module ActiveRecord
@macro, @name, @options, @active_record = macro, name, options, active_record
end
- # Returns the name of the macro. For example, <tt>composed_of :balance,
- # :class_name => 'Money'</tt> will return <tt>:balance</tt> or for
- # <tt>has_many :clients</tt> it will return <tt>:clients</tt>.
- def name
- @name
- end
+ # Returns the name of the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
+ # <tt>has_many :clients</tt> returns <tt>:clients</tt>
+ attr_reader :name
- # Returns the macro type. For example,
- # <tt>composed_of :balance, :class_name => 'Money'</tt> will return <tt>:composed_of</tt>
- # or for <tt>has_many :clients</tt> will return <tt>:has_many</tt>.
- def macro
- @macro
- end
+ # Returns the macro type.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
+ # <tt>has_many :clients</tt> returns <tt>:has_many</tt>
+ attr_reader :macro
- # Returns the hash of options used for the macro. For example, it would return <tt>{ :class_name => "Money" }</tt> for
- # <tt>composed_of :balance, :class_name => 'Money'</tt> or +{}+ for <tt>has_many :clients</tt>.
- def options
- @options
- end
+ # Returns the hash of options used for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
+ # <tt>has_many :clients</tt> returns +{}+
+ attr_reader :options
- # Returns the class for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money
- # class and <tt>has_many :clients</tt> returns the Client class.
+ # Returns the class for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
+ # <tt>has_many :clients</tt> returns the Client class
def klass
@klass ||= class_name.constantize
end
- # Returns the class name for the macro. For example, <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
- # and <tt>has_many :clients</tt> returns <tt>'Client'</tt>.
+ # Returns the class name for the macro.
+ #
+ # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
+ # <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
@class_name ||= options[:class_name] || derive_class_name
end
@@ -130,11 +131,6 @@ module ActiveRecord
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
end
- # Returns +true+ if +self+ is a +belongs_to+ reflection.
- def belongs_to?
- macro == :belongs_to
- end
-
private
def derive_class_name
name.to_s.camelize
@@ -150,7 +146,7 @@ module ActiveRecord
# Holds all the meta-data about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
- # Returns the target association's class:
+ # Returns the target association's class.
#
# class Author < ActiveRecord::Base
# has_many :books
@@ -159,7 +155,7 @@ module ActiveRecord
# Author.reflect_on_association(:books).klass
# # => Book
#
- # <b>Note:</b> do not call +klass.new+ or +klass.create+ to instantiate
+ # <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
# a new association object. Use +build_association+ or +create_association+
# instead. This allows plugins to hook into association object creation.
def klass
@@ -206,6 +202,10 @@ module ActiveRecord
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
end
+ def primary_key_column
+ @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
+ end
+
def association_foreign_key
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
end
@@ -270,7 +270,7 @@ module ActiveRecord
end
# Returns whether or not this association reflection is for a collection
- # association. Returns +true+ if the +macro+ is one of +has_many+ or
+ # association. Returns +true+ if the +macro+ is either +has_many+ or
# +has_and_belongs_to_many+, +false+ otherwise.
def collection?
@collection
@@ -280,7 +280,7 @@ module ActiveRecord
# the parent's validation.
#
# Unless you explicitly disable validation with
- # <tt>:validate => false</tt>, it will take place when:
+ # <tt>:validate => false</tt>, validation will take place when:
#
# * you explicitly enable validation; <tt>:validate => true</tt>
# * you use autosave; <tt>:autosave => true</tt>
@@ -300,6 +300,11 @@ module ActiveRecord
dependent_conditions
end
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
+ def belongs_to?
+ macro == :belongs_to
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
@@ -324,8 +329,6 @@ module ActiveRecord
# Gets the source of the through reflection. It checks both a singularized
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
#
- # (The <tt>:tags</tt> association on Tagging below.)
- #
# class Post < ActiveRecord::Base
# has_many :taggings
# has_many :tags, :through => :taggings
@@ -336,7 +339,7 @@ module ActiveRecord
end
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
- # of a HasManyThrough or HasOneThrough association. Example:
+ # of a HasManyThrough or HasOneThrough association.
#
# class Post < ActiveRecord::Base
# has_many :taggings
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index d9fc1b4940..30be723291 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,17 +10,18 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
- delegate :to_xml, :to_json, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
delegate :insert, :to => :arel
- attr_reader :table, :klass
+ attr_reader :table, :klass, :loaded
attr_accessor :extensions
+ alias :loaded? :loaded
def initialize(klass, table)
@klass, @table = klass, table
@implicit_readonly = nil
- @loaded = nil
+ @loaded = false
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
@@ -66,7 +67,8 @@ module ActiveRecord
preload += @includes_values unless eager_loading?
preload.each {|associations| @klass.send(:preload_associations, @records, associations) }
- # @readonly_value is true only if set explicitly. @implicit_readonly is true if there are JOINS and no explicit SELECT.
+ # @readonly_value is true only if set explicitly. @implicit_readonly is true if there
+ # are JOINS and no explicit SELECT.
readonly = @readonly_value.nil? ? @implicit_readonly : @readonly_value
@records.each { |record| record.readonly! } if readonly
@@ -74,6 +76,8 @@ module ActiveRecord
@records
end
+ def as_json(options = nil) to_a end #:nodoc:
+
# Returns size of the records.
def size
loaded? ? @records.length : count
@@ -96,7 +100,7 @@ module ActiveRecord
if block_given?
to_a.many? { |*block_args| yield(*block_args) }
else
- @limit_value.present? ? to_a.many? : size > 1
+ @limit_value ? to_a.many? : size > 1
end
end
@@ -105,7 +109,7 @@ module ActiveRecord
# ==== Example
#
# Comment.where(:post_id => 1).scoping do
- # Comment.first #=> SELECT * FROM comments WHERE post_id = 1
+ # Comment.first # SELECT * FROM comments WHERE post_id = 1
# end
#
# Please check unscoped if you want to remove all previous scopes (including
@@ -127,7 +131,8 @@ module ActiveRecord
# ==== Parameters
#
# * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
+ # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement.
+ # See conditions in the intro.
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
#
# ==== Examples
@@ -141,7 +146,7 @@ module ActiveRecord
# # Update all avatars migrated more than a week ago
# Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
#
- # # Update all books that match our conditions, but limit it to 5 ordered by date
+ # # Update all books that match conditions, but limit it to 5 ordered by date
# Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
def update_all(updates, conditions = nil, options = {})
if conditions || options.present?
@@ -162,14 +167,14 @@ module ActiveRecord
# ==== Parameters
#
# * +id+ - This should be the id or an array of ids to be updated.
- # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
+ # * +attributes+ - This should be a hash of attributes or an array of hashes.
#
# ==== Examples
#
- # # Updating one record:
+ # # Updates one record
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
#
- # # Updating multiple records:
+ # # Updates multiple records
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
# Person.update(people.keys, people.values)
def update(id, attributes)
@@ -290,10 +295,6 @@ module ActiveRecord
where(@klass.primary_key => id_or_array).delete_all
end
- def loaded?
- @loaded
- end
-
def reload
reset
to_a # force reload
@@ -317,12 +318,14 @@ module ActiveRecord
def scope_for_create
@scope_for_create ||= begin
- @create_with_value || @where_values.inject({}) do |hash, where|
- if where.is_a?(Arel::Predicates::Equality)
- hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
- end
- hash
- end
+ @create_with_value || Hash[
+ @where_values.find_all { |w|
+ w.respond_to?(:operator) && w.operator == :==
+ }.map { |where|
+ [where.operand1.name,
+ where.operand2.respond_to?(:value) ?
+ where.operand2.value : where.operand2]
+ }]
end
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 412be895c4..d7494ebb5a 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -50,9 +50,9 @@ module ActiveRecord
def find_in_batches(options = {})
relation = self
- if orders.present? || taken.present?
- ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
- end
+ if orders.present? || taken.present?
+ ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ end
if (finder_options = options.except(:start, :batch_size)).present?
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
@@ -73,7 +73,7 @@ module ActiveRecord
break if records.size < batch_size
if primary_key_offset = records.last.id
- records = relation.where(primary_key.gt(primary_key_offset)).all
+ records = relation.where(primary_key.gt(primary_key_offset)).to_a
else
raise "Primary key not included in the custom select clause"
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 44baeb6c84..a679c444cf 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -1,30 +1,38 @@
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/object/try'
module ActiveRecord
module Calculations
# Count operates using three different approaches.
#
# * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
- # * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present
+ # * Count using column: By passing a column name to count, it will return a count of all the
+ # rows for the model with supplied column present
# * Count using options will find the row count matched by the options used.
#
# The third approach, count using options, accepts an option hash as the only parameter. The options are:
#
- # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # See conditions in the intro to ActiveRecord::Base.
# * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
- # or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # or named associations in the same form used for the <tt>:include</tt> option, which will
+ # perform an INNER JOIN on the associated table(s).
+ # If the value is a string, then the records will be returned read-only since they will have
+ # attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
- # to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
+ # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs.
+ # The symbols named refer to already defined associations. When using named associations, count
+ # returns the number of DISTINCT items for the model you're counting.
# See eager loading under Associations.
# * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example,
+ # want to do a join but not
# include the joined columns.
- # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
- # of a database view).
+ # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as
+ # SELECT COUNT(DISTINCT posts.id) ...
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an
+ # alternate table name (or even the name of a database view).
#
# Examples for counting all:
# Person.count # returns the total count of all people
@@ -34,12 +42,19 @@ module ActiveRecord
#
# Examples for count with options:
# Person.count(:conditions => "age > 26")
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
- # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
+ #
+ # # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job)
+ #
+ # # finds the number of rows matching the conditions and joins.
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000",
+ # :joins => "LEFT JOIN jobs on jobs.person_id = person.id")
+ #
# Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
# Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
#
- # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition. Use Person.count instead.
+ # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition.
+ # Use Person.count instead.
def count(column_name = nil, options = {})
column_name, options = nil, column_name if column_name.is_a?(Hash)
calculate(:count, column_name, options)
@@ -80,13 +95,15 @@ module ActiveRecord
calculate(:sum, column_name, options)
end
- # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
- # Options such as <tt>:conditions</tt>, <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
+ # This calculates aggregate values in the given column. Methods for count, sum, average,
+ # minimum, and maximum have been added as shortcuts. Options such as <tt>:conditions</tt>,
+ # <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
#
# There are two basic forms of output:
- # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
- # * Grouped values: This returns an ordered hash of the values and groups them by the <tt>:group</tt> option. It takes either a column name, or the name
- # of a belongs_to association.
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
+ # for AVG, and the given column's type for everything else.
+ # * Grouped values: This returns an ordered hash of the values and groups them by the
+ # <tt>:group</tt> option. It takes either a column name, or the name of a belongs_to association.
#
# values = Person.maximum(:age, :group => 'last_name')
# puts values["Drake"]
@@ -102,21 +119,30 @@ module ActiveRecord
# end
#
# Options:
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
- # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
- # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
- # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # See conditions in the intro to ActiveRecord::Base.
+ # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything,
+ # the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
+ # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id".
+ # (Rarely needed).
+ # The records will be returned read-only since they will have attributes that do not correspond to the
+ # table's columns.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
- # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
- # include the joined columns.
- # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
+ # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example
+ # want to do a join, but not include the joined columns.
+ # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as
+ # SELECT COUNT(DISTINCT posts.id) ...
#
# Examples:
# Person.calculate(:count, :all) # The same as Person.count
# Person.average(:age) # SELECT AVG(age) FROM people...
- # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
- # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
+ # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for
+ # # everyone with a last name other than 'Drake'
+ #
+ # # Selects the minimum age for any family without any minors
+ # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name)
+ #
# Person.sum("2 * age")
def calculate(operation, column_name, options = {})
if options.except(:distinct).present?
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 3bf4c5bdd1..b34c11973b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -21,23 +21,28 @@ module ActiveRecord
#
# ==== Parameters
#
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
+ # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>,
+ # or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
+ # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a
+ # <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5,
+ # it would skip rows 0 through 4.
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
+ # named associations in the same form used for the <tt>:include</tt> option, which will perform an
+ # <tt>INNER JOIN</tt> on the associated table(s),
# or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # If the value is a string, then the records will be returned read-only since they will
+ # have attributes that do not correspond to the table's columns.
# Pass <tt>:readonly => false</tt> to override.
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
# to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
- # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
- # of a database view).
+ # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you,
+ # for example, want to do a join but not include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
+ # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed
+ # to an alternate table name (or even the name of a database view).
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
# * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
# <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
@@ -164,6 +169,8 @@ module ActiveRecord
# Person.exists?(['name LIKE ?', "%#{query}%"])
# Person.exists?
def exists?(id = nil)
+ id = id.id if ActiveRecord::Base === id
+
case id
when Array, Hash
where(id).exists?
@@ -279,6 +286,8 @@ module ActiveRecord
end
def find_one(id)
+ id = id.id if ActiveRecord::Base === id
+
record = where(primary_key.eq(id)).first
unless record
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4692271266..e71f1cca72 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -11,91 +11,91 @@ module ActiveRecord
def includes(*args)
args.reject! { |a| a.blank? }
- clone.tap { |r| r.includes_values += args if args.present? }
+ clone.tap {|r| r.includes_values += args if args.present? }
end
def eager_load(*args)
- clone.tap { |r| r.eager_load_values += args if args.present? }
+ clone.tap {|r| r.eager_load_values += args if args.present? }
end
def preload(*args)
- clone.tap { |r| r.preload_values += args if args.present? }
+ clone.tap {|r| r.preload_values += args if args.present? }
end
def select(*args)
if block_given?
- to_a.select { |*block_args| yield(*block_args) }
+ to_a.select {|*block_args| yield(*block_args) }
else
- clone.tap { |r| r.select_values += args if args.present? }
+ clone.tap {|r| r.select_values += args if args.present? }
end
end
def group(*args)
- clone.tap { |r| r.group_values += args if args.present? }
+ clone.tap {|r| r.group_values += args.flatten if args.present? }
end
def order(*args)
- clone.tap { |r| r.order_values += args if args.present? }
+ clone.tap {|r| r.order_values += args if args.present? }
end
def reorder(*args)
- clone.tap { |r| r.order_values = args if args.present? }
+ clone.tap {|r| r.order_values = args if args.present? }
end
def joins(*args)
args.flatten!
- clone.tap { |r| r.joins_values += args if args.present? }
+ clone.tap {|r| r.joins_values += args if args.present? }
end
- def where(*args)
- value = build_where(*args)
- clone.tap { |r| r.where_values += Array.wrap(value) if value.present? }
+ def where(opts, *rest)
+ value = build_where(opts, rest)
+ value ? clone.tap {|r| r.where_values += Array.wrap(value) } : clone
end
def having(*args)
value = build_where(*args)
- clone.tap { |r| r.having_values += Array.wrap(value) if value.present? }
+ clone.tap {|r| r.having_values += Array.wrap(value) if value.present? }
end
def limit(value = true)
- clone.tap { |r| r.limit_value = value }
+ clone.tap {|r| r.limit_value = value }
end
def offset(value = true)
- clone.tap { |r| r.offset_value = value }
+ clone.tap {|r| r.offset_value = value }
end
def lock(locks = true)
case locks
when String, TrueClass, NilClass
- clone.tap { |r| r.lock_value = locks || true }
+ clone.tap {|r| r.lock_value = locks || true }
else
- clone.tap { |r| r.lock_value = false }
+ clone.tap {|r| r.lock_value = false }
end
end
def readonly(value = true)
- clone.tap { |r| r.readonly_value = value }
+ clone.tap {|r| r.readonly_value = value }
end
def create_with(value = true)
- clone.tap { |r| r.create_with_value = value }
+ clone.tap {|r| r.create_with_value = value }
end
def from(value = true)
- clone.tap { |r| r.from_value = value }
+ clone.tap {|r| r.from_value = value }
end
def extending(*modules, &block)
modules << Module.new(&block) if block_given?
- clone.tap { |r| r.send(:apply_modules, *modules) }
+ clone.tap {|r| r.send(:apply_modules, *modules) }
end
def reverse_order
order_clause = arel.send(:order_clauses).join(', ')
relation = except(:order)
- if order_clause.present?
+ unless order_clauses.blank?
relation.order(reverse_sql_order(order_clause))
else
relation.order("#{@klass.table_name}.#{@klass.primary_key} DESC")
@@ -129,7 +129,7 @@ module ActiveRecord
def build_arel
arel = table
- arel = build_joins(arel, @joins_values) if @joins_values.present?
+ arel = build_joins(arel, @joins_values) unless @joins_values.empty?
@where_values.uniq.each do |where|
next if where.blank?
@@ -143,36 +143,27 @@ module ActiveRecord
end
end
- arel = arel.having(*@having_values.uniq.select{|h| h.present?}) if @having_values.present?
+ arel = arel.having(*@having_values.uniq.select{|h| h.present?}) unless @having_values.empty?
- arel = arel.take(@limit_value) if @limit_value.present?
- arel = arel.skip(@offset_value) if @offset_value.present?
+ arel = arel.take(@limit_value) if @limit_value
+ arel = arel.skip(@offset_value) if @offset_value
- arel = arel.group(*@group_values.uniq.select{|g| g.present?}) if @group_values.present?
+ arel = arel.group(*@group_values.uniq.select{|g| g.present?}) unless @group_values.empty?
- arel = arel.order(*@order_values.uniq.select{|o| o.present?}) if @order_values.present?
+ arel = arel.order(*@order_values.uniq.select{|o| o.present?}) unless @order_values.empty?
arel = build_select(arel, @select_values.uniq)
- arel = arel.from(@from_value) if @from_value.present?
-
- case @lock_value
- when TrueClass
- arel = arel.lock
- when String
- arel = arel.lock(@lock_value)
- end if @lock_value.present?
+ arel = arel.from(@from_value) if @from_value
+ arel = arel.lock(@lock_value) if @lock_value
arel
end
- def build_where(*args)
- return if args.blank?
-
- opts = args.first
+ def build_where(opts, other = [])
case opts
when String, Array
- @klass.send(:sanitize_sql, args.size > 1 ? args : opts)
+ @klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))
when Hash
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
PredicateBuilder.new(table.engine).build_from_hash(attributes, table)
@@ -230,7 +221,7 @@ module ActiveRecord
@implicit_readonly = false
# TODO: fix this ugly hack, we should refactor the callers to get an ARel compatible array.
# Before this change we were passing to ARel the last element only, and ARel is capable of handling an array
- if selects.all? { |s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
+ if selects.all? {|s| s.is_a?(String) || !s.is_a?(Arel::Expression) } && !(selects.last =~ /^COUNT\(/)
arel.project(*selects)
else
arel.project(selects.last)
@@ -247,7 +238,7 @@ module ActiveRecord
end
def reverse_sql_order(order_query)
- order_query.to_s.split(/,/).each { |s|
+ order_query.split(',').each { |s|
if s.match(/\s(asc|ASC)$/)
s.gsub!(/\s(asc|ASC)$/, ' DESC')
elsif s.match(/\s(desc|DESC)$/)
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index e2783087ec..c1bc3214ea 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/object/blank'
module ActiveRecord
# = Active Record Schema
- #
+ #
# Allows programmers to programmatically define a schema in a portable
# DSL. This means you can define tables, indexes, etc. without using SQL
# directly, so your applications can more easily support multiple
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index a4757773d8..e9af20e1b6 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -8,13 +8,13 @@ module ActiveRecord
# output format (i.e., ActiveRecord::Schema).
class SchemaDumper #:nodoc:
private_class_method :new
-
+
##
# :singleton-method:
- # A list of tables which should not be dumped to the schema.
+ # A list of tables which should not be dumped to the schema.
# Acceptable values are strings as well as regexp.
# This setting is only used if ActiveRecord::Base.schema_format == :ruby
- cattr_accessor :ignore_tables
+ cattr_accessor :ignore_tables
@@ignore_tables = []
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
@@ -71,7 +71,7 @@ HEADER
else
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
end
- end
+ end
table(tbl, stream)
end
end
@@ -87,7 +87,7 @@ HEADER
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
-
+
tbl.print " create_table #{table.inspect}"
if columns.detect { |c| c.name == pk }
if pk != 'id'
@@ -105,7 +105,7 @@ HEADER
next if column.name == pk
spec = {}
spec[:name] = column.name.inspect
-
+
# AR has an optimisation which handles zero-scale decimals as integers. This
# code ensures that the dumper still dumps the column as a decimal.
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
@@ -148,7 +148,7 @@ HEADER
tbl.puts " end"
tbl.puts
-
+
indexes(table, tbl)
tbl.rewind
@@ -158,7 +158,7 @@ HEADER
stream.puts "# #{e.message}"
stream.puts
end
-
+
stream
end
@@ -172,7 +172,7 @@ HEADER
value.inspect
end
end
-
+
def indexes(table, stream)
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb
index b88d550086..becde0fbfd 100644
--- a/activerecord/lib/active_record/session_store.rb
+++ b/activerecord/lib/active_record/session_store.rb
@@ -16,7 +16,7 @@ module ActiveRecord
# ActionController::SessionOverflowError will be raised.
#
# You may configure the table name, primary key, and data column.
- # For example, at the end of <tt>config/environment.rb</tt>:
+ # For example, at the end of <tt>config/application.rb</tt>:
#
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
@@ -49,8 +49,34 @@ module ActiveRecord
# The example SqlBypass class is a generic SQL session store. You may
# use it as a basis for high-performance database-specific stores.
class SessionStore < ActionDispatch::Session::AbstractStore
+ module ClassMethods # :nodoc:
+ def marshal(data)
+ ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
+ end
+
+ def unmarshal(data)
+ Marshal.load(ActiveSupport::Base64.decode64(data)) if data
+ end
+
+ def drop_table!
+ connection.execute "DROP TABLE #{table_name}"
+ end
+
+ def create_table!
+ connection.execute <<-end_sql
+ CREATE TABLE #{table_name} (
+ id #{connection.type_to_sql(:primary_key)},
+ #{connection.quote_column_name(session_id_column)} VARCHAR(255) UNIQUE,
+ #{connection.quote_column_name(data_column_name)} TEXT
+ )
+ end_sql
+ end
+ end
+
# The default Active Record class.
class Session < ActiveRecord::Base
+ extend ClassMethods
+
##
# :singleton-method:
# Customizable data column name. Defaults to 'data'.
@@ -62,7 +88,7 @@ module ActiveRecord
class << self
def data_column_size_limit
- @data_column_size_limit ||= columns_hash[@@data_column_name].limit
+ @data_column_size_limit ||= columns_hash[data_column_name].limit
end
# Hook to set up sessid compatibility.
@@ -71,29 +97,11 @@ module ActiveRecord
find_by_session_id(session_id)
end
- def marshal(data)
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
- end
-
- def unmarshal(data)
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
- end
-
- def create_table!
- connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{connection.quote_column_name('session_id')} TEXT UNIQUE,
- #{connection.quote_column_name(@@data_column_name)} TEXT(255)
- )
- end_sql
- end
-
- def drop_table!
- connection.execute "DROP TABLE #{table_name}"
- end
-
private
+ def session_id_column
+ 'session_id'
+ end
+
# Compatibility with tables using sessid instead of session_id.
def setup_sessid_compatibility!
# Reset column info since it may be stale.
@@ -106,6 +114,8 @@ module ActiveRecord
define_method(:session_id) { sessid }
define_method(:session_id=) { |session_id| self.sessid = session_id }
else
+ class << self; remove_method :find_by_session_id; end
+
def self.find_by_session_id(session_id)
find :first, :conditions => {:session_id=>session_id}
end
@@ -113,6 +123,11 @@ module ActiveRecord
end
end
+ def initialize(attributes = nil)
+ @data = nil
+ super
+ end
+
# Lazy-unmarshal session state.
def data
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
@@ -122,22 +137,22 @@ module ActiveRecord
# Has the session been loaded yet?
def loaded?
- !!@data
+ @data
end
private
def marshal_data!
- return false if !loaded?
- write_attribute(@@data_column_name, self.class.marshal(self.data))
+ return false unless loaded?
+ write_attribute(@@data_column_name, self.class.marshal(data))
end
# Ensures that the data about to be stored in the database is not
# larger than the data storage column. Raises
# ActionController::SessionOverflowError.
def raise_on_session_data_overflow!
- return false if !loaded?
+ return false unless loaded?
limit = self.class.data_column_size_limit
- if loaded? and limit and read_attribute(@@data_column_name).size > limit
+ if limit and read_attribute(@@data_column_name).size > limit
raise ActionController::SessionOverflowError
end
end
@@ -162,6 +177,8 @@ module ActiveRecord
# binary session data in a +text+ column. For higher performance,
# store in a +blob+ column instead and forgo the Base64 encoding.
class SqlBypass
+ extend ClassMethods
+
##
# :singleton-method:
# Use the ActiveRecord::Base.connection by default.
@@ -186,6 +203,8 @@ module ActiveRecord
@@data_column = 'data'
class << self
+ alias :data_column_name :data_column
+
def connection
@@connection ||= ActiveRecord::Base.connection
end
@@ -196,43 +215,21 @@ module ActiveRecord
new(:session_id => session_id, :marshaled_data => record['data'])
end
end
-
- def marshal(data)
- ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
- end
-
- def unmarshal(data)
- Marshal.load(ActiveSupport::Base64.decode64(data)) if data
- end
-
- def create_table!
- @@connection.execute <<-end_sql
- CREATE TABLE #{table_name} (
- id INTEGER PRIMARY KEY,
- #{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
- #{@@connection.quote_column_name(data_column)} TEXT
- )
- end_sql
- end
-
- def drop_table!
- @@connection.execute "DROP TABLE #{table_name}"
- end
end
- attr_reader :session_id
+ attr_reader :session_id, :new_record
+ alias :new_record? :new_record
+
attr_writer :data
# Look for normal and marshaled data, self.find_by_session_id's way of
# telling us to postpone unmarshaling until the data is requested.
# We need to handle a normal data attribute in case of a new record.
def initialize(attributes)
- @session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
- @new_record = @marshaled_data.nil?
- end
-
- def new_record?
- @new_record
+ @session_id = attributes[:session_id]
+ @data = attributes[:data]
+ @marshaled_data = attributes[:marshaled_data]
+ @new_record = @marshaled_data.nil?
end
# Lazy-unmarshal session state.
@@ -248,39 +245,41 @@ module ActiveRecord
end
def loaded?
- !!@data
+ @data
end
def save
- return false if !loaded?
+ return false unless loaded?
marshaled_data = self.class.marshal(data)
+ connect = connection
if @new_record
@new_record = false
- @@connection.update <<-end_sql, 'Create session'
- INSERT INTO #{@@table_name} (
- #{@@connection.quote_column_name(@@session_id_column)},
- #{@@connection.quote_column_name(@@data_column)} )
+ connect.update <<-end_sql, 'Create session'
+ INSERT INTO #{table_name} (
+ #{connect.quote_column_name(session_id_column)},
+ #{connect.quote_column_name(data_column)} )
VALUES (
- #{@@connection.quote(session_id)},
- #{@@connection.quote(marshaled_data)} )
+ #{connect.quote(session_id)},
+ #{connect.quote(marshaled_data)} )
end_sql
else
- @@connection.update <<-end_sql, 'Update session'
- UPDATE #{@@table_name}
- SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
+ connect.update <<-end_sql, 'Update session'
+ UPDATE #{table_name}
+ SET #{connect.quote_column_name(data_column)}=#{connect.quote(marshaled_data)}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
end_sql
end
end
def destroy
- unless @new_record
- @@connection.delete <<-end_sql, 'Destroy session'
- DELETE FROM #{@@table_name}
- WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
- end_sql
- end
+ return if @new_record
+
+ connect = connection
+ connect.delete <<-end_sql, 'Destroy session'
+ DELETE FROM #{table_name}
+ WHERE #{connect.quote_column_name(session_id_column)}=#{connect.quote(session_id)}
+ end_sql
end
end
@@ -289,7 +288,7 @@ module ActiveRecord
cattr_accessor :session_class
self.session_class = Session
- SESSION_RECORD_KEY = 'rack.session.record'.freeze
+ SESSION_RECORD_KEY = 'rack.session.record'
private
def get_session(env, sid)
@@ -317,7 +316,7 @@ module ActiveRecord
sid
end
-
+
def destroy(env)
if sid = current_session_id(env)
Base.silence do
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 1075a60f07..5531d12a41 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -12,6 +12,19 @@ module ActiveRecord
# Timestamps are in the local timezone by default but you can use UTC by setting:
#
# <tt>ActiveRecord::Base.default_timezone = :utc</tt>
+ #
+ # == Time Zone aware attributes
+ #
+ # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
+ #
+ # ActiveRecord::Base.time_zone_aware_attributes = true
+ #
+ # This feature can easily be turned off by assigning value <tt>false</tt> .
+ #
+ # If your attributes are time zone aware and you desire to skip time zone conversion for certain
+ # attributes then you can do following:
+ #
+ # Topic.skip_time_zone_conversion_for_attributes = [:written_on]
module Timestamp
extend ActiveSupport::Concern
@@ -19,35 +32,16 @@ module ActiveRecord
class_inheritable_accessor :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
-
- # Saves the record with the updated_at/on attributes set to the current time.
- # If the save fails because of validation errors, an
- # ActiveRecord::RecordInvalid exception is raised. If an attribute name is passed,
- # that attribute is used for the touch instead of the updated_at/on attributes.
- #
- # Examples:
- #
- # product.touch # updates updated_at
- # product.touch(:designed_at) # updates the designed_at attribute
- def touch(attribute = nil)
- current_time = current_time_from_proper_timezone
-
- if attribute
- write_attribute(attribute, current_time)
- else
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- end
-
- save!
- end
private
+
def create #:nodoc:
if record_timestamps
current_time = current_time_from_proper_timezone
- write_attribute('created_at', current_time) if respond_to?(:created_at) && created_at.nil?
- write_attribute('created_on', current_time) if respond_to?(:created_on) && created_on.nil?
+ timestamp_attributes_for_create.each do |column|
+ write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
+ end
timestamp_attributes_for_update_in_model.each do |column|
write_attribute(column.to_s, current_time) if self.send(column).nil?
@@ -58,22 +52,33 @@ module ActiveRecord
end
def update(*args) #:nodoc:
- record_update_timestamps
+ record_update_timestamps if !partial_updates? || changed?
super
end
- def record_update_timestamps
- if record_timestamps && (!partial_updates? || changed?)
- current_time = current_time_from_proper_timezone
- timestamp_attributes_for_update_in_model.each { |column| write_attribute(column.to_s, current_time) }
- true
- else
- false
+ def record_update_timestamps #:nodoc:
+ return unless record_timestamps
+ current_time = current_time_from_proper_timezone
+ timestamp_attributes_for_update_in_model.inject({}) do |hash, column|
+ hash[column.to_s] = write_attribute(column.to_s, current_time)
+ hash
end
end
def timestamp_attributes_for_update_in_model #:nodoc:
- [:updated_at, :updated_on].select { |elem| respond_to?(elem) }
+ timestamp_attributes_for_update.select { |elem| respond_to?(elem) }
+ end
+
+ def timestamp_attributes_for_update #:nodoc:
+ [:updated_at, :updated_on]
+ end
+
+ def timestamp_attributes_for_create #:nodoc:
+ [:created_at, :created_on]
+ end
+
+ def all_timestamp_attributes #:nodoc:
+ timestamp_attributes_for_update + timestamp_attributes_for_create
end
def current_time_from_proper_timezone #:nodoc:
diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb
index 0b0f5682aa..15b587de45 100644
--- a/activerecord/lib/active_record/validations/associated.rb
+++ b/activerecord/lib/active_record/validations/associated.rb
@@ -27,8 +27,9 @@ module ActiveRecord
#
# this would specify a circular dependency and cause infinite recursion.
#
- # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
- # is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
+ # ensure that the association is both present and guaranteed to be valid, you also need to
+ # use +validates_presence_of+.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
@@ -44,4 +45,4 @@ module ActiveRecord
end
end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 1c9ecc7b1b..bf863c7063 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -78,22 +78,25 @@ module ActiveRecord
end
module ClassMethods
- # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
+ # Validates whether the value of the specified attributes are unique across the system.
+ # Useful for making sure that only one user
# can be named "davidhh".
#
# class Person < ActiveRecord::Base
# validates_uniqueness_of :user_name, :scope => :account_id
# end
#
- # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
- # making sure that a teacher can only be on the schedule once per semester for a particular class.
+ # It can also validate whether the value of the specified attributes are unique based on multiple
+ # scope parameters. For example, making sure that a teacher can only be on the schedule once
+ # per semester for a particular class.
#
# class TeacherSchedule < ActiveRecord::Base
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
# end
#
- # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
- # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+ # When the record is created, a check is performed to make sure that no record exists in the database
+ # with the given value for the specified attribute (that maps to a column). When the record is updated,
+ # the same check is made but disregarding the record itself.
#
# Configuration options:
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
@@ -102,11 +105,12 @@ module ActiveRecord
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a true or false value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, proc or string should
+ # return or evaluate to a true or false value.
#
# === Concurrency and integrity
#
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index d18fed0131..a467ffa960 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 6e6645511c..509baacaef 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -42,7 +42,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
assert_equal "DROP TABLE `people`", drop_table(:people)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_create_mysql_database_with_encoding
assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
@@ -101,6 +101,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
#we need to actually modify some data, so we make execute point to the original method
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
alias_method :execute_with_stub, :execute
+ remove_method :execute
alias_method :execute, :execute_without_stub
end
yield
diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
new file mode 100644
index 0000000000..a83399d0cd
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -0,0 +1,125 @@
+require "cases/helper"
+
+class ActiveSchemaTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_without_stub, :execute
+ remove_method :execute
+ def execute(sql, name = nil) return sql end
+ end
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ end
+
+ def test_add_index
+ # add_index calls index_name_exists? which can't work since execute is stubbed
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
+ false
+ end
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ assert_equal expected, add_index(:people, :last_name, :length => nil)
+
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ assert_equal expected, add_index(:people, :last_name, :length => 10)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
+
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
+ end
+
+ def test_drop_table
+ assert_equal "DROP TABLE `people`", drop_table(:people)
+ end
+
+ if current_adapter?(:Mysql2Adapter)
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
+
+ def test_recreate_mysql_database_with_encoding
+ create_database(:luca, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'})
+ end
+ end
+
+ def test_add_column
+ assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
+ end
+
+ def test_add_column_with_limit
+ assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+ end
+
+ def test_drop_table_with_specific_database
+ assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
+ end
+
+ def test_add_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ end
+ ActiveRecord::Base.connection.add_timestamps :delete_me
+ assert column_present?('delete_me', 'updated_at', 'datetime')
+ assert column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ def test_remove_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps
+ end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me
+ assert !column_present?('delete_me', 'updated_at', 'datetime')
+ assert !column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ private
+ def with_real_execute
+ #we need to actually modify some data, so we make execute point to the original method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ alias_method :execute_with_stub, :execute
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ yield
+ ensure
+ #before finishing, we restore the alias to the mock-up method
+ ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_with_stub
+ end
+ end
+
+
+ def method_missing(method_symbol, *arguments)
+ ActiveRecord::Base.connection.send(method_symbol, *arguments)
+ end
+
+ def column_present?(table_name, column_name, type)
+ results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
+ results.first && results.first['Type'] == type
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
new file mode 100644
index 0000000000..b973da621b
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -0,0 +1,42 @@
+require "cases/helper"
+
+class MysqlConnectionTest < ActiveRecord::TestCase
+ def setup
+ super
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_no_automatic_reconnection_after_timeout
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ assert !@connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_manual_reconnect
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.reconnect!
+ assert @connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_verify
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.verify!
+ assert @connection.active?
+ end
+
+ private
+
+ def run_without_connection
+ original_connection = ActiveRecord::Base.remove_connection
+ begin
+ yield original_connection
+ ensure
+ ActiveRecord::Base.establish_connection(original_connection)
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
new file mode 100644
index 0000000000..90d8b0d923
--- /dev/null
+++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb
@@ -0,0 +1,176 @@
+require "cases/helper"
+
+class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select, :class_name => 'Select'
+ has_one :values
+end
+
+class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+end
+
+class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+end
+
+class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+end
+
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
+ # will fail with these table names if these test cases fail
+
+ create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
+ 'select'=>'id int auto_increment primary key',
+ 'values'=>'id int auto_increment primary key, group_id int',
+ 'distinct'=>'id int auto_increment primary key',
+ 'distincts_selects'=>'distinct_id int, select_id int'
+ end
+
+ def teardown
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ end
+
+ # create tables with reserved-word names and columns
+ def test_create_tables
+ assert_nothing_raised {
+ @connection.create_table :order do |t|
+ t.column :group, :string
+ end
+ }
+ end
+
+ # rename tables with reserved-word names
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ # alter column with a reserved-word name in a table with a reserved-word name
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
+ #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
+ assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ # dump structure of table with reserved word name
+ def test_structure_dump
+ assert_nothing_raised { @connection.structure_dump }
+ end
+
+ # introspect table with reserved word name
+ def test_introspect
+ assert_nothing_raised { @connection.columns(:group) }
+ assert_nothing_raised { @connection.indexes(:group) }
+ end
+
+ #fixtures
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = false
+
+ #fixtures :group
+
+ def test_fixtures
+ f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.delete_existing_fixtures
+ end
+ }
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.insert_fixtures
+ end
+ }
+ end
+
+ #activerecord model class with reserved-word table name
+ def test_activerecord_model
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ x = nil
+ assert_nothing_raised { x = Group.new }
+ x.order = 'x'
+ assert_nothing_raised { x.save }
+ x.order = 'y'
+ assert_nothing_raised { x.save }
+ assert_nothing_raised { y = Group.find_by_order('y') }
+ assert_nothing_raised { y = Group.find(1) }
+ x = Group.find(1)
+ end
+
+ # has_one association with reserved-word table name
+ def test_has_one_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ v = nil
+ assert_nothing_raised { v = Group.find(1).values }
+ assert_equal 2, v.id
+ end
+
+ # belongs_to association with reserved-word table name
+ def test_belongs_to_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ gs = nil
+ assert_nothing_raised { gs = Select.find(2).groups }
+ assert_equal gs.length, 2
+ assert(gs.collect{|x| x.id}.sort == [2, 3])
+ end
+
+ # has_and_belongs_to_many with reserved-word table name
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ s = nil
+ assert_nothing_raised { s = Distinct.find(1).selects }
+ assert_equal s.length, 2
+ assert(s.collect{|x|x.id}.sort == [1, 2])
+ end
+
+ # activerecord model introspection with reserved-word table and column names
+ def test_activerecord_introspection
+ assert_nothing_raised { Group.table_exists? }
+ assert_nothing_raised { Group.columns }
+ end
+
+ # Calculations
+ def test_calculations_work_with_reserved_words
+ assert_nothing_raised { Group.count }
+ end
+
+ def test_associations_work_with_reserved_words
+ assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ end
+
+ #the following functions were added to DRY test cases
+
+ private
+ # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+
+ # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
+ def drop_tables_directly(table_names, connection = @connection)
+ table_names.each do |name|
+ connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ end
+ end
+
+ # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
+ def create_tables_directly (tables, connection = @connection)
+ tables.each do |table_name, column_properties|
+ connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
index f106e14319..e4746d4aa3 100644
--- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -1,6 +1,6 @@
require 'cases/helper'
-class PostgresqlActiveSchemaTest < Test::Unit::TestCase
+class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
def setup
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
alias_method :real_execute, :execute
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
new file mode 100644
index 0000000000..7b72151b57
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class PostgreSQLAdapterTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_table_alias_length
+ assert_nothing_raised do
+ @connection.table_alias_length
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
index 2505372b7e..ce0b2f5f5b 100644
--- a/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite/sqlite_adapter_test.rb
@@ -103,17 +103,107 @@ module ActiveRecord
end
end
+ def test_columns
+ columns = @ctx.columns('items').sort_by { |x| x.name }
+ assert_equal 2, columns.length
+ assert_equal %w{ id number }.sort, columns.map { |x| x.name }
+ assert_equal [nil, nil], columns.map { |x| x.default }
+ assert_equal [true, true], columns.map { |x| x.null }
+ end
+
+ def test_columns_with_default
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer default 10
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert_equal 10, column.default
+ end
+
+ def test_columns_with_not_null
+ @ctx.execute <<-eosql
+ CREATE TABLE columns_with_default (
+ id integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ column = @ctx.columns('columns_with_default').find { |x|
+ x.name == 'number'
+ }
+ assert !column.null, "column should not be null"
+ end
+
+ def test_indexes_logs
+ intercept_logs_on @ctx
+ assert_difference('@ctx.logged.length') do
+ @ctx.indexes('items')
+ end
+ assert_match(/items/, @ctx.logged.last.first)
+ end
+
+ def test_no_indexes
+ assert_equal [], @ctx.indexes('items')
+ end
+
+ def test_index
+ @ctx.add_index 'items', 'id', :unique => true, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+
+ assert_equal 'items', index.table
+ assert index.unique, 'index is unique'
+ assert_equal ['id'], index.columns
+ end
+
+ def test_non_unique_index
+ @ctx.add_index 'items', 'id', :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert !index.unique, 'index is not unique'
+ end
+
+ def test_compound_index
+ @ctx.add_index 'items', %w{ id number }, :name => 'fun'
+ index = @ctx.indexes('items').find { |idx| idx.name == 'fun' }
+ assert_equal %w{ id number }.sort, index.columns.sort
+ end
+
+ def test_primary_key
+ assert_equal 'id', @ctx.primary_key('items')
+
+ @ctx.execute <<-eosql
+ CREATE TABLE foos (
+ internet integer PRIMARY KEY AUTOINCREMENT,
+ number integer not null
+ )
+ eosql
+ assert_equal 'internet', @ctx.primary_key('foos')
+ end
+
+ def test_no_primary_key
+ @ctx.execute 'CREATE TABLE failboat (number integer not null)'
+ assert_nil @ctx.primary_key('failboat')
+ end
+
+ private
+
def assert_logged logs
+ intercept_logs_on @ctx
+ yield
+ assert_equal logs, @ctx.logged
+ end
+
+ def intercept_logs_on ctx
@ctx.extend(Module.new {
- attr_reader :logged
+ attr_accessor :logged
def log sql, name
- @logged ||= []
@logged << [sql, name]
yield
end
})
- yield
- assert_equal logs, @ctx.logged
+ @ctx.logged = []
end
end
end
diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb
index 74588b4f47..9e285e57dc 100644
--- a/activerecord/test/cases/aggregations_test.rb
+++ b/activerecord/test/cases/aggregations_test.rb
@@ -125,7 +125,7 @@ class OverridingAggregationsTest < ActiveRecord::TestCase
class Name; end
class DifferentName; end
- class Person < ActiveRecord::Base
+ class Person < ActiveRecord::Base
composed_of :composed_of, :mapping => %w(person_first_name first_name)
end
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index 665c387d5d..588adc38e3 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -28,7 +28,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 7, ActiveRecord::Migrator::current_version
end
- def test_schema_raises_an_error_for_invalid_column_ntype
+ def test_schema_raises_an_error_for_invalid_column_type
assert_raise NoMethodError do
ActiveRecord::Schema.define(:version => 8) do
create_table :vegetables do |t|
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index fb1e6e7e70..a1ce9b1689 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -5,8 +5,6 @@ require 'models/company'
require 'models/topic'
require 'models/reply'
require 'models/computer'
-require 'models/customer'
-require 'models/order'
require 'models/post'
require 'models/author'
require 'models/tag'
@@ -34,7 +32,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
def test_belongs_to_with_primary_key_joins_on_correct_column
sql = Client.joins(:firm_with_primary_key).to_sql
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_no_match(/`firm_with_primary_keys_companies`\.`id`/, sql)
assert_match(/`firm_with_primary_keys_companies`\.`name`/, sql)
elsif current_adapter?(:OracleAdapter)
@@ -217,6 +215,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
r1.topic = Topic.find(t2.id)
+ assert_no_queries do
+ r1.topic = t2
+ end
+
assert r1.save
assert_equal 0, Topic.find(t1.id).replies.size
assert_equal 1, Topic.find(t2.id).replies.size
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 91b1af125e..15537d6940 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -1,8 +1,6 @@
require "cases/helper"
require 'models/post'
-require 'models/comment'
require 'models/author'
-require 'models/category'
require 'models/project'
require 'models/developer'
diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
index 9c5dcc2ad9..b93e49613d 100644
--- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
+++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -2,7 +2,6 @@ require "cases/helper"
require 'models/post'
require 'models/comment'
require 'models/author'
-require 'models/category'
require 'models/categorization'
require 'models/company'
require 'models/topic'
@@ -46,6 +45,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
assert_equal people(:michael), Person.eager_load(:primary_contact => :primary_contact).where('primary_contacts_people_2.first_name = ?', 'Susan').order('people.id').first
end
+ def test_eager_association_loading_with_join_for_count
+ authors = Author.joins(:special_posts).includes([:posts, :categorizations])
+
+ assert_nothing_raised { authors.count }
+ assert_queries(3) { authors.all }
+ end
+
def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
assert_equal 2, authors.size
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 b11969a841..ed7d9a782c 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
@@ -2,20 +2,14 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
-require 'models/topic'
-require 'models/reply'
-require 'models/computer'
require 'models/customer'
require 'models/order'
require 'models/categorization'
require 'models/category'
require 'models/post'
require 'models/author'
-require 'models/comment'
require 'models/tag'
require 'models/tagging'
-require 'models/person'
-require 'models/reader'
require 'models/parrot'
require 'models/pirate'
require 'models/treasure'
@@ -24,6 +18,8 @@ require 'models/club'
require 'models/member'
require 'models/membership'
require 'models/sponsor'
+require 'models/country'
+require 'models/treaty'
require 'active_support/core_ext/string/conversions'
class ProjectWithAfterCreateHook < ActiveRecord::Base
@@ -83,6 +79,60 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
:parrots, :pirates, :treasures, :price_estimates, :tags, :taggings
+ def setup_data_for_habtm_case
+ ActiveRecord::Base.connection.execute('delete from countries_treaties')
+
+ country = Country.new(:name => 'India')
+ country.country_id = 'c1'
+ country.save!
+
+ treaty = Treaty.new(:name => 'peace')
+ treaty.treaty_id = 't1'
+ country.treaties << treaty
+ end
+
+ def test_should_property_quote_string_primary_keys
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_equal 'c1', record[0]
+ assert_equal 't1', record[1]
+ end
+
+ def test_should_record_timestamp_for_join_table
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_not_nil record[2]
+ assert_not_nil record[3]
+ if current_adapter?(:Mysql2Adapter)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db)
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db)
+ else
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
+ assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
+ end
+ end
+
+ def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded
+ begin
+ Treaty.record_timestamps = false
+ setup_data_for_habtm_case
+
+ con = ActiveRecord::Base.connection
+ sql = 'select * from countries_treaties'
+ record = con.select_rows(sql).last
+ assert_nil record[2]
+ assert_nil record[3]
+ ensure
+ Treaty.record_timestamps = true
+ end
+ end
+
def test_has_and_belongs_to_many
david = Developer.find(1)
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index a52cedd8c2..ac2021c369 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -11,53 +11,50 @@ require 'models/comment'
require 'models/person'
require 'models/reader'
require 'models/tagging'
+require 'models/invoice'
+require 'models/line_item'
-class HasManyAssociationsTest < ActiveRecord::TestCase
- fixtures :accounts, :categories, :companies, :developers, :projects,
- :developers_projects, :topics, :authors, :comments,
- :people, :posts, :readers, :taggings
-
- def setup
- Client.destroyed_client_ids.clear
+class HasManyAssociationsTestForCountWithFinderSql < ActiveRecord::TestCase
+ class Invoice < ActiveRecord::Base
+ has_many :custom_line_items, :class_name => 'LineItem', :finder_sql => "SELECT line_items.* from line_items"
end
+ def test_should_fail
+ assert_raise(ArgumentError) do
+ Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
+ end
+ end
+end
- def test_create_by
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
-
- assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id post.id
-
- reader = person.readers.create_by_post_id post.id
-
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
+class HasManyAssociationsTestForCountWithCountSql < ActiveRecord::TestCase
+ class Invoice < ActiveRecord::Base
+ has_many :custom_line_items, :class_name => 'LineItem', :counter_sql => "SELECT COUNT(*) line_items.* from line_items"
end
+ def test_should_fail
+ assert_raise(ArgumentError) do
+ Invoice.create.custom_line_items.count(:conditions => {:amount => 0})
+ end
+ end
+end
- def test_create_by_multi
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
- assert_equal [], person.readers
- reader = person.readers.create_by_post_id_and_skimmer post.id, false
+class HasManyAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :categories, :companies, :developers, :projects,
+ :developers_projects, :topics, :authors, :comments,
+ :people, :posts, :readers, :taggings
- assert_equal 1, person.readers.count
- assert_equal 1, person.readers.length
- assert_equal post, person.readers.first.post
- assert_equal person, person.readers.first.person
+ def setup
+ Client.destroyed_client_ids.clear
end
- def test_find_or_create_by
- person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
+ def test_create_resets_cached_counters
+ person = Person.create!(:first_name => 'tenderlove')
+ post = Post.first
assert_equal [], person.readers
- assert_nil person.readers.find_by_post_id post.id
+ assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.find_or_create_by_post_id post.id
+ reader = person.readers.create(:post_id => post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -65,16 +62,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
- def test_find_or_create
+ def test_find_or_create_by_resets_cached_counters
person = Person.create! :first_name => 'tenderlove'
- post = Post.find :first
+ post = Post.first
assert_equal [], person.readers
- assert_nil person.readers.find(:first, :conditions => {
- :post_id => post.id
- })
+ assert_nil person.readers.find_by_post_id(post.id)
- reader = person.readers.find_or_create :post_id => post.id
+ reader = person.readers.find_or_create_by_post_id(post.id)
assert_equal 1, person.readers.count
assert_equal 1, person.readers.length
@@ -82,7 +77,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal person, person.readers.first.person
end
-
def force_signal37_to_load_all_clients_of_firm
companies(:first_firm).clients_of_firm.each {|f| }
end
@@ -173,6 +167,15 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
end
+ def test_dynamic_find_or_create_from_two_attributes_using_an_association
+ author = authors(:david)
+ number_of_posts = Post.count
+ another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert_equal number_of_posts + 1, Post.count
+ assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body")
+ assert !another.new_record?
+ end
+
def test_cant_save_has_many_readonly_association
authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
authors(:david).readonly_comments.each { |c| assert c.readonly? }
@@ -549,7 +552,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert the_client.new_record?
end
- def test_find_or_create
+ def test_find_or_create_updates_size
number_of_clients = companies(:first_firm).clients.size
the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
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 e4dd810732..0eaadac5ae 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -14,9 +14,14 @@ require 'models/toy'
require 'models/contract'
require 'models/company'
require 'models/developer'
+require 'models/subscriber'
+require 'models/book'
+require 'models/subscription'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies
+ fixtures :posts, :readers, :people, :comments, :authors,
+ :owners, :pets, :toys, :jobs, :references, :companies,
+ :subscribers, :books, :subscriptions, :developers
# Dummies to force column loads so query counts are clean.
def setup
@@ -383,4 +388,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+
+ def test_collection_singular_ids_getter_with_string_primary_keys
+ book = books(:awdr)
+ assert_equal 2, book.subscriber_ids.size
+ assert_equal [subscribers(:first).nick, subscribers(:second).nick].sort, book.subscriber_ids.sort
+ end
+
+ def test_collection_singular_ids_setter
+ company = companies(:rails_core)
+ dev = Developer.find(:first)
+
+ company.developer_ids = [dev.id]
+ assert_equal [dev], company.developers
+ end
+
+ def test_collection_singular_ids_setter_with_string_primary_keys
+ assert_nothing_raised do
+ book = books(:awdr)
+ book.subscriber_ids = [subscribers(:second).nick]
+ assert_equal [subscribers(:second)], book.subscribers(true)
+
+ book.subscriber_ids = []
+ assert_equal [], book.subscribers(true)
+ end
+
+ end
+
+ def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
+ company = companies(:rails_core)
+ ids = [Developer.find(:first).id, -9999]
+ assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
+ end
+
end
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 178c57435b..3fcd150422 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -6,9 +6,12 @@ require 'models/membership'
require 'models/sponsor'
require 'models/organization'
require 'models/member_detail'
+require 'models/minivan'
+require 'models/dashboard'
+require 'models/speedometer'
class HasOneThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations
+ fixtures :member_types, :members, :clubs, :memberships, :sponsors, :organizations, :minivans, :dashboards, :speedometers
def setup
@member = members(:groucho)
@@ -202,4 +205,11 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
Club.find(@club.id, :include => :sponsored_member).save!
end
end
+
+ def test_value_is_properly_quoted
+ minivan = Minivan.find('m1')
+ assert_nothing_raised do
+ minivan.dashboard
+ end
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 34d24a2948..fa5c2e49df 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -412,7 +412,7 @@ class InverseBelongsToTests < ActiveRecord::TestCase
i = interests(:trainspotting)
m = i.man
assert_not_nil m.interests
- iz = m.interests.detect {|iz| iz.id == i.id}
+ iz = m.interests.detect { |_iz| _iz.id == i.id}
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = 'Eating cheese with a spoon'
@@ -516,7 +516,7 @@ class InversePolymorphicBelongsToTests < ActiveRecord::TestCase
i = interests(:llama_wrangling)
m = i.polymorphic_man
assert_not_nil m.polymorphic_interests
- iz = m.polymorphic_interests.detect {|iz| iz.id == i.id}
+ iz = m.polymorphic_interests.detect { |_iz| _iz.id == i.id}
assert_not_nil iz
assert_equal i.topic, iz.topic, "Interest topics should be the same before changes to child"
i.topic = 'Eating cheese with a spoon'
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 4ae776c35a..b31611e27a 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -2,11 +2,6 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
-require 'models/topic'
-require 'models/reply'
-require 'models/computer'
-require 'models/customer'
-require 'models/order'
require 'models/categorization'
require 'models/category'
require 'models/post'
@@ -17,18 +12,66 @@ require 'models/tagging'
require 'models/person'
require 'models/reader'
require 'models/parrot'
-require 'models/pirate'
-require 'models/treasure'
-require 'models/price_estimate'
-require 'models/club'
-require 'models/member'
-require 'models/membership'
-require 'models/sponsor'
+require 'models/ship_part'
+require 'models/ship'
+require 'models/liquid'
+require 'models/molecule'
+require 'models/electron'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
:computers, :people, :readers
+ def test_eager_loading_should_not_change_count_of_children
+ liquid = Liquid.create(:name => 'salty')
+ molecule = liquid.molecules.create(:name => 'molecule_1')
+ molecule.electrons.create(:name => 'electron_1')
+ molecule.electrons.create(:name => 'electron_2')
+
+ liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null')
+ assert_equal 1, liquids[0].molecules.length
+ end
+
+ def test_clear_association_cache_stored
+ firm = Firm.find(1)
+ assert_kind_of Firm, firm
+
+ firm.clear_association_cache
+ assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
+ end
+
+ def test_clear_association_cache_new_record
+ firm = Firm.new
+ client_stored = Client.find(3)
+ client_new = Client.new
+ client_new.name = "The Joneses"
+ clients = [ client_stored, client_new ]
+
+ firm.clients << clients
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+
+ firm.clear_association_cache
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+ end
+
+ def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
+ ship = Ship.create!(:name => "The good ship Dollypop")
+ part = ship.parts.create!(:name => "Mast")
+ part.mark_for_destruction
+ ship.parts.send(:load_target)
+ assert ship.parts[0].marked_for_destruction?
+ end
+
+ def test_loading_the_association_target_should_load_most_recent_attributes_for_child_records_marked_for_destruction
+ ship = Ship.create!(:name => "The good ship Dollypop")
+ part = ship.parts.create!(:name => "Mast")
+ part.mark_for_destruction
+ ShipPart.find(part.id).update_attribute(:name, 'Deck')
+ ship.parts.send(:load_target)
+ assert_equal 'Deck', ship.parts[0].name
+ end
+
+
def test_include_with_order_works
assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d59fa0a632..2c069cd8a5 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -1,9 +1,15 @@
require "cases/helper"
-require 'models/topic'
require 'models/minimalistic'
+require 'models/developer'
+require 'models/auto_id'
+require 'models/computer'
+require 'models/topic'
+require 'models/company'
+require 'models/category'
+require 'models/reply'
class AttributeMethodsTest < ActiveRecord::TestCase
- fixtures :topics
+ fixtures :topics, :developers, :companies, :computers
def setup
@old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup
@@ -16,6 +22,276 @@ class AttributeMethodsTest < ActiveRecord::TestCase
ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers)
end
+ def test_attribute_present
+ t = Topic.new
+ t.title = "hello there!"
+ t.written_on = Time.now
+ assert t.attribute_present?("title")
+ assert t.attribute_present?("written_on")
+ assert !t.attribute_present?("content")
+ end
+
+ def test_attribute_keys_on_new_instance
+ t = Topic.new
+ assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
+ assert_raise(NoMethodError) { t.title2 }
+ end
+
+ def test_boolean_attributes
+ assert ! Topic.find(1).approved?
+ assert Topic.find(2).approved?
+ end
+
+ def test_set_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
+ topic.save
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
+ end
+
+ def test_set_attributes_without_hash
+ topic = Topic.new
+ assert_nothing_raised { topic.attributes = '' }
+ end
+
+ def test_integers_as_nil
+ test = AutoId.create('value' => '')
+ assert_nil AutoId.find(test.id).value
+ end
+
+ def test_set_attributes_with_block
+ topic = Topic.new do |t|
+ t.title = "Budget"
+ t.author_name = "Jason"
+ end
+
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ end
+
+ def test_respond_to?
+ topic = Topic.find(1)
+ assert_respond_to topic, "title"
+ assert_respond_to topic, "title?"
+ assert_respond_to topic, "title="
+ assert_respond_to topic, :title
+ assert_respond_to topic, :title?
+ assert_respond_to topic, :title=
+ assert_respond_to topic, "author_name"
+ assert_respond_to topic, "attribute_names"
+ assert !topic.respond_to?("nothingness")
+ assert !topic.respond_to?(:nothingness)
+ end
+
+ def test_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+ topic.save
+
+ assert_equal(%w( one two three ), Topic.find(topic.id).content)
+ end
+
+ def test_read_attributes_before_type_cast
+ category = Category.new({:name=>"Test categoty", :type => nil})
+ category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil}
+ assert_equal category_attrs , category.attributes_before_type_cast
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_read_attributes_before_type_cast_on_boolean
+ bool = Booleantest.create({ "value" => false })
+ assert_equal "0", bool.reload.attributes_before_type_cast["value"]
+ end
+ end
+
+ unless current_adapter?(:Mysql2Adapter)
+ def test_read_attributes_before_type_cast_on_datetime
+ developer = Developer.find(:first)
+ # Oracle adapter returns Time before type cast
+ unless current_adapter?(:OracleAdapter)
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
+ else
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
+
+ developer.created_at = "345643456"
+ assert_equal developer.created_at_before_type_cast, "345643456"
+ assert_equal developer.created_at, nil
+
+ developer.created_at = "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at_before_type_cast, "2010-03-21T21:23:32+01:00"
+ assert_equal developer.created_at, Time.parse("2010-03-21T21:23:32+01:00")
+ end
+ end
+ end
+
+ def test_hash_content
+ topic = Topic.new
+ topic.content = { "one" => 1, "two" => 2 }
+ topic.save
+
+ assert_equal 2, Topic.find(topic.id).content["two"]
+
+ topic.content_will_change!
+ topic.content["three"] = 3
+ topic.save
+
+ assert_equal 3, Topic.find(topic.id).content["three"]
+ end
+
+ def test_update_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+
+ topic.content.push "four"
+ assert_equal(%w( one two three four ), topic.content)
+
+ topic.save
+
+ topic = Topic.find(topic.id)
+ topic.content << "five"
+ assert_equal(%w( one two three four five ), topic.content)
+ end
+
+ def test_case_sensitive_attributes_hash
+ # DB2 is not case-sensitive
+ return true if current_adapter?(:DB2Adapter)
+
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
+ end
+
+ def test_hashes_not_mangled
+ new_topic = { :title => "New Topic" }
+ new_topic_values = { :title => "AnotherTopic" }
+
+ topic = Topic.new(new_topic)
+ assert_equal new_topic[:title], topic.title
+
+ topic.attributes= new_topic_values
+ assert_equal new_topic_values[:title], topic.title
+ end
+
+ def test_create_through_factory
+ topic = Topic.create("title" => "New Topic")
+ topicReloaded = Topic.find(topic.id)
+ assert_equal(topic, topicReloaded)
+ end
+
+ def test_write_attribute
+ topic = Topic.new
+ topic.send(:write_attribute, :title, "Still another topic")
+ assert_equal "Still another topic", topic.title
+
+ topic.send(:write_attribute, "title", "Still another topic: part 2")
+ assert_equal "Still another topic: part 2", topic.title
+ end
+
+ def test_read_attribute
+ topic = Topic.new
+ topic.title = "Don't change the topic"
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic["title"]
+
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic[:title]
+ end
+
+ def test_read_attribute_when_false
+ topic = topics(:first)
+ topic.approved = false
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ assert !topic.approved?, "approved should be false"
+ end
+
+ def test_read_attribute_when_true
+ topic = topics(:first)
+ topic.approved = true
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ assert topic.approved?, "approved should be true"
+ end
+
+ def test_read_write_boolean_attribute
+ topic = Topic.new
+ # puts ""
+ # puts "New Topic"
+ # puts topic.inspect
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ # puts ""
+ end
+
+ def test_query_attribute_string
+ [nil, "", " "].each do |value|
+ assert_equal false, Topic.new(:author_name => value).author_name?
+ end
+
+ assert_equal true, Topic.new(:author_name => "Name").author_name?
+ end
+
+ def test_query_attribute_number
+ [nil, 0, "0"].each do |value|
+ assert_equal false, Developer.new(:salary => value).salary?
+ end
+
+ assert_equal true, Developer.new(:salary => 1).salary?
+ assert_equal true, Developer.new(:salary => "1").salary?
+ end
+
+ def test_query_attribute_boolean
+ [nil, "", false, "false", "f", 0].each do |value|
+ assert_equal false, Topic.new(:approved => value).approved?
+ end
+
+ [true, "true", "1", 1].each do |value|
+ assert_equal true, Topic.new(:approved => value).approved?
+ end
+ end
+
+ def test_query_attribute_with_custom_fields
+ object = Company.find_by_sql(<<-SQL).first
+ SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
+ FROM companies c1, companies c2
+ WHERE c1.firm_id = c2.id
+ AND c1.id = 2
+ SQL
+
+ assert_equal "Firm", object.string_value
+ assert object.string_value?
+
+ object.string_value = " "
+ assert !object.string_value?
+
+ assert_equal 1, object.int_value.to_i
+ assert object.int_value?
+
+ object.int_value = "0"
+ assert !object.int_value?
+ end
+
+ def test_non_attribute_access_and_assignment
+ topic = Topic.new
+ assert !topic.respond_to?("mumbo")
+ assert_raise(NoMethodError) { topic.mumbo }
+ assert_raise(NoMethodError) { topic.mumbo = 5 }
+ end
+
def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing
topic = @target.new(:title => 'Budget')
assert topic.respond_to?('title')
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 3b89c12a3f..49e7147773 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -13,6 +13,8 @@ require 'models/post'
require 'models/reader'
require 'models/ship'
require 'models/ship_part'
+require 'models/tag'
+require 'models/tagging'
require 'models/treasure'
require 'models/company'
@@ -171,7 +173,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
end
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
- fixtures :companies
+ fixtures :companies, :posts, :tags, :taggings
def test_should_save_parent_but_not_invalid_child
client = Client.new(:name => 'Joe (the Plumber)')
@@ -312,6 +314,12 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert_equal num_orders +1, Order.count
assert_equal num_customers +2, Customer.count
end
+
+ def test_store_association_with_a_polymorphic_relationship
+ num_tagging = Tagging.count
+ tags(:misc).create_tagging(:taggable => posts(:thinking))
+ assert_equal num_tagging +1, Tagging.count
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index a4cf5120e1..ca397d3847 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -52,357 +52,6 @@ class BasicsTest < ActiveRecord::TestCase
assert Topic.table_exists?
end
- def test_set_attributes
- topic = Topic.find(1)
- topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
- topic.save
- assert_equal("Budget", topic.title)
- assert_equal("Jason", topic.author_name)
- assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
- end
-
- def test_set_attributes_without_hash
- topic = Topic.new
- assert_nothing_raised do
- topic.attributes = ''
- end
- end
-
- def test_integers_as_nil
- test = AutoId.create('value' => '')
- assert_nil AutoId.find(test.id).value
- end
-
- def test_set_attributes_with_block
- topic = Topic.new do |t|
- t.title = "Budget"
- t.author_name = "Jason"
- end
-
- assert_equal("Budget", topic.title)
- assert_equal("Jason", topic.author_name)
- end
-
- def test_respond_to?
- topic = Topic.find(1)
- assert_respond_to topic, "title"
- assert_respond_to topic, "title?"
- assert_respond_to topic, "title="
- assert_respond_to topic, :title
- assert_respond_to topic, :title?
- assert_respond_to topic, :title=
- assert_respond_to topic, "author_name"
- assert_respond_to topic, "attribute_names"
- assert !topic.respond_to?("nothingness")
- assert !topic.respond_to?(:nothingness)
- end
-
- def test_array_content
- topic = Topic.new
- topic.content = %w( one two three )
- topic.save
-
- assert_equal(%w( one two three ), Topic.find(topic.id).content)
- end
-
- def test_read_attributes_before_type_cast
- category = Category.new({:name=>"Test categoty", :type => nil})
- category_attrs = {"name"=>"Test categoty", "type" => nil, "categorizations_count" => nil}
- assert_equal category_attrs , category.attributes_before_type_cast
- end
-
- if current_adapter?(:MysqlAdapter)
- def test_read_attributes_before_type_cast_on_boolean
- bool = Booleantest.create({ "value" => false })
- assert_equal "0", bool.reload.attributes_before_type_cast["value"]
- end
- end
-
- def test_read_attributes_before_type_cast_on_datetime
- developer = Developer.find(:first)
- # Oracle adapter returns Time before type cast
- unless current_adapter?(:OracleAdapter)
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
- else
- assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"].to_s(:db)
- end
- end
-
- def test_hash_content
- topic = Topic.new
- topic.content = { "one" => 1, "two" => 2 }
- topic.save
-
- assert_equal 2, Topic.find(topic.id).content["two"]
-
- topic.content_will_change!
- topic.content["three"] = 3
- topic.save
-
- assert_equal 3, Topic.find(topic.id).content["three"]
- end
-
- def test_update_array_content
- topic = Topic.new
- topic.content = %w( one two three )
-
- topic.content.push "four"
- assert_equal(%w( one two three four ), topic.content)
-
- topic.save
-
- topic = Topic.find(topic.id)
- topic.content << "five"
- assert_equal(%w( one two three four five ), topic.content)
- end
-
- def test_case_sensitive_attributes_hash
- # DB2 is not case-sensitive
- return true if current_adapter?(:DB2Adapter)
-
- assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
- end
-
- def test_create
- topic = Topic.new
- topic.title = "New Topic"
- topic.save
- topic_reloaded = Topic.find(topic.id)
- assert_equal("New Topic", topic_reloaded.title)
- end
-
- def test_save!
- topic = Topic.new(:title => "New Topic")
- assert topic.save!
-
- reply = WrongReply.new
- assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
- end
-
- def test_save_null_string_attributes
- topic = Topic.find(1)
- topic.attributes = { "title" => "null", "author_name" => "null" }
- topic.save!
- topic.reload
- assert_equal("null", topic.title)
- assert_equal("null", topic.author_name)
- end
-
- def test_save_nil_string_attributes
- topic = Topic.find(1)
- topic.title = nil
- topic.save!
- topic.reload
- assert_nil topic.title
- end
-
- def test_save_for_record_with_only_primary_key
- minimalistic = Minimalistic.new
- assert_nothing_raised { minimalistic.save }
- end
-
- def test_save_for_record_with_only_primary_key_that_is_provided
- assert_nothing_raised { Minimalistic.create!(:id => 2) }
- end
-
- def test_hashes_not_mangled
- new_topic = { :title => "New Topic" }
- new_topic_values = { :title => "AnotherTopic" }
-
- topic = Topic.new(new_topic)
- assert_equal new_topic[:title], topic.title
-
- topic.attributes= new_topic_values
- assert_equal new_topic_values[:title], topic.title
- end
-
- def test_create_many
- topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
- assert_equal 2, topics.size
- assert_equal "first", topics.first.title
- end
-
- def test_create_columns_not_equal_attributes
- topic = Topic.new
- topic.title = 'Another New Topic'
- topic.send :write_attribute, 'does_not_exist', 'test'
- assert_nothing_raised { topic.save }
- end
-
- def test_create_through_factory
- topic = Topic.create("title" => "New Topic")
- topicReloaded = Topic.find(topic.id)
- assert_equal(topic, topicReloaded)
- end
-
- def test_create_through_factory_with_block
- topic = Topic.create("title" => "New Topic") do |t|
- t.author_name = "David"
- end
- topicReloaded = Topic.find(topic.id)
- assert_equal("New Topic", topic.title)
- assert_equal("David", topic.author_name)
- end
-
- def test_create_many_through_factory_with_block
- topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t|
- t.author_name = "David"
- end
- assert_equal 2, topics.size
- topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id)
- assert_equal "first", topic1.title
- assert_equal "David", topic1.author_name
- assert_equal "second", topic2.title
- assert_equal "David", topic2.author_name
- end
-
- def test_update
- topic = Topic.new
- topic.title = "Another New Topic"
- topic.written_on = "2003-12-12 23:23:00"
- topic.save
- topicReloaded = Topic.find(topic.id)
- assert_equal("Another New Topic", topicReloaded.title)
-
- topicReloaded.title = "Updated topic"
- topicReloaded.save
-
- topicReloadedAgain = Topic.find(topic.id)
-
- assert_equal("Updated topic", topicReloadedAgain.title)
- end
-
- def test_update_columns_not_equal_attributes
- topic = Topic.new
- topic.title = "Still another topic"
- topic.save
-
- topicReloaded = Topic.find(topic.id)
- topicReloaded.title = "A New Topic"
- topicReloaded.send :write_attribute, 'does_not_exist', 'test'
- assert_nothing_raised { topicReloaded.save }
- end
-
- def test_update_for_record_with_only_primary_key
- minimalistic = minimalistics(:first)
- assert_nothing_raised { minimalistic.save }
- end
-
- def test_write_attribute
- topic = Topic.new
- topic.send(:write_attribute, :title, "Still another topic")
- assert_equal "Still another topic", topic.title
-
- topic.send(:write_attribute, "title", "Still another topic: part 2")
- assert_equal "Still another topic: part 2", topic.title
- end
-
- def test_read_attribute
- topic = Topic.new
- topic.title = "Don't change the topic"
- assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
- assert_equal "Don't change the topic", topic["title"]
-
- assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
- assert_equal "Don't change the topic", topic[:title]
- end
-
- def test_read_attribute_when_false
- topic = topics(:first)
- topic.approved = false
- assert !topic.approved?, "approved should be false"
- topic.approved = "false"
- assert !topic.approved?, "approved should be false"
- end
-
- def test_read_attribute_when_true
- topic = topics(:first)
- topic.approved = true
- assert topic.approved?, "approved should be true"
- topic.approved = "true"
- assert topic.approved?, "approved should be true"
- end
-
- def test_read_write_boolean_attribute
- topic = Topic.new
- # puts ""
- # puts "New Topic"
- # puts topic.inspect
- topic.approved = "false"
- # puts "Expecting false"
- # puts topic.inspect
- assert !topic.approved?, "approved should be false"
- topic.approved = "false"
- # puts "Expecting false"
- # puts topic.inspect
- assert !topic.approved?, "approved should be false"
- topic.approved = "true"
- # puts "Expecting true"
- # puts topic.inspect
- assert topic.approved?, "approved should be true"
- topic.approved = "true"
- # puts "Expecting true"
- # puts topic.inspect
- assert topic.approved?, "approved should be true"
- # puts ""
- end
-
- def test_query_attribute_string
- [nil, "", " "].each do |value|
- assert_equal false, Topic.new(:author_name => value).author_name?
- end
-
- assert_equal true, Topic.new(:author_name => "Name").author_name?
- end
-
- def test_query_attribute_number
- [nil, 0, "0"].each do |value|
- assert_equal false, Developer.new(:salary => value).salary?
- end
-
- assert_equal true, Developer.new(:salary => 1).salary?
- assert_equal true, Developer.new(:salary => "1").salary?
- end
-
- def test_query_attribute_boolean
- [nil, "", false, "false", "f", 0].each do |value|
- assert_equal false, Topic.new(:approved => value).approved?
- end
-
- [true, "true", "1", 1].each do |value|
- assert_equal true, Topic.new(:approved => value).approved?
- end
- end
-
- def test_query_attribute_with_custom_fields
- object = Company.find_by_sql(<<-SQL).first
- SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
- FROM companies c1, companies c2
- WHERE c1.firm_id = c2.id
- AND c1.id = 2
- SQL
-
- assert_equal "Firm", object.string_value
- assert object.string_value?
-
- object.string_value = " "
- assert !object.string_value?
-
- assert_equal 1, object.int_value.to_i
- assert object.int_value?
-
- object.int_value = "0"
- assert !object.int_value?
- end
-
-
- def test_non_attribute_access_and_assignment
- topic = Topic.new
- assert !topic.respond_to?("mumbo")
- assert_raise(NoMethodError) { topic.mumbo }
- assert_raise(NoMethodError) { topic.mumbo = 5 }
- end
-
def test_preserving_date_objects
if current_adapter?(:SybaseAdapter)
# Sybase ctlib does not (yet?) support the date type; use datetime instead.
@@ -499,29 +148,6 @@ class BasicsTest < ActiveRecord::TestCase
assert topic.instance_variable_get("@custom_approved")
end
- def test_delete
- topic = Topic.find(1)
- assert_equal topic, topic.delete, 'topic.delete did not return self'
- assert topic.frozen?, 'topic not frozen after delete'
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
- end
-
- def test_delete_doesnt_run_callbacks
- Topic.find(1).delete
- assert_not_nil Topic.find(2)
- end
-
- def test_destroy
- topic = Topic.find(1)
- assert_equal topic, topic.destroy, 'topic.destroy did not return self'
- assert topic.frozen?, 'topic not frozen after destroy'
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
- end
-
- def test_record_not_found_exception
- assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
- end
-
def test_initialize_with_attributes
topic = Topic.new({
"title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
@@ -657,129 +283,13 @@ class BasicsTest < ActiveRecord::TestCase
GUESSED_CLASSES.each(&:reset_table_name)
end
- def test_destroy_all
- conditions = "author_name = 'Mary'"
- topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
- assert ! topics_by_mary.empty?
-
- assert_difference('Topic.count', -topics_by_mary.size) do
- destroyed = Topic.destroy_all(conditions).sort_by(&:id)
- assert_equal topics_by_mary, destroyed
- assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
- end
- end
-
- def test_destroy_many
- clients = Client.find([2, 3], :order => 'id')
- assert_difference('Client.count', -2) do
- destroyed = Client.destroy([2, 3]).sort_by(&:id)
- assert_equal clients, destroyed
- assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
- end
- end
-
- def test_delete_many
- original_count = Topic.count
- Topic.delete(deleting = [1, 2])
- assert_equal original_count - deleting.size, Topic.count
- end
-
- def test_boolean_attributes
- assert ! Topic.find(1).approved?
- assert Topic.find(2).approved?
- end
-
- def test_update_all
- assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
- assert_equal "bulk updated!", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
-
- assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!'])
- assert_equal "bulk updated again!", Topic.find(1).content
- assert_equal "bulk updated again!", Topic.find(2).content
-
- assert_equal Topic.count, Topic.update_all(['content = ?', nil])
- assert_nil Topic.find(1).content
- end
-
- def test_update_all_with_hash
- assert_not_nil Topic.find(1).last_read
- assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
- assert_equal "bulk updated with hash!", Topic.find(1).content
- assert_equal "bulk updated with hash!", Topic.find(2).content
- assert_nil Topic.find(1).last_read
- assert_nil Topic.find(2).last_read
- end
-
- def test_update_all_with_non_standard_table_name
- assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
- assert_equal 0, WarehouseThing.find(1).value
- end
-
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_update_all_with_order_and_limit
assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
end
end
- # Oracle UPDATE does not support ORDER BY
- unless current_adapter?(:OracleAdapter)
- def test_update_all_ignores_order_without_limit_from_association
- author = authors(:david)
- assert_nothing_raised do
- assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
- end
- end
-
- def test_update_all_with_order_and_limit_updates_subset_only
- author = authors(:david)
- assert_nothing_raised do
- assert_equal 1, author.posts_sorted_by_id_limited.size
- assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
- assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
- assert_equal "bulk update!", posts(:welcome).body
- assert_not_equal "bulk update!", posts(:thinking).body
- end
- end
- end
-
- def test_update_many
- topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
- updated = Topic.update(topic_data.keys, topic_data.values)
-
- assert_equal 2, updated.size
- assert_equal "1 updated", Topic.find(1).content
- assert_equal "2 updated", Topic.find(2).content
- end
-
- def test_delete_all
- assert Topic.count > 0
-
- assert_equal Topic.count, Topic.delete_all
- end
-
- def test_update_by_condition
- Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
- assert_equal "Have a nice day", Topic.find(1).content
- assert_equal "bulk updated!", Topic.find(2).content
- end
-
- def test_attribute_present
- t = Topic.new
- t.title = "hello there!"
- t.written_on = Time.now
- assert t.attribute_present?("title")
- assert t.attribute_present?("written_on")
- assert !t.attribute_present?("content")
- end
-
- def test_attribute_keys_on_new_instance
- t = Topic.new
- assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
- assert_raise(NoMethodError) { t.title2 }
- end
-
def test_null_fields
assert_nil Topic.find(1).parent_id
assert_nil Topic.create("title" => "Hey you").parent_id
@@ -863,120 +373,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
end
- def test_delete_new_record
- client = Client.new
- client.delete
- assert client.frozen?
- end
-
- def test_delete_record_with_associations
- client = Client.find(3)
- client.delete
- assert client.frozen?
- assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
- end
-
- def test_destroy_new_record
- client = Client.new
- client.destroy
- assert client.frozen?
- end
-
- def test_destroy_record_with_associations
- client = Client.find(3)
- client.destroy
- assert client.frozen?
- assert_kind_of Firm, client.firm
- assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
- end
-
- def test_update_attribute
- assert !Topic.find(1).approved?
- Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
-
- Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
- end
-
- def test_update_attribute_with_one_changed_and_one_updated
- t = Topic.order('id').limit(1).first
- title, author_name = t.title, t.author_name
- t.author_name = 'John'
- t.update_attribute(:title, 'super_title')
- assert_equal 'John', t.author_name
- assert_equal 'super_title', t.title
- assert t.changed?, "topic should have changed"
- assert t.author_name_changed?, "author_name should have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
- assert_equal ['author_name'], t.changed
-
- t.reload
- assert_equal 'David', t.author_name
- assert_equal 'super_title', t.title
- end
-
- def test_update_attribute_with_one_updated
- t = Topic.first
- title = t.title
- t.update_attribute(:title, 'super_title')
- assert_equal 'super_title', t.title
- assert !t.changed?, "topic should not have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
-
- t.reload
- assert_equal 'super_title', t.title
- end
-
- def test_update_attribute_for_udpated_at_on
- developer = Developer.find(1)
- updated_at = developer.updated_at
- developer.update_attribute(:salary, 80001)
- assert_not_equal updated_at, developer.updated_at
- developer.reload
- assert_not_equal updated_at, developer.updated_at
- end
-
- def test_update_attributes
- topic = Topic.find(1)
- assert !topic.approved?
- assert_equal "The First Topic", topic.title
-
- topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
- topic.reload
- assert topic.approved?
- assert_equal "The First Topic Updated", topic.title
-
- topic.update_attributes(:approved => false, :title => "The First Topic")
- topic.reload
- assert !topic.approved?
- assert_equal "The First Topic", topic.title
- end
-
- def test_update_attributes!
- Reply.validates_presence_of(:title)
- reply = Reply.find(2)
- assert_equal "The Second Topic of the day", reply.title
- assert_equal "Have a nice day", reply.content
-
- reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
- reply.reload
- assert_equal "The Second Topic of the day updated", reply.title
- assert_equal "Have a nice evening", reply.content
-
- reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
- reply.reload
- assert_equal "The Second Topic of the day", reply.title
- assert_equal "Have a nice day", reply.content
-
- assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
- ensure
- Reply.reset_callbacks(:validate)
- end
-
def test_readonly_attributes
assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
@@ -1236,35 +632,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal false, Topic.find(1).new_record?
end
- def test_destroyed_returns_boolean
- developer = Developer.first
- assert_equal false, developer.destroyed?
- developer.destroy
- assert_equal true, developer.destroyed?
-
- developer = Developer.last
- assert_equal false, developer.destroyed?
- developer.delete
- assert_equal true, developer.destroyed?
- end
-
- def test_persisted_returns_boolean
- developer = Developer.new(:name => "Jose")
- assert_equal false, developer.persisted?
- developer.save!
- assert_equal true, developer.persisted?
-
- developer = Developer.first
- assert_equal true, developer.persisted?
- developer.destroy
- assert_equal false, developer.persisted?
-
- developer = Developer.last
- assert_equal true, developer.persisted?
- developer.delete
- assert_equal false, developer.persisted?
- end
-
def test_clone
topic = Topic.find(1)
cloned_topic = nil
@@ -1607,67 +974,6 @@ class BasicsTest < ActiveRecord::TestCase
end
end
- def test_class_level_destroy
- should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
- Topic.find(1).replies << should_be_destroyed_reply
-
- Topic.destroy(1)
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
- end
-
- def test_class_level_delete
- should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
- Topic.find(1).replies << should_be_destroyed_reply
-
- Topic.delete(1)
- assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
- assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
- end
-
- def test_increment_attribute
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).increment! :credit_limit
- assert_equal 51, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
- assert_equal 53, accounts(:signals37, :reload).credit_limit
- end
-
- def test_increment_nil_attribute
- assert_nil topics(:first).parent_id
- topics(:first).increment! :parent_id
- assert_equal 1, topics(:first).parent_id
- end
-
- def test_increment_attribute_by
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).increment! :credit_limit, 5
- assert_equal 55, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3)
- assert_equal 59, accounts(:signals37, :reload).credit_limit
- end
-
- def test_decrement_attribute
- assert_equal 50, accounts(:signals37).credit_limit
-
- accounts(:signals37).decrement!(:credit_limit)
- assert_equal 49, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
- assert_equal 47, accounts(:signals37, :reload).credit_limit
- end
-
- def test_decrement_attribute_by
- assert_equal 50, accounts(:signals37).credit_limit
- accounts(:signals37).decrement! :credit_limit, 5
- assert_equal 45, accounts(:signals37, :reload).credit_limit
-
- accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3)
- assert_equal 41, accounts(:signals37, :reload).credit_limit
- end
-
def test_toggle_attribute
assert !topics(:first).approved?
topics(:first).toggle!(:approved)
@@ -1794,28 +1100,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal res6, res7
end
- def test_clear_association_cache_stored
- firm = Firm.find(1)
- assert_kind_of Firm, firm
-
- firm.clear_association_cache
- assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
- end
-
- def test_clear_association_cache_new_record
- firm = Firm.new
- client_stored = Client.find(3)
- client_new = Client.new
- client_new.name = "The Joneses"
- clients = [ client_stored, client_new ]
-
- firm.clients << clients
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
-
- firm.clear_association_cache
- assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
- end
-
def test_interpolate_sql
assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
@@ -2015,134 +1299,6 @@ class BasicsTest < ActiveRecord::TestCase
assert_no_queries { assert true }
end
- def test_to_xml
- xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
- bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
- written_on_in_current_timezone = topics(:first).written_on.xmlschema
- last_read_in_current_timezone = topics(:first).last_read.xmlschema
-
- assert_equal "topic", xml.root.name
- assert_equal "The First Topic" , xml.elements["//title"].text
- assert_equal "David" , xml.elements["//author-name"].text
- assert_match "Have a nice day", xml.elements["//content"].text
-
- assert_equal "1", xml.elements["//id"].text
- assert_equal "integer" , xml.elements["//id"].attributes['type']
-
- assert_equal "1", xml.elements["//replies-count"].text
- assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
-
- assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
- assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
-
- assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
-
- assert_equal nil, xml.elements["//parent-id"].text
- assert_equal "integer", xml.elements["//parent-id"].attributes['type']
- assert_equal "true", xml.elements["//parent-id"].attributes['nil']
-
- if current_adapter?(:SybaseAdapter)
- assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
- assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
- else
- # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
- assert_equal "2004-04-15", xml.elements["//last-read"].text
- assert_equal "date" , xml.elements["//last-read"].attributes['type']
- end
-
- # Oracle and DB2 don't have true boolean or time-only fields
- unless current_adapter?(:OracleAdapter, :DB2Adapter)
- assert_equal "false", xml.elements["//approved"].text
- assert_equal "boolean" , xml.elements["//approved"].attributes['type']
-
- assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
- assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
- end
- end
-
- def test_to_xml_skipping_attributes
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
- assert_equal "<topic>", xml.first(7)
- assert !xml.include?(%(<title>The First Topic</title>))
- assert xml.include?(%(<author-name>David</author-name>))
-
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
- assert !xml.include?(%(<title>The First Topic</title>))
- assert !xml.include?(%(<author-name>David</author-name>))
- end
-
- def test_to_xml_including_has_many_association
- xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
- assert_equal "<topic>", xml.first(7)
- assert xml.include?(%(<replies type="array"><reply>))
- assert xml.include?(%(<title>The Second Topic of the day</title>))
- end
-
- def test_array_to_xml_including_has_many_association
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
- assert xml.include?(%(<replies type="array"><reply>))
- end
-
- def test_array_to_xml_including_methods
- xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
- assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
- assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
- end
-
- def test_array_to_xml_including_has_one_association
- xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
- assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_array_to_xml_including_belongs_to_association
- xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
- end
-
- def test_to_xml_including_belongs_to_association
- xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert !xml.include?("<firm>")
-
- xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
- assert xml.include?("<firm>")
- end
-
- def test_to_xml_including_multiple_associations
- xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<account>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_multiple_associations_with_options
- xml = companies(:first_firm).to_xml(
- :indent => 0, :skip_instruct => true,
- :include => { :clients => { :only => :name } }
- )
-
- assert_equal "<firm>", xml.first(6)
- assert xml.include?(%(<client><name>Summit</name></client>))
- assert xml.include?(%(<clients type="array"><client>))
- end
-
- def test_to_xml_including_methods
- xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
- end
-
- def test_to_xml_with_block
- value = "Rockin' the block"
- xml = Company.new.to_xml(:skip_instruct => true) do |xml|
- xml.tag! "arbitrary-element", value
- end
- assert_equal "<company>", xml.first(9)
- assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
- end
-
def test_to_param_should_return_string
assert_kind_of String, Client.find(:first).to_param
end
@@ -2237,15 +1393,6 @@ class BasicsTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = original_logger
end
- def test_create_with_custom_timestamps
- custom_datetime = 1.hour.ago.beginning_of_day
-
- %w(created_at created_on updated_at updated_on).each do |attribute|
- parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
- assert_equal custom_datetime, parrot[attribute]
- end
- end
-
def test_dup
assert !Minimalistic.new.freeze.dup.frozen?
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 2c9d23c80f..afef31396e 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -325,7 +325,7 @@ class CalculationsTest < ActiveRecord::TestCase
end
def test_from_option_with_specified_index
- if Edge.connection.adapter_name == 'MySQL'
+ if Edge.connection.adapter_name == 'MySQL' or Edge.connection.adapter_name == 'Mysql2'
assert_equal Edge.count(:all), Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)')
assert_equal Edge.count(:all, :conditions => 'sink_id < 5'),
Edge.count(:all, :from => 'edges USE INDEX(unique_edge_index)', :conditions => 'sink_id < 5')
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index b5767344cd..cc6a6b44f2 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -68,6 +68,40 @@ class ColumnDefinitionTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:Mysql2Adapter)
+ def test_should_set_default_for_mysql_binary_data_types
+ binary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "binary(1)")
+ assert_equal "a", binary_column.default
+
+ varbinary_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "varbinary(1)")
+ assert_equal "a", varbinary_column.default
+ end
+
+ def test_should_not_set_default_for_blob_and_text_data_types
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "a", "blob")
+ end
+
+ assert_raise ArgumentError do
+ ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", "Hello", "text")
+ end
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert_equal nil, text_column.default
+
+ not_null_text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text", false)
+ assert_equal "", not_null_text_column.default
+ end
+
+ def test_has_default_should_return_false_for_blog_and_test_data_types
+ blob_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "blob")
+ assert !blob_column.has_default?
+
+ text_column = ActiveRecord::ConnectionAdapters::Mysql2Column.new("title", nil, "text")
+ assert !text_column.has_default?
+ end
+ end
+
if current_adapter?(:PostgreSQLAdapter)
def test_bigint_column_should_map_to_integer
bigint_column = ActiveRecord::ConnectionAdapters::PostgreSQLColumn.new('number', nil, "bigint")
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
new file mode 100644
index 0000000000..c535119972
--- /dev/null
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -0,0 +1,25 @@
+require "cases/helper"
+
+class ConnectionManagementTest < ActiveRecord::TestCase
+ def setup
+ @env = {}
+ @app = stub('App')
+ @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
+
+ @connections_cleared = false
+ ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
+ end
+
+ test "clears active connections after each call" do
+ @app.expects(:call).with(@env)
+ @management.call(@env)
+ assert @connections_cleared
+ end
+
+ test "doesn't clear active connections when running in a test case" do
+ @env['rack.test'] = true
+ @app.expects(:call).with(@env)
+ @management.call(@env)
+ assert !@connections_cleared
+ end
+end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index cc9b2a45f4..82b3c36ed2 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -1,25 +1,31 @@
require "cases/helper"
-class ConnectionManagementTest < ActiveRecord::TestCase
- def setup
- @env = {}
- @app = stub('App')
- @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
-
- @connections_cleared = false
- ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
- end
-
- test "clears active connections after each call" do
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert @connections_cleared
- end
-
- test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert !@connections_cleared
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionPoolTest < ActiveRecord::TestCase
+ def test_clear_stale_cached_connections!
+ pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec
+
+ threads = [
+ Thread.new { pool.connection },
+ Thread.new { pool.connection }]
+
+ threads.map { |t| t.join }
+
+ pool.extend Module.new {
+ attr_accessor :checkins
+ def checkin conn
+ @checkins << conn
+ conn.object_id
+ end
+ }
+ pool.checkins = []
+
+ cleared_threads = pool.clear_stale_cached_connections!
+ assert((cleared_threads - threads.map { |x| x.object_id }).empty?,
+ "threads should have been removed")
+ assert_equal pool.checkins.length, threads.length
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index ef29422824..0e90128907 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -39,7 +39,7 @@ class DefaultTest < ActiveRecord::TestCase
end
end
-if current_adapter?(:MysqlAdapter)
+if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional fixtures mode, this will
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 860d330a7f..4f3e43d77d 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -10,7 +10,6 @@ require 'models/entrant'
require 'models/project'
require 'models/developer'
require 'models/customer'
-require 'models/job'
class DynamicFinderMatchTest < ActiveRecord::TestCase
def test_find_no_match
@@ -694,6 +693,14 @@ class FinderTest < ActiveRecord::TestCase
assert_equal [], Topic.find_all_by_title("The First Topic!!")
end
+ def test_find_all_by_one_attribute_which_is_a_symbol
+ topics = Topic.find_all_by_content("Have a nice day".to_sym)
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
def test_find_all_by_one_attribute_that_is_an_aggregate
balance = customers(:david).balance
assert_kind_of Money, balance
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index 8008b86f81..93f8749255 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -36,7 +36,7 @@ class FixturesTest < ActiveRecord::TestCase
fixtures = nil
assert_nothing_raised { fixtures = create_fixtures(name) }
assert_kind_of(Fixtures, fixtures)
- fixtures.each { |name, fixture|
+ fixtures.each { |_name, fixture|
fixture.each { |key, value|
assert_match(MATCH_ATTRIBUTE_NAME, key)
}
@@ -229,9 +229,9 @@ if Account.connection.respond_to?(:reset_pk_sequence!)
def test_create_fixtures_resets_sequences_when_not_cached
@instances.each do |instance|
- max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)|
+ max_id = create_fixtures(instance.class.table_name).inject(0) do |_max_id, (name, fixture)|
fixture_id = fixture['id'].to_i
- fixture_id > max_id ? fixture_id : max_id
+ fixture_id > _max_id ? fixture_id : _max_id
end
# Clone the last fixture to check that it gets the next greatest id.
diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb
index ae4dcfb81e..3287626378 100644
--- a/activerecord/test/cases/i18n_test.rb
+++ b/activerecord/test/cases/i18n_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
require 'models/topic'
require 'models/reply'
-class ActiveRecordI18nTests < Test::Unit::TestCase
+class ActiveRecordI18nTests < ActiveRecord::TestCase
def setup
I18n.backend = I18n::Backend::Simple.new
diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb
index 99af7d2986..2de50b224c 100644
--- a/activerecord/test/cases/invalid_date_test.rb
+++ b/activerecord/test/cases/invalid_date_test.rb
@@ -1,7 +1,7 @@
require 'cases/helper'
require 'models/topic'
-class InvalidDateTest < Test::Unit::TestCase
+class InvalidDateTest < ActiveRecord::TestCase
def test_assign_valid_dates
valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]]
diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb
index c275557da8..2bc746c0b8 100644
--- a/activerecord/test/cases/json_serialization_test.rb
+++ b/activerecord/test/cases/json_serialization_test.rb
@@ -201,4 +201,11 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
}
assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name])
end
+
+ def test_should_be_able_to_encode_relation
+ authors_relation = Author.where(:id => [@david.id, @mary.id])
+
+ json = ActiveSupport::JSON.encode authors_relation, :only => :name
+ assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json
+ end
end
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 66874cdad1..e7126964cd 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -53,7 +53,8 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::StaleObjectError) { p2.destroy }
assert p1.destroy
- assert_equal true, p1.frozen?
+ assert p1.frozen?
+ assert p1.destroyed?
assert_raises(ActiveRecord::RecordNotFound) { Person.find(1) }
end
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index 4aeae1fe45..cbaaca764b 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -2,8 +2,9 @@ require "cases/helper"
require "models/developer"
require "active_support/log_subscriber/test_helper"
-class LogSubscriberTest < ActiveSupport::TestCase
+class LogSubscriberTest < ActiveRecord::TestCase
include ActiveSupport::LogSubscriber::TestHelper
+ include ActiveSupport::BufferedLogger::Severity
def setup
@old_logger = ActiveRecord::Base.logger
@@ -39,4 +40,25 @@ class LogSubscriberTest < ActiveSupport::TestCase
assert_match(/CACHE/, @logger.logged(:debug).last)
assert_match(/SELECT .*?FROM .?developers.?/i, @logger.logged(:debug).last)
end
+
+ def test_basic_query_doesnt_log_when_level_is_not_debug
+ @logger.level = INFO
+ Developer.all
+ wait
+ assert_equal 0, @logger.logged(:debug).size
+ end
+
+ def test_cached_queries_doesnt_log_when_level_is_not_debug
+ @logger.level = INFO
+ ActiveRecord::Base.cache do
+ Developer.all
+ Developer.all
+ end
+ wait
+ assert_equal 0, @logger.logged(:debug).size
+ end
+
+ def test_initializes_runtime
+ Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join
+ end
end
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 4e8ce1dac1..5256ab8d11 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -8,7 +8,6 @@ require 'models/author'
require 'models/developer'
require 'models/project'
require 'models/comment'
-require 'models/category'
class MethodScopingTest < ActiveRecord::TestCase
fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
@@ -209,6 +208,13 @@ class MethodScopingTest < ActiveRecord::TestCase
end
end
+ def test_scope_for_create_only_uses_equal
+ table = VerySpecialComment.arel_table
+ relation = VerySpecialComment.scoped
+ relation.where_values << table[:id].not_eq(1)
+ assert_equal({:type => "VerySpecialComment"}, relation.send(:scope_for_create))
+ end
+
def test_scoped_create
new_comment = nil
@@ -543,4 +549,4 @@ class NestedScopingTest < ActiveRecord::TestCase
assert_equal 1, scoped_authors.size
assert_equal authors(:david).attributes, scoped_authors.first.attributes
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 2c3fc46831..0cf3979694 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -256,7 +256,7 @@ if ActiveRecord::Base.connection.supports_migrations?
def test_create_table_with_defaults
# MySQL doesn't allow defaults on TEXT or BLOB columns.
- mysql = current_adapter?(:MysqlAdapter)
+ mysql = current_adapter?(:MysqlAdapter) || current_adapter?(:Mysql2Adapter)
Person.connection.create_table :testings do |t|
t.column :one, :string, :default => "hello"
@@ -313,7 +313,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
assert_equal 'integer', eleven.sql_type
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match 'int(11)', default.sql_type
assert_match 'tinyint', one.sql_type
assert_match 'int', four.sql_type
@@ -581,7 +581,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert_kind_of BigDecimal, bob.wealth
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_unabstracted_database_dependent_types
Person.delete_all
@@ -621,7 +621,7 @@ if ActiveRecord::Base.connection.supports_migrations?
assert !Person.column_methods_hash.include?(:last_name)
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def testing_table_for_positioning
Person.connection.create_table :testings, :id => false do |t|
t.column :first, :integer
@@ -1447,7 +1447,7 @@ if ActiveRecord::Base.connection.supports_migrations?
columns = Person.connection.columns(:binary_testings)
data_column = columns.detect { |c| c.name == "data" }
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_equal '', data_column.default
else
assert_nil data_column.default
@@ -1748,7 +1748,7 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def integer_column
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
'int(11)'
elsif current_adapter?(:OracleAdapter)
'NUMBER(38)'
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index dc85b395d3..c42dda2ccb 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -270,27 +270,27 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
- def test_should_build_with_proxy_options
+ def test_should_build_on_top_of_named_scope
topic = Topic.approved.build({})
assert topic.approved
end
- def test_should_build_new_with_proxy_options
+ def test_should_build_new_on_top_of_named_scope
topic = Topic.approved.new
assert topic.approved
end
- def test_should_create_with_proxy_options
+ def test_should_create_on_top_of_named_scope
topic = Topic.approved.create({})
assert topic.approved
end
- def test_should_create_with_bang_with_proxy_options
+ def test_should_create_with_bang_on_top_of_named_scope
topic = Topic.approved.create!({})
assert topic.approved
end
- def test_should_build_with_proxy_options_chained
+ def test_should_build_on_top_of_chained_named_scopes
topic = Topic.approved.by_lifo.build({})
assert topic.approved
assert_equal 'lifo', topic.author_name
@@ -478,4 +478,10 @@ class DynamicScopeTest < ActiveRecord::TestCase
assert_equal Post.scoped_by_author_id(1).find(1), Post.find(1)
assert_equal Post.scoped_by_author_id_and_title(1, "Welcome to the weblog").first, Post.find(:first, :conditions => { :author_id => 1, :title => "Welcome to the weblog"})
end
+
+ def test_dynamic_scope_should_create_methods_after_hitting_method_missing
+ assert Developer.methods.grep(/scoped_by_created_at/).blank?
+ Developer.scoped_by_created_at(nil)
+ assert Developer.methods.grep(/scoped_by_created_at/).present?
+ end
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index c9ea0d8c40..df09bbd46a 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -59,6 +59,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.save!
assert_equal 1, pirate.birds_with_reject_all_blank.count
+ assert_equal 'Tweetie', pirate.birds_with_reject_all_blank.first.name
end
def test_should_raise_an_ArgumentError_for_non_existing_associations
@@ -74,7 +75,7 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
ship = pirate.create_ship(:name => 'Nights Dirty Lightning')
assert_no_difference('Ship.count') do
- pirate.update_attributes(:ship_attributes => { '_destroy' => true })
+ pirate.update_attributes(:ship_attributes => { '_destroy' => true, :id => ship.id })
end
end
@@ -100,7 +101,8 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_no_difference('Ship.count') { pirate.save! }
- # pirate.reject_empty_ships_on_create returns false for saved records
+ # pirate.reject_empty_ships_on_create returns false for saved pirate records
+ # in the previous step note that pirate gets saved but ship fails
pirate.ship_attributes = { :name => 'Red Pearl', :_reject_me_if_new => true }
assert_difference('Ship.count') { pirate.save! }
end
@@ -266,6 +268,28 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
end
assert_equal 'Mayflower', @ship.reload.name
end
+
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
+ @ship.delete
+ @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+
+ assert_no_difference('Ship.count') do
+ @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id })
+ end
+ assert_equal 'Mayflower', @ship.reload.name
+ end
+
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => true
+ @ship.delete
+ @ship = @pirate.create_update_only_ship(:name => 'Nights Dirty Lightning')
+
+ assert_difference('Ship.count', -1) do
+ @pirate.update_attributes(:update_only_ship_attributes => { :name => 'Mayflower', :id => @ship.id, :_destroy => true })
+ end
+ Pirate.accepts_nested_attributes_for :update_only_ship, :update_only => true, :allow_destroy => false
+ end
+
end
class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
@@ -411,6 +435,27 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
end
assert_equal 'Arr', @pirate.reload.catchphrase
end
+
+ def test_should_update_existing_when_update_only_is_true_and_id_is_given
+ @pirate.delete
+ @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+
+ assert_no_difference('Pirate.count') do
+ @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id })
+ end
+ assert_equal 'Arr', @pirate.reload.catchphrase
+ end
+
+ def test_should_destroy_existing_when_update_only_is_true_and_id_is_given_and_is_marked_for_destruction
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => true
+ @pirate.delete
+ @pirate = @ship.create_update_only_pirate(:catchphrase => 'Aye')
+
+ assert_difference('Pirate.count', -1) do
+ @ship.update_attributes(:update_only_pirate_attributes => { :catchphrase => 'Arr', :id => @pirate.id, :_destroy => true })
+ end
+ Ship.accepts_nested_attributes_for :update_only_pirate, :update_only => true, :allow_destroy => false
+ end
end
module NestedAttributesOnACollectionAssociationTests
@@ -811,7 +856,25 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR
@part = @ship.parts.create!(:name => "Mast")
@trinket = @part.trinkets.create!(:name => "Necklace")
end
-
+
+ test "if association is not loaded and association record is saved and then in memory record attributes should be saved" do
+ @ship.parts_attributes=[{:id => @part.id,:name =>'Deck'}]
+ assert_equal 1, @ship.parts.proxy_target.size
+ assert_equal 'Deck', @ship.parts[0].name
+ end
+
+ test "if association is not loaded and child doesn't change and I am saving a grandchild then in memory record should be used" do
+ @ship.parts_attributes=[{:id => @part.id,:trinkets_attributes =>[{:id => @trinket.id, :name => 'Ruby'}]}]
+ assert_equal 1, @ship.parts.proxy_target.size
+ assert_equal 'Mast', @ship.parts[0].name
+ assert_no_difference("@ship.parts[0].trinkets.proxy_target.size") do
+ @ship.parts[0].trinkets.proxy_target.size
+ end
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
+ @ship.save
+ assert_equal 'Ruby', @ship.parts[0].trinkets[0].name
+ end
+
test "when grandchild changed in memory, saving parent should save grandchild" do
@trinket.name = "changed"
@ship.save
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
new file mode 100644
index 0000000000..d7666b19f6
--- /dev/null
+++ b/activerecord/test/cases/persistence_test.rb
@@ -0,0 +1,470 @@
+require "cases/helper"
+require 'models/post'
+require 'models/author'
+require 'models/topic'
+require 'models/reply'
+require 'models/category'
+require 'models/company'
+require 'models/developer'
+require 'models/project'
+require 'models/minimalistic'
+require 'models/warehouse_thing'
+require 'models/parrot'
+require 'models/minivan'
+require 'models/loose_person'
+require 'rexml/document'
+require 'active_support/core_ext/exception'
+
+class PersistencesTest < ActiveRecord::TestCase
+
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts, :minivans
+
+ # Oracle UPDATE does not support ORDER BY
+ unless current_adapter?(:OracleAdapter)
+ def test_update_all_ignores_order_without_limit_from_association
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all([ "body = ?", "bulk update!" ])
+ end
+ end
+
+ def test_update_all_with_order_and_limit_updates_subset_only
+ author = authors(:david)
+ assert_nothing_raised do
+ assert_equal 1, author.posts_sorted_by_id_limited.size
+ assert_equal 2, author.posts_sorted_by_id_limited.find(:all, :limit => 2).size
+ assert_equal 1, author.posts_sorted_by_id_limited.update_all([ "body = ?", "bulk update!" ])
+ assert_equal "bulk update!", posts(:welcome).body
+ assert_not_equal "bulk update!", posts(:thinking).body
+ end
+ end
+ end
+
+ def test_update_many
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+ updated = Topic.update(topic_data.keys, topic_data.values)
+
+ assert_equal 2, updated.size
+ assert_equal "1 updated", Topic.find(1).content
+ assert_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_delete_all
+ assert Topic.count > 0
+
+ assert_equal Topic.count, Topic.delete_all
+ end
+
+ def test_update_by_condition
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
+ assert_equal "Have a nice day", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+ end
+
+ def test_increment_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit
+ assert_equal 51, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
+ assert_equal 53, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_increment_nil_attribute
+ assert_nil topics(:first).parent_id
+ topics(:first).increment! :parent_id
+ assert_equal 1, topics(:first).parent_id
+ end
+
+ def test_increment_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit, 5
+ assert_equal 55, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3)
+ assert_equal 59, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_destroy_all
+ conditions = "author_name = 'Mary'"
+ topics_by_mary = Topic.all(:conditions => conditions, :order => 'id')
+ assert ! topics_by_mary.empty?
+
+ assert_difference('Topic.count', -topics_by_mary.size) do
+ destroyed = Topic.destroy_all(conditions).sort_by(&:id)
+ assert_equal topics_by_mary, destroyed
+ assert destroyed.all? { |topic| topic.frozen? }, "destroyed topics should be frozen"
+ end
+ end
+
+ def test_destroy_many
+ clients = Client.find([2, 3], :order => 'id')
+
+ assert_difference('Client.count', -2) do
+ destroyed = Client.destroy([2, 3]).sort_by(&:id)
+ assert_equal clients, destroyed
+ assert destroyed.all? { |client| client.frozen? }, "destroyed clients should be frozen"
+ end
+ end
+
+ def test_delete_many
+ original_count = Topic.count
+ Topic.delete(deleting = [1, 2])
+ assert_equal original_count - deleting.size, Topic.count
+ end
+
+ def test_decrement_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+
+ accounts(:signals37).decrement!(:credit_limit)
+ assert_equal 49, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
+ assert_equal 47, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_decrement_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).decrement! :credit_limit, 5
+ assert_equal 45, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3)
+ assert_equal 41, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_create
+ topic = Topic.new
+ topic.title = "New Topic"
+ topic.save
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic_reloaded.title)
+ end
+
+ def test_save!
+ topic = Topic.new(:title => "New Topic")
+ assert topic.save!
+
+ reply = WrongReply.new
+ assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
+ end
+
+ def test_save_null_string_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "null", "author_name" => "null" }
+ topic.save!
+ topic.reload
+ assert_equal("null", topic.title)
+ assert_equal("null", topic.author_name)
+ end
+
+ def test_save_nil_string_attributes
+ topic = Topic.find(1)
+ topic.title = nil
+ topic.save!
+ topic.reload
+ assert_nil topic.title
+ end
+
+ def test_save_for_record_with_only_primary_key
+ minimalistic = Minimalistic.new
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_save_for_record_with_only_primary_key_that_is_provided
+ assert_nothing_raised { Minimalistic.create!(:id => 2) }
+ end
+
+ def test_create_many
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
+ assert_equal 2, topics.size
+ assert_equal "first", topics.first.title
+ end
+
+ def test_create_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = 'Another New Topic'
+ topic.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topic.save }
+ end
+
+ def test_create_through_factory_with_block
+ topic = Topic.create("title" => "New Topic") do |t|
+ t.author_name = "David"
+ end
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic.title)
+ assert_equal("David", topic.author_name)
+ end
+
+ def test_create_many_through_factory_with_block
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t|
+ t.author_name = "David"
+ end
+ assert_equal 2, topics.size
+ topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id)
+ assert_equal "first", topic1.title
+ assert_equal "David", topic1.author_name
+ assert_equal "second", topic2.title
+ assert_equal "David", topic2.author_name
+ end
+
+ def test_update
+ topic = Topic.new
+ topic.title = "Another New Topic"
+ topic.written_on = "2003-12-12 23:23:00"
+ topic.save
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topicReloaded.title)
+
+ topicReloaded.title = "Updated topic"
+ topicReloaded.save
+
+ topicReloadedAgain = Topic.find(topic.id)
+
+ assert_equal("Updated topic", topicReloadedAgain.title)
+ end
+
+ def test_update_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = "Still another topic"
+ topic.save
+
+ topicReloaded = Topic.find(topic.id)
+ topicReloaded.title = "A New Topic"
+ topicReloaded.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topicReloaded.save }
+ end
+
+ def test_update_for_record_with_only_primary_key
+ minimalistic = minimalistics(:first)
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_delete
+ topic = Topic.find(1)
+ assert_equal topic, topic.delete, 'topic.delete did not return self'
+ assert topic.frozen?, 'topic not frozen after delete'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
+ def test_delete_doesnt_run_callbacks
+ Topic.find(1).delete
+ assert_not_nil Topic.find(2)
+ end
+
+ def test_destroy
+ topic = Topic.find(1)
+ assert_equal topic, topic.destroy, 'topic.destroy did not return self'
+ assert topic.frozen?, 'topic not frozen after destroy'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
+ def test_record_not_found_exception
+ assert_raise(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
+ end
+
+ def test_update_all
+ assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
+ assert_equal "bulk updated!", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!'])
+ assert_equal "bulk updated again!", Topic.find(1).content
+ assert_equal "bulk updated again!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', nil])
+ assert_nil Topic.find(1).content
+ end
+
+ def test_update_all_with_hash
+ assert_not_nil Topic.find(1).last_read
+ assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
+ assert_equal "bulk updated with hash!", Topic.find(1).content
+ assert_equal "bulk updated with hash!", Topic.find(2).content
+ assert_nil Topic.find(1).last_read
+ assert_nil Topic.find(2).last_read
+ end
+
+ def test_update_all_with_non_standard_table_name
+ assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
+ assert_equal 0, WarehouseThing.find(1).value
+ end
+
+ def test_delete_new_record
+ client = Client.new
+ client.delete
+ assert client.frozen?
+ end
+
+ def test_delete_record_with_associations
+ client = Client.find(3)
+ client.delete
+ assert client.frozen?
+ assert_kind_of Firm, client.firm
+ assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ end
+
+ def test_destroy_new_record
+ client = Client.new
+ client.destroy
+ assert client.frozen?
+ end
+
+ def test_destroy_record_with_associations
+ client = Client.find(3)
+ client.destroy
+ assert client.frozen?
+ assert_kind_of Firm, client.firm
+ assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ end
+
+ def test_update_attribute
+ assert !Topic.find(1).approved?
+ Topic.find(1).update_attribute("approved", true)
+ assert Topic.find(1).approved?
+
+ Topic.find(1).update_attribute(:approved, false)
+ assert !Topic.find(1).approved?
+ end
+
+ def test_update_attribute_for_readonly_attribute
+ minivan = Minivan.find('m1')
+ assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
+ end
+
+ def test_update_attribute_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+ assert_equal ['author_name'], t.changed
+
+ t.reload
+ assert_equal 'David', t.author_name
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_with_one_updated
+ t = Topic.first
+ title = t.title
+ t.update_attribute(:title, 'super_title')
+ assert_equal 'super_title', t.title
+ assert !t.changed?, "topic should not have changed"
+ assert !t.title_changed?, "title should not have changed"
+ assert_nil t.title_change, 'title change should be nil'
+
+ t.reload
+ assert_equal 'super_title', t.title
+ end
+
+ def test_update_attribute_for_udpated_at_on
+ developer = Developer.find(1)
+ prev_month = Time.now.prev_month
+ developer.update_attribute(:updated_at, prev_month)
+ assert_equal prev_month, developer.updated_at
+ developer.update_attribute(:salary, 80001)
+ assert_not_equal prev_month, developer.updated_at
+ developer.reload
+ assert_not_equal prev_month, developer.updated_at
+ end
+
+ def test_update_attributes
+ topic = Topic.find(1)
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+
+ topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
+ topic.reload
+ assert topic.approved?
+ assert_equal "The First Topic Updated", topic.title
+
+ topic.update_attributes(:approved => false, :title => "The First Topic")
+ topic.reload
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+ end
+
+ def test_update_attributes!
+ Reply.validates_presence_of(:title)
+ reply = Reply.find(2)
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
+ reply.reload
+ assert_equal "The Second Topic of the day updated", reply.title
+ assert_equal "Have a nice evening", reply.content
+
+ reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
+ reply.reload
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ ensure
+ Reply.reset_callbacks(:validate)
+ end
+
+ def test_destroyed_returns_boolean
+ developer = Developer.first
+ assert_equal false, developer.destroyed?
+ developer.destroy
+ assert_equal true, developer.destroyed?
+
+ developer = Developer.last
+ assert_equal false, developer.destroyed?
+ developer.delete
+ assert_equal true, developer.destroyed?
+ end
+
+ def test_persisted_returns_boolean
+ developer = Developer.new(:name => "Jose")
+ assert_equal false, developer.persisted?
+ developer.save!
+ assert_equal true, developer.persisted?
+
+ developer = Developer.first
+ assert_equal true, developer.persisted?
+ developer.destroy
+ assert_equal false, developer.persisted?
+
+ developer = Developer.last
+ assert_equal true, developer.persisted?
+ developer.delete
+ assert_equal false, developer.persisted?
+ end
+
+ def test_class_level_destroy
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.destroy(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_class_level_delete
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.delete(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_create_with_custom_timestamps
+ custom_datetime = 1.hour.ago.beginning_of_day
+
+ %w(created_at created_on updated_at updated_on).each do |attribute|
+ parrot = LiveParrot.create(:name => "colombian", attribute => custom_datetime)
+ assert_equal custom_datetime, parrot[attribute]
+ end
+ end
+
+end
diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 73f4b3848c..1e44237e0a 100644
--- a/activerecord/test/cases/pk_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -13,7 +13,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
topic = Topic.new
assert topic.to_key.nil?
topic = Topic.find(1)
- assert_equal topic.to_key, [1]
+ assert_equal [1], topic.to_key
end
def test_to_key_with_customized_primary_key
@@ -26,7 +26,7 @@ class PrimaryKeysTest < ActiveRecord::TestCase
def test_to_key_with_primary_key_after_destroy
topic = Topic.find(1)
topic.destroy
- assert_equal topic.to_key, [1]
+ assert_equal [1], topic.to_key
end
def test_integer_key
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 68abca70b3..594db1d0ab 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -1,8 +1,6 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
require 'models/task'
-require 'models/course'
require 'models/category'
require 'models/post'
@@ -59,7 +57,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5'
+ elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' or current_adapter?(:Mysql2Adapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 41dcdbcd37..a50a4d4165 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -5,6 +5,8 @@ require 'models/developer'
require 'models/project'
require 'models/comment'
require 'models/category'
+require 'models/person'
+require 'models/reference'
class RelationScopingTest < ActiveRecord::TestCase
fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
@@ -218,7 +220,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase
end
class HasManyScopingTest< ActiveRecord::TestCase
- fixtures :comments, :posts
+ fixtures :comments, :posts, :people, :references
def setup
@welcome = Post.find(1)
@@ -250,6 +252,23 @@ class HasManyScopingTest< ActiveRecord::TestCase
assert_equal 'a comment...', @welcome.comments.what_are_you
end
end
+
+ def test_should_maintain_default_scope_on_associations
+ person = people(:michael)
+ magician = BadReference.find(1)
+ assert_equal [magician], people(:michael).bad_references
+ end
+
+ def test_should_default_scope_on_associations_is_overriden_by_association_conditions
+ person = people(:michael)
+ assert_equal [], people(:michael).fixed_bad_references
+ end
+
+ def test_should_maintain_default_scope_on_eager_loaded_associations
+ michael = Person.where(:id => people(:michael).id).includes(:bad_references).first
+ magician = BadReference.find(1)
+ assert_equal [magician], michael.bad_references
+ end
end
class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
@@ -364,6 +383,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
+ def test_named_scope_reorders_default
+ expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reordered_by_name.find(:all).collect { |dev| dev.name }
+ assert_equal expected, received
+ end
+
def test_nested_exclusive_scope
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
@@ -393,4 +418,4 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index ffde8daa07..ac7b501bb7 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1,5 +1,4 @@
require "cases/helper"
-require 'models/tag'
require 'models/tagging'
require 'models/post'
require 'models/topic'
@@ -23,6 +22,11 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 5, Post.where(:id => post_authors).size
end
+ def test_multivalue_where
+ posts = Post.where('author_id = ? AND id = ?', 1, 1)
+ assert_equal 1, posts.to_a.size
+ end
+
def test_scoped
topics = Topic.scoped
assert_kind_of ActiveRecord::Relation, topics
@@ -188,11 +192,23 @@ class RelationTest < ActiveRecord::TestCase
end
end
- def test_respond_to_private_arel_methods
+ def test_respond_to_delegates_to_relation
relation = Topic.scoped
+ fake_arel = Struct.new(:responds) {
+ def respond_to? method, access = false
+ responds << [method, access]
+ end
+ }.new []
+
+ relation.extend(Module.new { attr_accessor :arel })
+ relation.arel = fake_arel
+
+ relation.respond_to?(:matching_attributes)
+ assert_equal [:matching_attributes, false], fake_arel.responds.first
- assert ! relation.respond_to?(:matching_attributes)
- assert relation.respond_to?(:matching_attributes, true)
+ fake_arel.responds = []
+ relation.respond_to?(:matching_attributes, true)
+ assert_equal [:matching_attributes, true], fake_arel.responds.first
end
def test_respond_to_dynamic_finders
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 1c43e3c5b5..66446b6b7e 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -93,7 +93,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{c_int_4.*}, output
assert_no_match %r{c_int_4.*:limit}, output
- elsif current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
assert_match %r{c_int_1.*:limit => 1}, output
assert_match %r{c_int_2.*:limit => 2}, output
assert_match %r{c_int_3.*:limit => 3}, output
@@ -169,7 +169,7 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
end
- if current_adapter?(:MysqlAdapter)
+ if current_adapter?(:MysqlAdapter) or current_adapter?(:Mysql2Adapter)
def test_schema_dump_should_not_add_default_value_for_mysql_text_field
output = standard_dump
assert_match %r{t.text\s+"body",\s+:null => false$}, output
diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb
index 8c385af97c..dab81530af 100644
--- a/activerecord/test/cases/serialization_test.rb
+++ b/activerecord/test/cases/serialization_test.rb
@@ -1,7 +1,13 @@
require "cases/helper"
require 'models/contact'
+require 'models/topic'
+require 'models/reply'
+require 'models/company'
class SerializationTest < ActiveRecord::TestCase
+
+ fixtures :topics, :companies, :accounts
+
FORMATS = [ :xml, :json ]
def setup
@@ -17,6 +23,134 @@ class SerializationTest < ActiveRecord::TestCase
@contact = Contact.new(@contact_attributes)
end
+ def test_to_xml
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
+
+ assert_equal "topic", xml.root.name
+ assert_equal "The First Topic" , xml.elements["//title"].text
+ assert_equal "David" , xml.elements["//author-name"].text
+ assert_match "Have a nice day", xml.elements["//content"].text
+
+ assert_equal "1", xml.elements["//id"].text
+ assert_equal "integer" , xml.elements["//id"].attributes['type']
+
+ assert_equal "1", xml.elements["//replies-count"].text
+ assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
+
+ assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
+ assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
+
+ assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
+
+ assert_equal nil, xml.elements["//parent-id"].text
+ assert_equal "integer", xml.elements["//parent-id"].attributes['type']
+ assert_equal "true", xml.elements["//parent-id"].attributes['nil']
+
+ if current_adapter?(:SybaseAdapter)
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
+ else
+ # Oracle enhanced adapter allows to define Date attributes in model class (see topic.rb)
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
+ end
+
+ # Oracle and DB2 don't have true boolean or time-only fields
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
+ assert_equal "false", xml.elements["//approved"].text
+ assert_equal "boolean" , xml.elements["//approved"].attributes['type']
+
+ assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
+ assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
+ end
+ end
+
+ def test_to_xml_skipping_attributes
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
+ assert_equal "<topic>", xml.first(7)
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert xml.include?(%(<author-name>David</author-name>))
+
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
+ assert !xml.include?(%(<title>The First Topic</title>))
+ assert !xml.include?(%(<author-name>David</author-name>))
+ end
+
+ def test_to_xml_including_has_many_association
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
+ assert_equal "<topic>", xml.first(7)
+ assert xml.include?(%(<replies type="array"><reply>))
+ assert xml.include?(%(<title>The Second Topic of the day</title>))
+ end
+
+ def test_array_to_xml_including_has_many_association
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
+ assert xml.include?(%(<replies type="array"><reply>))
+ end
+
+ def test_array_to_xml_including_methods
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
+ assert xml.include?(%(<topic-id type="integer">#{topics(:first).topic_id}</topic-id>)), xml
+ assert xml.include?(%(<topic-id type="integer">#{topics(:second).topic_id}</topic-id>)), xml
+ end
+
+ def test_array_to_xml_including_has_one_association
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_array_to_xml_including_belongs_to_association
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_to_xml_including_belongs_to_association
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert !xml.include?("<firm>")
+
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?("<firm>")
+ end
+
+ def test_to_xml_including_multiple_associations
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<account>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
+ def test_to_xml_including_multiple_associations_with_options
+ xml = companies(:first_firm).to_xml(
+ :indent => 0, :skip_instruct => true,
+ :include => { :clients => { :only => :name } }
+ )
+
+ assert_equal "<firm>", xml.first(6)
+ assert xml.include?(%(<client><name>Summit</name></client>))
+ assert xml.include?(%(<clients type="array"><client>))
+ end
+
+ def test_to_xml_including_methods
+ xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
+ assert_equal "<company>", xml.first(9)
+ assert xml.include?(%(<arbitrary-method>I am Jack's profound disappointment</arbitrary-method>))
+ end
+
+ def test_to_xml_with_block
+ value = "Rockin' the block"
+ xml = Company.new.to_xml(:skip_instruct => true) do |_xml|
+ _xml.tag! "arbitrary-element", value
+ end
+ assert_equal "<company>", xml.first(9)
+ assert xml.include?(%(<arbitrary-element>#{value}</arbitrary-element>))
+ end
+
def test_serialize_should_be_reversible
for format in FORMATS
@serialized = Contact.new.send("to_#{format}")
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
new file mode 100644
index 0000000000..6f1c170a0c
--- /dev/null
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -0,0 +1,68 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+ class SessionStore
+ class SessionTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ super
+ Session.drop_table! if Session.table_exists?
+ end
+
+ def test_data_column_name
+ # default column name is 'data'
+ assert_equal 'data', Session.data_column_name
+ end
+
+ def test_table_name
+ assert_equal 'sessions', Session.table_name
+ end
+
+ def test_create_table!
+ assert !Session.table_exists?
+ Session.create_table!
+ assert Session.table_exists?
+ Session.drop_table!
+ assert !Session.table_exists?
+ end
+
+ def test_find_by_sess_id_compat
+ klass = Class.new(Session) do
+ def self.session_id_column
+ 'sessid'
+ end
+ end
+ klass.create_table!
+
+ assert klass.columns_hash['sessid'], 'sessid column exists'
+ session = klass.new(:data => 'hello')
+ session.sessid = "100"
+ session.save!
+
+ found = klass.find_by_session_id("100")
+ assert_equal session, found
+ assert_equal session.sessid, found.session_id
+ ensure
+ klass.drop_table!
+ end
+
+ def test_find_by_session_id
+ Session.create_table!
+ session_id = "10"
+ s = Session.create!(:data => 'world', :session_id => session_id)
+ t = Session.find_by_session_id(session_id)
+ assert_equal s, t
+ assert_equal s.data, t.data
+ Session.drop_table!
+ end
+
+ def test_loaded?
+ s = Session.new
+ assert !s.loaded?, 'session is not loaded'
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb
new file mode 100644
index 0000000000..f0ba166465
--- /dev/null
+++ b/activerecord/test/cases/session_store/sql_bypass.rb
@@ -0,0 +1,56 @@
+require 'cases/helper'
+require 'action_dispatch'
+require 'active_record/session_store'
+
+module ActiveRecord
+ class SessionStore
+ class SqlBypassTest < ActiveRecord::TestCase
+ def setup
+ super
+ Session.drop_table! if Session.table_exists?
+ end
+
+ def test_create_table
+ assert !Session.table_exists?
+ SqlBypass.create_table!
+ assert Session.table_exists?
+ SqlBypass.drop_table!
+ assert !Session.table_exists?
+ end
+
+ def test_new_record?
+ s = SqlBypass.new :data => 'foo', :session_id => 10
+ assert s.new_record?, 'this is a new record!'
+ end
+
+ def test_not_loaded?
+ s = SqlBypass.new({})
+ assert !s.loaded?, 'it is not loaded'
+ end
+
+ def test_loaded?
+ s = SqlBypass.new :data => 'hello'
+ assert s.loaded?, 'it is loaded'
+ end
+
+ def test_save
+ SqlBypass.create_table! unless Session.table_exists?
+ session_id = 20
+ s = SqlBypass.new :data => 'hello', :session_id => session_id
+ s.save
+ t = SqlBypass.find_by_session_id session_id
+ assert_equal s.session_id, t.session_id
+ assert_equal s.data, t.data
+ end
+
+ def test_destroy
+ SqlBypass.create_table! unless Session.table_exists?
+ session_id = 20
+ s = SqlBypass.new :data => 'hello', :session_id => session_id
+ s.save
+ s.destroy
+ assert_nil SqlBypass.find_by_session_id session_id
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 549c4af6b1..401439994d 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -2,9 +2,10 @@ require 'cases/helper'
require 'models/developer'
require 'models/owner'
require 'models/pet'
+require 'models/toy'
class TimestampTest < ActiveRecord::TestCase
- fixtures :developers, :owners, :pets
+ fixtures :developers, :owners, :pets, :toys
def setup
@developer = Developer.first
@@ -25,16 +26,26 @@ class TimestampTest < ActiveRecord::TestCase
end
def test_touching_a_record_updates_its_timestamp
+ previous_salary = @developer.salary
+ @developer.salary = previous_salary + 10000
@developer.touch
assert_not_equal @previously_updated_at, @developer.updated_at
+ assert_equal previous_salary + 10000, @developer.salary
+ assert @developer.salary_changed?, 'developer salary should have changed'
+ assert @developer.changed?, 'developer should be marked as changed'
+ @developer.reload
+ assert_equal previous_salary, @developer.salary
end
def test_touching_a_different_attribute
previously_created_at = @developer.created_at
@developer.touch(:created_at)
+ assert !@developer.created_at_changed? , 'created_at should not be changed'
+ assert !@developer.changed?, 'record should not be changed'
assert_not_equal previously_created_at, @developer.created_at
+ assert_not_equal @previously_updated_at, @developer.updated_at
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
@@ -72,4 +83,21 @@ class TimestampTest < ActiveRecord::TestCase
ensure
Pet.belongs_to :owner, :touch => true
end
+
+ def test_touching_a_record_touches_parent_record_and_grandparent_record
+ Toy.belongs_to :pet, :touch => true
+ Pet.belongs_to :owner, :touch => true
+
+ toy = Toy.first
+ pet = toy.pet
+ owner = pet.owner
+
+ owner.update_attribute(:updated_at, (time = 3.days.ago))
+ toy.touch
+ owner.reload
+
+ assert_not_equal time, owner.updated_at
+ ensure
+ Toy.belongs_to :pet
+ end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index df123c9de8..d72c4bf7c4 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
class TransactionCallbacksTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
@@ -246,3 +245,44 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:after_rollback], @second.history
end
end
+
+
+class TransactionObserverCallbacksTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :topics
+
+ class TopicWithObserverAttached < ActiveRecord::Base
+ set_table_name :topics
+ def history
+ @history ||= []
+ end
+ end
+
+ class TopicWithObserverAttachedObserver < ActiveRecord::Observer
+ def after_commit(record)
+ record.history.push :"TopicWithObserverAttachedObserver#after_commit"
+ end
+
+ def after_rollback(record)
+ record.history.push :"TopicWithObserverAttachedObserver#after_rollback"
+ end
+ end
+
+ def test_after_commit_called
+ topic = TopicWithObserverAttached.new
+ topic.save!
+
+ assert topic.history, [:"TopicWithObserverAttachedObserver#after_commit"]
+ end
+
+ def test_after_rollback_called
+ topic = TopicWithObserverAttached.new
+
+ Topic.transaction do
+ topic.save!
+ raise ActiveRecord::Rollback
+ end
+
+ assert topic.history, [:"TopicWithObserverObserver#after_rollback"]
+ end
+end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 958a4e4f94..9255190613 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -3,10 +3,12 @@ require 'models/topic'
require 'models/reply'
require 'models/developer'
require 'models/book'
+require 'models/author'
+require 'models/post'
class TransactionTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
- fixtures :topics, :developers
+ fixtures :topics, :developers, :authors, :posts
def setup
@first, @second = Topic.find(1, 2).sort_by { |t| t.id }
@@ -103,6 +105,25 @@ class TransactionTest < ActiveRecord::TestCase
end
end
+ def test_update_attributes_should_rollback_on_failure
+ author = Author.find(1)
+ posts_count = author.posts.size
+ assert posts_count > 0
+ status = author.update_attributes(:name => nil, :post_ids => [])
+ assert !status
+ assert_equal posts_count, author.posts(true).size
+ end
+
+ def test_update_attributes_should_rollback_on_failure!
+ author = Author.find(1)
+ posts_count = author.posts.size
+ assert posts_count > 0
+ assert_raise(ActiveRecord::RecordInvalid) do
+ author.update_attributes!(:name => nil, :post_ids => [])
+ end
+ assert_equal posts_count, author.posts(true).size
+ end
+
def test_cancellation_from_before_destroy_rollbacks_in_destroy
add_cancelling_before_destroy_with_db_side_effect_to_topic
begin
diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
index 454e42ed37..628029f8df 100644
--- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -1,6 +1,5 @@
require "cases/helper"
require 'models/topic'
-require 'models/reply'
class I18nGenerateMessageValidationTest < ActiveRecord::TestCase
def setup
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index 3f1b0e333f..fd771ef4be 100644
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -4,11 +4,6 @@ require 'models/topic'
require 'models/reply'
require 'models/person'
require 'models/developer'
-require 'models/warehouse_thing'
-require 'models/guid'
-require 'models/owner'
-require 'models/pet'
-require 'models/event'
require 'models/parrot'
require 'models/company'
diff --git a/activerecord/test/cases/xml_serialization_test.rb b/activerecord/test/cases/xml_serialization_test.rb
index 751946ffc5..b11b340e94 100644
--- a/activerecord/test/cases/xml_serialization_test.rb
+++ b/activerecord/test/cases/xml_serialization_test.rb
@@ -2,7 +2,6 @@ require "cases/helper"
require 'models/contact'
require 'models/post'
require 'models/author'
-require 'models/tagging'
require 'models/comment'
require 'models/company_in_module'
diff --git a/activerecord/test/connections/native_mysql2/connection.rb b/activerecord/test/connections/native_mysql2/connection.rb
new file mode 100644
index 0000000000..c6f198b1ac
--- /dev/null
+++ b/activerecord/test/connections/native_mysql2/connection.rb
@@ -0,0 +1,25 @@
+print "Using native Mysql2\n"
+require_dependency 'models/course'
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+# GRANT ALL PRIVILEGES ON activerecord_unittest.* to 'rails'@'localhost';
+# GRANT ALL PRIVILEGES ON activerecord_unittest2.* to 'rails'@'localhost';
+
+ActiveRecord::Base.configurations = {
+ 'arunit' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :encoding => 'utf8',
+ :database => 'activerecord_unittest',
+ },
+ 'arunit2' => {
+ :adapter => 'mysql2',
+ :username => 'rails',
+ :database => 'activerecord_unittest2'
+ }
+}
+
+ActiveRecord::Base.establish_connection 'arunit'
+Course.establish_connection 'arunit2'
diff --git a/activerecord/test/fixtures/dashboards.yml b/activerecord/test/fixtures/dashboards.yml
new file mode 100644
index 0000000000..e75bf46e6c
--- /dev/null
+++ b/activerecord/test/fixtures/dashboards.yml
@@ -0,0 +1,3 @@
+cool_first:
+ dashboard_id: d1
+ name: my_dashboard \ No newline at end of file
diff --git a/activerecord/test/fixtures/minivans.yml b/activerecord/test/fixtures/minivans.yml
new file mode 100644
index 0000000000..f1224a4c1a
--- /dev/null
+++ b/activerecord/test/fixtures/minivans.yml
@@ -0,0 +1,5 @@
+cool_first:
+ minivan_id: m1
+ name: my_minivan
+ speedometer_id: s1
+ color: blue
diff --git a/activerecord/test/fixtures/speedometers.yml b/activerecord/test/fixtures/speedometers.yml
new file mode 100644
index 0000000000..6a471aba0a
--- /dev/null
+++ b/activerecord/test/fixtures/speedometers.yml
@@ -0,0 +1,4 @@
+cool_first:
+ speedometer_id: s1
+ name: my_speedometer
+ dashboard_id: d1 \ No newline at end of file
diff --git a/activerecord/test/fixtures/subscriptions.yml b/activerecord/test/fixtures/subscriptions.yml
index 371bfd3422..5a93c12193 100644
--- a/activerecord/test/fixtures/subscriptions.yml
+++ b/activerecord/test/fixtures/subscriptions.yml
@@ -9,4 +9,4 @@ webster_rfr:
alterself_awdr:
id: 3
subscriber_id: alterself
- book_id: 3 \ No newline at end of file
+ book_id: 1
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index 655b45bf57..727978431c 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -108,6 +108,8 @@ class Author < ActiveRecord::Base
%w(twitter github)
end
+ validates_presence_of :name
+
private
def log_before_adding(object)
@post_log << "before_adding#{object.id || '<new>'}"
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index cfd07abddc..1e030b4f59 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -1,4 +1,7 @@
class Book < ActiveRecord::Base
has_many :citations, :foreign_key => 'book1_id'
has_many :references, :through => :citations, :source => :reference_of, :uniq => true
+
+ has_many :subscriptions
+ has_many :subscribers, :through => :subscriptions
end
diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb
index 83d71b6909..2c8c30efb4 100644
--- a/activerecord/test/models/company_in_module.rb
+++ b/activerecord/test/models/company_in_module.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/object/misc'
+require 'active_support/core_ext/object/with_options'
module MyApplication
module Business
diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb
new file mode 100644
index 0000000000..15e3a1de0b
--- /dev/null
+++ b/activerecord/test/models/country.rb
@@ -0,0 +1,7 @@
+class Country < ActiveRecord::Base
+
+ set_primary_key :country_id
+
+ has_and_belongs_to_many :treaties
+
+end
diff --git a/activerecord/test/models/dashboard.rb b/activerecord/test/models/dashboard.rb
new file mode 100644
index 0000000000..a8a25834b1
--- /dev/null
+++ b/activerecord/test/models/dashboard.rb
@@ -0,0 +1,3 @@
+class Dashboard < ActiveRecord::Base
+ set_primary_key :dashboard_id
+end \ No newline at end of file
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index de68fd7f24..c61c583c1d 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -88,6 +88,7 @@ class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
scope :by_name, :order => 'name DESC'
+ scope :reordered_by_name, reorder('name DESC')
def self.all_ordered_by_name
with_scope(:find => { :order => 'name DESC' }) do
diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb
new file mode 100644
index 0000000000..35af9f679b
--- /dev/null
+++ b/activerecord/test/models/electron.rb
@@ -0,0 +1,3 @@
+class Electron < ActiveRecord::Base
+ belongs_to :molecule
+end
diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb
new file mode 100644
index 0000000000..b96c054f6c
--- /dev/null
+++ b/activerecord/test/models/liquid.rb
@@ -0,0 +1,5 @@
+class Liquid < ActiveRecord::Base
+ set_table_name :liquid
+ has_many :molecules, :uniq => true
+end
+
diff --git a/activerecord/test/models/minivan.rb b/activerecord/test/models/minivan.rb
new file mode 100644
index 0000000000..602438d16f
--- /dev/null
+++ b/activerecord/test/models/minivan.rb
@@ -0,0 +1,9 @@
+class Minivan < ActiveRecord::Base
+ set_primary_key :minivan_id
+
+ belongs_to :speedometer
+ has_one :dashboard, :through => :speedometer
+
+ attr_readonly :color
+
+end
diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb
new file mode 100644
index 0000000000..69325b8d29
--- /dev/null
+++ b/activerecord/test/models/molecule.rb
@@ -0,0 +1,4 @@
+class Molecule < ActiveRecord::Base
+ belongs_to :liquid
+ has_many :electrons
+end
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 2a73b1ee01..951ec93c53 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -4,6 +4,8 @@ class Person < ActiveRecord::Base
has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null'
has_many :references
+ has_many :bad_references
+ has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference'
has_many :jobs, :through => :references
has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true]
has_many :posts_with_comments_sorted_by_comment_id, :through => :readers, :source => :post, :include => :comments, :order => 'comments.id'
diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb
index 479e8b72c6..4a17c936f5 100644
--- a/activerecord/test/models/reference.rb
+++ b/activerecord/test/models/reference.rb
@@ -2,3 +2,8 @@ class Reference < ActiveRecord::Base
belongs_to :person
belongs_to :job
end
+
+class BadReference < ActiveRecord::Base
+ self.table_name ='references'
+ default_scope :conditions => {:favourite => false }
+end
diff --git a/activerecord/test/models/speedometer.rb b/activerecord/test/models/speedometer.rb
new file mode 100644
index 0000000000..94743eff8e
--- /dev/null
+++ b/activerecord/test/models/speedometer.rb
@@ -0,0 +1,4 @@
+class Speedometer < ActiveRecord::Base
+ set_primary_key :speedometer_id
+ belongs_to :dashboard
+end \ No newline at end of file
diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb
new file mode 100644
index 0000000000..b46537f0d2
--- /dev/null
+++ b/activerecord/test/models/treaty.rb
@@ -0,0 +1,7 @@
+class Treaty < ActiveRecord::Base
+
+ set_primary_key :treaty_id
+
+ has_and_belongs_to_many :countries
+
+end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
new file mode 100644
index 0000000000..c78d99f4af
--- /dev/null
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -0,0 +1,24 @@
+ActiveRecord::Schema.define do
+ create_table :binary_fields, :force => true, :options => 'CHARACTER SET latin1' do |t|
+ t.binary :tiny_blob, :limit => 255
+ t.binary :normal_blob, :limit => 65535
+ t.binary :medium_blob, :limit => 16777215
+ t.binary :long_blob, :limit => 2147483647
+ t.text :tiny_text, :limit => 255
+ t.text :normal_text, :limit => 65535
+ t.text :medium_text, :limit => 16777215
+ t.text :long_text, :limit => 2147483647
+ end
+
+ ActiveRecord::Base.connection.execute <<-SQL
+DROP PROCEDURE IF EXISTS ten;
+SQL
+
+ ActiveRecord::Base.connection.execute <<-SQL
+CREATE PROCEDURE ten() SQL SECURITY INVOKER
+BEGIN
+ select 10;
+END
+SQL
+
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index bea351b95a..fc3810f82b 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -164,6 +164,11 @@ ActiveRecord::Schema.define do
t.string :address_country
t.string :gps_location
end
+
+ create_table :dashboards, :force => true, :id => false do |t|
+ t.string :dashboard_id
+ t.string :name
+ end
create_table :developers, :force => true do |t|
t.string :name
@@ -290,6 +295,13 @@ ActiveRecord::Schema.define do
t.boolean :favourite
t.integer :lock_version, :default => 0
end
+
+ create_table :minivans, :force => true, :id => false do |t|
+ t.string :minivan_id
+ t.string :name
+ t.string :speedometer_id
+ t.string :color
+ end
create_table :minimalistics, :force => true do |t|
end
@@ -386,6 +398,7 @@ ActiveRecord::Schema.define do
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
+ t.timestamps
end
create_table :pirates, :force => true do |t|
@@ -452,6 +465,12 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :ship_id
end
+
+ create_table :speedometers, :force => true, :id => false do |t|
+ t.string :speedometer_id
+ t.string :name
+ t.string :dashboard_id
+ end
create_table :sponsors, :force => true do |t|
t.integer :club_id
@@ -512,6 +531,7 @@ ActiveRecord::Schema.define do
create_table :toys, :primary_key => :toy_id ,:force => true do |t|
t.string :name
t.integer :pet_id, :integer
+ t.timestamps
end
create_table :traffic_lights, :force => true do |t|
@@ -583,6 +603,34 @@ ActiveRecord::Schema.define do
t.string :title
end
+ create_table :countries, :force => true, :id => false, :primary_key => 'country_id' do |t|
+ t.string :country_id
+ t.string :name
+ end
+ create_table :treaties, :force => true, :id => false, :primary_key => 'treaty_id' do |t|
+ t.string :treaty_id
+ t.string :name
+ end
+ create_table :countries_treaties, :force => true, :id => false do |t|
+ t.string :country_id, :null => false
+ t.string :treaty_id, :null => false
+ t.datetime :created_at
+ t.datetime :updated_at
+ end
+
+ create_table :liquid, :force => true do |t|
+ t.string :name
+ end
+ create_table :molecules, :force => true do |t|
+ t.integer :liquid_id
+ t.string :name
+ end
+ create_table :electrons, :force => true do |t|
+ t.integer :molecule_id
+ t.string :name
+ end
+
+
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, :force => true do |t|
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 2b2092b9fe..8fa1b0b4b3 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,3 +1,8 @@
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* No material changes
+
+
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
* JSON: set Base.include_root_in_json = true to include a root value in the JSON: {"post": {"title": ...}}. Mirrors the Active Record option. [Santiago Pastorino]
diff --git a/activeresource/README b/activeresource/README.rdoc
index 127ac5b4a9..127ac5b4a9 100644
--- a/activeresource/README
+++ b/activeresource/README.rdoc
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index 04b08ed8cb..2145f1017c 100644
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/packagetask'
require 'rake/gempackagetask'
@@ -29,13 +29,13 @@ end
# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Resource -- Object-oriented REST services"
- rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/activeresource.rb')
}
@@ -80,9 +80,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/ar", "doc").upload
-end
diff --git a/activeresource/activeresource.gemspec b/activeresource/activeresource.gemspec
index ca74c0dd7d..a71168722b 100644
--- a/activeresource/activeresource.gemspec
+++ b/activeresource/activeresource.gemspec
@@ -14,12 +14,12 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activeresource'
- s.files = Dir['CHANGELOG', 'README', 'examples/**/*', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'examples/**/*', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
- s.extra_rdoc_files = %w( README )
- s.rdoc_options.concat ['--main', 'README']
+ s.extra_rdoc_files = %w( README.rdoc )
+ s.rdoc_options.concat ['--main', 'README.rdoc']
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 6c494a8bcc..62420725ad 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -7,7 +7,6 @@ require 'active_support/core_ext/module/attr_accessor_with_default'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/misc'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/object/duplicable'
require 'set'
@@ -578,7 +577,7 @@ module ActiveResource
# Default value is <tt>site.path</tt>.
def prefix=(value = '/')
# Replace :placeholders with '#{embedded options[:lookups]}'
- prefix_call = value.gsub(/:\w+/) { |key| "\#{options[#{key}]}" }
+ prefix_call = value.gsub(/:\w+/) { |key| "\#{URI.escape options[#{key}].to_s}" }
# Clear prefix parameters in case they have been cached
@prefix_parameters = nil
@@ -623,7 +622,7 @@ module ActiveResource
#
def element_path(id, prefix_options = {}, query_options = nil)
prefix_options, query_options = split_options(prefix_options) if query_options.nil?
- "#{prefix(prefix_options)}#{collection_name}/#{id}.#{format.extension}#{query_string(query_options)}"
+ "#{prefix(prefix_options)}#{collection_name}/#{URI.escape id.to_s}.#{format.extension}#{query_string(query_options)}"
end
# Gets the new element path for REST resources.
@@ -1223,10 +1222,10 @@ module ActiveResource
when Array
resource = find_or_create_resource_for_collection(key)
value.map do |attrs|
- if attrs.is_a?(String) || attrs.is_a?(Numeric)
- attrs.duplicable? ? attrs.dup : attrs
- else
+ if attrs.is_a?(Hash)
resource.new(attrs)
+ else
+ attrs.duplicable? ? attrs.dup : attrs
end
end
when Hash
diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb
index f192c53b4f..75425c01c0 100644
--- a/activeresource/lib/active_resource/http_mock.rb
+++ b/activeresource/lib/active_resource/http_mock.rb
@@ -123,7 +123,11 @@ module ActiveResource
# def post(path, body, headers)
# request = ActiveResource::Request.new(:post, path, body, headers)
# self.class.requests << request
- # self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}"))
+ # if response = self.class.responses.assoc(request)
+ # response[1]
+ # else
+ # raise InvalidRequestError.new("No response recorded for #{request}")
+ # end
# end
module_eval <<-EOE, __FILE__, __LINE__ + 1
def #{method}(path, #{'body, ' if has_body}headers)
diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb
index 198e77a3d1..43c00e9cf1 100644
--- a/activeresource/lib/active_resource/version.rb
+++ b/activeresource/lib/active_resource/version.rb
@@ -3,7 +3,7 @@ module ActiveResource
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb
index 7745a9439b..228dc36d9b 100644
--- a/activeresource/test/cases/base/load_test.rb
+++ b/activeresource/test/cases/base/load_test.rb
@@ -47,6 +47,8 @@ class BaseLoadTest < Test::Unit::TestCase
{ :id => 1, :name => 'Willamette' },
{ :id => 2, :name => 'Columbia', :rafted_by => @matz }],
:postal_codes => [ 97018, 1234567890 ],
+ :dates => [ Time.now ],
+ :votes => [ true, false, true ],
:places => [ "Columbia City", "Unknown" ]}}}
@person = Person.new
@@ -149,6 +151,16 @@ class BaseLoadTest < Test::Unit::TestCase
assert_kind_of Array, places
assert_kind_of String, places.first
assert_equal @deep[:street][:state][:places].first, places.first
+
+ dates = state.dates
+ assert_kind_of Array, dates
+ assert_kind_of Time, dates.first
+ assert_equal @deep[:street][:state][:dates].first, dates.first
+
+ votes = state.votes
+ assert_kind_of Array, votes
+ assert_kind_of TrueClass, votes.first
+ assert_equal @deep[:street][:state][:votes].first, votes.first
end
def test_nested_collections_within_the_same_namespace
diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb
index 4d036d73cc..91b375681b 100644
--- a/activeresource/test/cases/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -6,6 +6,7 @@ require "fixtures/sound"
require "fixtures/beast"
require "fixtures/proxy"
require 'active_support/json'
+require 'active_support/ordered_hash'
require 'active_support/core_ext/hash/conversions'
require 'mocha'
@@ -555,13 +556,14 @@ class BaseTest < Test::Unit::TestCase
assert_equal '/people.xml?name[]=bob&name[]=your+uncle%2Bme&name[]=&name[]=false', Person.collection_path(:name => ['bob', 'your uncle+me', nil, false])
- assert_equal '/people.xml?struct[a][]=2&struct[a][]=1&struct[b]=fred', Person.collection_path(:struct => {:a => [2,1], 'b' => 'fred'})
+ assert_equal '/people.xml?struct[a][]=2&struct[a][]=1&struct[b]=fred', Person.collection_path(:struct => ActiveSupport::OrderedHash[:a, [2,1], 'b', 'fred'])
end
def test_custom_element_path
assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, :person_id => 1)
assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 1)
assert_equal '/people/Greg/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 'Greg')
+ assert_equal '/people/ann%20mary/addresses/ann%20mary.xml', StreetAddress.element_path(:'ann mary', 'person_id' => 'ann mary')
end
def test_module_element_path
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 60586cf99a..8485e7d46b 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,4 +1,6 @@
-*Rails 3.0.0 [Release Candidate] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Removed Object#returning, Object#tap should be used instead. [Santiago Pastorino]
* Deprecation behavior is no longer hardcoded to the name of the environment.
Instead, it is set via config.active_support.deprecation and can be one
diff --git a/activesupport/README b/activesupport/README
deleted file mode 100644
index 9fb9a80cbe..0000000000
--- a/activesupport/README
+++ /dev/null
@@ -1,43 +0,0 @@
-= Active Support -- Utility classes and standard library extensions from Rails
-
-Active Support is a collection of various utility classes and standard library extensions that were found useful
-for Rails. All these additions have hence been collected in this bundle as way to gather all that sugar that makes
-Ruby sweeter.
-
-
-== Download
-
-The latest version of Active Support can be found at
-
-* http://rubyforge.org/project/showfiles.php?group_id=182
-
-Documentation can be found at
-
-* http://as.rubyonrails.com
-
-
-== Installation
-
-The preferred method of installing Active Support is through its GEM file. You'll need to have
-RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have it,
-then use:
-
- % [sudo] gem install activesupport-1.0.0.gem
-
-
-== License
-
-Active Support is released under the MIT license.
-
-
-== Support
-
-The Active Support homepage is http://www.rubyonrails.com. You can find the Active Support
-RubyForge page at http://rubyforge.org/projects/activesupport. And as Jim from Rake says:
-
- Feel free to submit commits or feature requests. If you send a patch,
- remember to update the corresponding unit tests. If fact, I prefer
- new feature to be submitted in the form of new unit tests.
-
-For other information, feel free to ask on the ruby-talk mailing list
-(which is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
new file mode 100644
index 0000000000..77b8a64304
--- /dev/null
+++ b/activesupport/README.rdoc
@@ -0,0 +1,33 @@
+= Active Support -- Utility classes and Ruby extensions from Rails
+
+Active Support is a collection of utility classes and standard library
+extensions that were found useful for the Rails framework. These additions
+reside in this package so they can be loaded as needed in Ruby projects
+outside of Rails.
+
+
+== Download and installation
+
+The latest version of Active Support can be installed with Rubygems:
+
+ % [sudo] gem install activesupport
+
+Source code can be downloaded as part of the Rails project on GitHub
+
+* http://github.com/rails/rails/tree/master/activesupport/
+
+
+== License
+
+Active Support is released under the MIT license.
+
+
+== Support
+
+API documentation is at
+
+* http://api.rubyonrails.com
+
+Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
+
+* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 2aebe05de2..8e2683ef89 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,7 +1,7 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/gempackagetask'
task :default => :test
@@ -22,13 +22,13 @@ dist_dirs = [ "lib", "test"]
# Genereate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails"
- rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG')
rdoc.rdoc_files.include('lib/active_support.rb')
rdoc.rdoc_files.include('lib/active_support/**/*.rb')
}
@@ -45,9 +45,3 @@ task :release => :package do
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end
-
-desc "Publish the API documentation"
-task :pdoc => [:rdoc] do
- require 'rake/contrib/sshpublisher'
- Rake::SshDirPublisher.new("rails@api.rubyonrails.org", "public_html/as", "doc").upload
-end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 8611a1e5fa..df7f68fecf 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'activesupport'
- s.files = Dir['CHANGELOG', 'README', 'lib/**/*']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.has_rdoc = true
diff --git a/activesupport/install.rb b/activesupport/install.rb
deleted file mode 100644
index 9c54d8c1a9..0000000000
--- a/activesupport/install.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-require 'rbconfig'
-require 'find'
-require 'ftools'
-
-include Config
-
-# this was adapted from rdoc's install.rb by ways of Log4r
-
-$sitedir = CONFIG["sitelibdir"]
-unless $sitedir
- version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
- $libdir = File.join(CONFIG["libdir"], "ruby", version)
- $sitedir = $:.find {|x| x =~ /site_ruby/ }
- if !$sitedir
- $sitedir = File.join($libdir, "site_ruby")
- elsif $sitedir !~ Regexp.quote(version)
- $sitedir = File.join($sitedir, version)
- end
-end
-
-# the actual gruntwork
-Dir.chdir("lib")
-
-Find.find("active_support", "active_support.rb") { |f|
- if f[-3..-1] == ".rb"
- File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
- else
- File::makedirs(File.join($sitedir, *f.split(/\//)))
- end
-}
diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb
index 29c3843d16..b861a6f62a 100644
--- a/activesupport/lib/active_support/buffered_logger.rb
+++ b/activesupport/lib/active_support/buffered_logger.rb
@@ -101,7 +101,11 @@ module ActiveSupport
@guard.synchronize do
unless buffer.empty?
old_buffer = buffer
- @log.write(old_buffer.join)
+ all_content = StringIO.new
+ old_buffer.each do |content|
+ all_content << content
+ end
+ @log.write(all_content.string)
end
# Important to do this even if buffer was empty or else @buffer will
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index bef9c98ecf..30195bdea5 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -19,8 +19,6 @@ module ActiveSupport
autoload :SynchronizedMemoryStore, 'active_support/cache/synchronized_memory_store'
autoload :CompressedMemCacheStore, 'active_support/cache/compressed_mem_cache_store'
- EMPTY_OPTIONS = {}.freeze
-
# These options mean something to all cache implementations. Individual cache
# implementations may support additional options.
UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl]
@@ -129,13 +127,6 @@ module ActiveSupport
# cache.namespace = lambda { @last_mod_time } # Set the namespace to a variable
# @last_mod_time = Time.now # Invalidate the entire cache by changing namespace
#
- # All caches support auto expiring content after a specified number of seconds.
- # To set the cache entry time to live, you can either specify +:expires_in+ as
- # an option to the constructor to have it affect all entries or to the +fetch+
- # or +write+ methods for just one entry.
- #
- # cache = ActiveSupport::Cache::MemoryStore.new(:expire_in => 5.minutes)
- # cache.write(key, value, :expire_in => 1.minute) # Set a lower value for one entry
#
# Caches can also store values in a compressed format to save space and reduce
# time spent sending data. Since there is some overhead, values must be large
@@ -147,7 +138,7 @@ module ActiveSupport
cattr_accessor :logger, :instance_writer => true
- attr_reader :silence
+ attr_reader :silence, :options
alias :silence? :silence
# Create a new cache. The options will be passed to any write method calls except
@@ -156,11 +147,6 @@ module ActiveSupport
@options = options ? options.dup : {}
end
- # Get the default options set when the cache was created.
- def options
- @options ||= {}
- end
-
# Silence the logger.
def silence!
@silence = true
@@ -175,7 +161,7 @@ module ActiveSupport
@silence = previous_silence
end
- # Set to true if cache stores should be instrumented. By default is false.
+ # Set to true if cache stores should be instrumented. Default is false.
def self.instrument=(boolean)
Thread.current[:instrument_cache_store] = boolean
end
@@ -187,7 +173,7 @@ module ActiveSupport
# Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned.
#
- # If there is no such data in the cache (a cache miss occurred), then
+ # If there is no such data in the cache (a cache miss occurred),
# then nil will be returned. However, if a block has been passed, then
# that block will be run in the event of a cache miss. The return value
# of the block will be written to the cache under the given cache key,
@@ -211,23 +197,30 @@ module ActiveSupport
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
#
- # Setting <tt>:expires_in</tt> will set an expiration time on the cache
- # entry if it is set by call.
#
- # Setting <tt>:race_condition_ttl</tt> will invoke logic on entries set with
- # an <tt>:expires_in</tt> option. If an entry is found in the cache that is
- # expired and it has been expired for less than the number of seconds specified
- # by this option and a block was passed to the method call, then the expiration
- # future time of the entry in the cache will be updated to that many seconds
- # in the and the block will be evaluated and written to the cache.
+ # Setting <tt>:expires_in</tt> will set an expiration time on the cache. All caches
+ # support auto expiring content after a specified number of seconds. This value can
+ # be specified as an option to the construction in which call all entries will be
+ # affected. Or it can be supplied to the +fetch+ or +write+ method for just one entry.
#
- # This is very useful in situations where a cache entry is used very frequently
- # under heavy load. The first process to find an expired cache entry will then
- # become responsible for regenerating that entry while other processes continue
- # to use the slightly out of date entry. This can prevent race conditions where
- # too many processes are trying to regenerate the entry all at once. If the
- # process regenerating the entry errors out, the entry will be regenerated
- # after the specified number of seconds.
+ # cache = ActiveSupport::Cache::MemoryStore.new(:expire_in => 5.minutes)
+ # cache.write(key, value, :expire_in => 1.minute) # Set a lower value for one entry
+ #
+ # Setting <tt>:race_condition_ttl</tt> is very useful in situations where a cache entry
+ # is used very frequently unver heavy load. If a cache expires and due to heavy load
+ # seven different processes will try to read data natively and then they all will try to
+ # write to cache. To avoid that case the first process to find an expired cache entry will
+ # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. Yes
+ # this process is extending the time for a stale value by another few seconds. Because
+ # of extended life of the previous cache, other processes will continue to use slightly
+ # stale data for a just a big longer. In the meantime that first process will go ahead
+ # and will write into cache the new value. After that all the processes will start
+ # getting new value. The key is to keep <tt>:race_condition_ttl</tt> small.
+ #
+ # If the process regenerating the entry errors out, the entry will be regenerated
+ # after the specified number of seconds. Also note that the life of stale cache is
+ # extended only if it expired recently. Otherwise a new value is generated and
+ # <tt>:race_condition_ttl</tt> does not play any role.
#
# # Set all values to expire after one minute.
# cache = ActiveSupport::Cache::MemoryCache.new(:expires_in => 1.minute)
@@ -252,6 +245,7 @@ module ActiveSupport
#
# # val_1 => "new value 1"
# # val_2 => "original value"
+ # # sleep 10 # First thread extend the life of cache by another 10 seconds
# # cache.fetch("foo") => "new value 1"
#
# Other options will be handled by the specific cache store implementation.
@@ -353,11 +347,9 @@ module ActiveSupport
results
end
- # Writes the given value to the cache, with the given key.
+ # Writes the value to the cache, with the key.
#
- # You may also specify additional options via the +options+ argument.
- # The specific cache store implementation will decide what to do with
- # +options+.
+ # Options are passed to the underlying cache implementation.
def write(name, value, options = nil)
options = merged_options(options)
instrument(:write, name, options) do |payload|
@@ -366,7 +358,7 @@ module ActiveSupport
end
end
- # Delete an entry in the cache. Returns +true+ if there was an entry to delete.
+ # Deletes an entry in the cache. Returns +true+ if an entry is deleted.
#
# Options are passed to the underlying cache implementation.
def delete(name, options = nil)
@@ -376,7 +368,7 @@ module ActiveSupport
end
end
- # Return true if the cache contains an entry with this name.
+ # Return true if the cache contains an entry for the given key.
#
# Options are passed to the underlying cache implementation.
def exist?(name, options = nil)
@@ -391,11 +383,11 @@ module ActiveSupport
end
end
- # Delete all entries whose keys match a pattern.
+ # Delete all entries with keys matching the pattern.
#
# Options are passed to the underlying cache implementation.
#
- # Not all implementations may support +delete_matched+.
+ # All implementations may not support this method.
def delete_matched(matcher, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
end
@@ -404,7 +396,7 @@ module ActiveSupport
#
# Options are passed to the underlying cache implementation.
#
- # Not all implementations may support +delete_matched+.
+ # All implementations may not support this method.
def increment(name, amount = 1, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
@@ -413,28 +405,26 @@ module ActiveSupport
#
# Options are passed to the underlying cache implementation.
#
- # Not all implementations may support +delete_matched+.
+ # All implementations may not support this method.
def decrement(name, amount = 1, options = nil)
raise NotImplementedError.new("#{self.class.name} does not support decrement")
end
- # Cleanup the cache by removing expired entries. Not all cache implementations may
- # support this method.
+ # Cleanup the cache by removing expired entries.
#
# Options are passed to the underlying cache implementation.
#
- # Not all implementations may support +delete_matched+.
+ # All implementations may not support this method.
def cleanup(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
end
- # Clear the entire cache. Not all cache implementations may support this method.
- # You should be careful with this method since it could affect other processes
- # if you are using a shared cache.
+ # Clear the entire cache. Be careful with this method since it could
+ # affect other processes if shared cache is being used.
#
# Options are passed to the underlying cache implementation.
#
- # Not all implementations may support +delete_matched+.
+ # All implementations may not support this method.
def clear(options = nil)
raise NotImplementedError.new("#{self.class.name} does not support clear")
end
@@ -483,9 +473,9 @@ module ActiveSupport
end
end
- # Expand a key to be a consistent string value. If the object responds to +cache_key+,
- # it will be called. Otherwise, the to_param method will be called. If the key is a
- # Hash, the keys will be sorted alphabetically.
+ # Expand key to be a consistent string value. Invoke +cache_key+ if
+ # object responds to +cache_key+. Otherwise, to_param method will be
+ # called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
if key.respond_to?(:cache_key)
key = key.cache_key.to_s
@@ -502,7 +492,7 @@ module ActiveSupport
end
end
- # Prefix a key with the namespace. The two values will be delimited with a colon.
+ # Prefix a key with the namespace. Namespace and key will be delimited with a colon.
def namespaced_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
@@ -599,7 +589,7 @@ module ActiveSupport
end
end
- # Set a new time to live on the entry so it expires at the given time.
+ # Set a new time when the entry will expire.
def expires_at=(time)
if time
@expires_in = time.to_f - @created_at
@@ -608,12 +598,12 @@ module ActiveSupport
end
end
- # Seconds since the epoch when the cache entry will expire.
+ # Seconds since the epoch when the entry will expire.
def expires_at
@expires_in ? @created_at + @expires_in : nil
end
- # Get the size of the cached value. This could be less than value.size
+ # Returns the size of the cached value. This could be less than value.size
# if the data is compressed.
def size
if @value.nil?
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 852defeae8..f32b562368 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -16,8 +16,7 @@ module ActiveSupport
# Special features:
# - Clustering and load balancing. One can specify multiple memcached servers,
# and MemCacheStore will load balance between all available servers. If a
- # server goes down, then MemCacheStore will ignore it until it goes back
- # online.
+ # server goes down, then MemCacheStore will ignore it until it comes back up.
#
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in memory cache inside of a block.
@@ -69,7 +68,7 @@ module ActiveSupport
extend LocalCacheWithRaw
end
- # Reads multiple keys from the cache using a single call to the
+ # Reads multiple values from the cache using a single call to the
# servers for all keys. Options can be passed in the last argument.
def read_multi(*names)
options = names.extract_options!
@@ -113,7 +112,7 @@ module ActiveSupport
end
# Clear the entire cache on all memcached servers. This method should
- # be used with care when using a shared cache.
+ # be used with care when shared cache is being used.
def clear(options = nil)
@data.flush_all
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index f5c2b8af8b..b15bb42c88 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -5,9 +5,9 @@ module ActiveSupport
# A cache store implementation which stores everything into memory in the
# same process. If you're running multiple Ruby on Rails server processes
# (which is the case if you're using mongrel_cluster or Phusion Passenger),
- # then this means that your Rails server process instances won't be able
+ # then this means that Rails server process instances won't be able
# to share cache data with each other and this may not be the most
- # appropriate cache for you.
+ # appropriate cache in that scenario.
#
# This cache has a bounded size specified by the :size options to the
# initializer (default is 32Mb). When the cache exceeds the allotted size,
@@ -47,8 +47,8 @@ module ActiveSupport
end
end
- # Prune the cache down so the entries fit within the specified memory size by removing
- # the least recently accessed entries.
+ # To ensure entries fit within the specified memory prune the cache by removing the least
+ # recently accessed entries.
def prune(target_size, max_time = nil)
return if pruning?
@pruning = true
@@ -67,7 +67,7 @@ module ActiveSupport
end
end
- # Return true if the cache is currently be pruned to remove older entries.
+ # Returns true if the cache is currently being pruned.
def pruning?
@pruning
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index efb5ad26ab..3edba52fc4 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -8,7 +8,7 @@ module ActiveSupport
# duration of a block. Repeated calls to the cache for the same key will hit the
# in memory cache for faster access.
module LocalCache
- # Simple memory backed cache. This cache is not thread safe but is intended only
+ # Simple memory backed cache. This cache is not thread safe and is intended only
# for serving as a temporary memory cache for a single thread.
class LocalStore < Store
def initialize
@@ -16,7 +16,7 @@ module ActiveSupport
@data = {}
end
- # Since it isn't thread safe, don't allow synchronizing.
+ # Don't allow synchronizing since it isn't thread safe,
def synchronize # :nodoc:
yield
end
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- # Use a local cache to front for the cache for the duration of a block.
+ # Use a local cache for the duration of block.
def with_local_cache
save_val = Thread.current[thread_local_key]
begin
@@ -50,8 +50,8 @@ module ActiveSupport
end
end
- # Middleware class can be inserted as a Rack handler to use a local cache for the
- # duration of a request.
+ # Middleware class can be inserted as a Rack handler to be local cache for the
+ # duration of request.
def middleware
@middleware ||= begin
klass = Class.new
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 1c7802f7de..24e407c253 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -419,7 +419,10 @@ module ActiveSupport
@_keyed_callbacks ||= {}
@_keyed_callbacks[name] ||= begin
str = send("_#{kind}_callbacks").compile(name, object)
- class_eval "def #{name}() #{str} end", __FILE__, __LINE__
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ def #{name}() #{str} end
+ protected :#{name}
+ RUBY_EVAL
true
end
end
@@ -483,7 +486,11 @@ module ActiveSupport
end
end
- # Skip a previously defined callback for a given type.
+ # Skip a previously defined callback.
+ #
+ # class Writer < Person
+ # skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
+ # end
#
def skip_callback(name, *filter_list, &block)
__update_callbacks(name, filter_list, block) do |chain, type, filters, options|
@@ -523,7 +530,8 @@ module ActiveSupport
# This macro accepts the following options:
#
# * <tt>:terminator</tt> - Indicates when a before filter is considered
- # to be halted.
+ # to halted. This is a string to be eval'ed and has the result of the
+ # very filter available in the <tt>result</tt> variable:
#
# define_callbacks :validate, :terminator => "result == false"
#
@@ -568,7 +576,9 @@ module ActiveSupport
#
# would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
# <tt>"#{kind}_#{name}"</tt> on the given instance. In this case "kind" is "before" and
- # "name" is "save".
+ # "name" is "save". In this context ":kind" and ":name" have special meanings: ":kind"
+ # refers to the kind of callback (before/after/around) and ":name" refers to the
+ # method on which callbacks are being defined.
#
# A declaration like
#
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index eb31f7cad4..2d87e8d0e5 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -1,3 +1,38 @@
+# A typical module looks like this
+#
+# module M
+# def self.included(base)
+# base.send(:extend, ClassMethods)
+# base.send(:include, InstanceMethods)
+# scope :foo, :conditions => { :created_at => nil }
+# end
+#
+# module ClassMethods
+# def cm; puts 'I am a class method'; end
+# end
+#
+# module InstanceMethods
+# def im; puts 'I am an instance method'; end
+# end
+# end
+#
+# By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
+#
+# module M
+# extend ActiveSupport::Concern
+#
+# included do
+# scope :foo, :conditions => { :created_at => nil }
+# end
+#
+# module ClassMethods
+# def cm; puts 'I am a class method'; end
+# end
+#
+# module InstanceMethods
+# def im; puts 'I am an instance method'; end
+# end
+# end
module ActiveSupport
module Concern
def self.extended(base)
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 79e3828817..7585137aca 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -58,12 +58,12 @@ class Array
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
- # Returns a string that represents this array in XML by sending +to_xml+
- # to each element. Active Record collections delegate their representation
+ # Returns a string that represents the array in XML by invoking +to_xml+
+ # on each element. Active Record collections delegate their representation
# in XML to this method.
#
# All elements are expected to respond to +to_xml+, if any of them does
- # not an exception is raised.
+ # not then an exception is raised.
#
# The root node reflects the class name of the first element in plural
# if all elements belong to the same type and that's not Hash:
@@ -115,8 +115,8 @@ class Array
# <?xml version="1.0" encoding="UTF-8"?>
# <projects type="array"/>
#
- # By default root children have as node name the one of the root
- # singularized. You can change it with the <tt>:children</tt> option.
+ # By default name of the node for the children of root is <tt>root.singularize</tt>.
+ # You can change it with the <tt>:children</tt> option.
#
# The +options+ hash is passed downwards:
#
diff --git a/activesupport/lib/active_support/core_ext/array/uniq_by.rb b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
index a09b2302fd..bd5c7a187f 100644
--- a/activesupport/lib/active_support/core_ext/array/uniq_by.rb
+++ b/activesupport/lib/active_support/core_ext/array/uniq_by.rb
@@ -2,7 +2,7 @@ class Array
# Return an unique array based on the criteria given as a proc.
#
# [1, 2, 3, 4].uniq_by { |i| i.odd? }
- # #=> [1, 2]
+ # # => [1, 2]
#
def uniq_by
hash, array = {}, []
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index e211bdeeca..06b2acd662 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -1,15 +1,41 @@
class Array
- # Wraps the object in an Array unless it's an Array. Converts the
- # object to an Array using #to_ary if it implements that.
+ # Wraps its argument in an array unless it is already an array (or array-like).
#
- # It differs with Array() in that it does not call +to_a+ on
- # the argument:
+ # Specifically:
+ #
+ # * If the argument is +nil+ an empty list is returned.
+ # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
+ # * Otherwise, returns an array with the argument as its single element.
+ #
+ # Array.wrap(nil) # => []
+ # Array.wrap([1, 2, 3]) # => [1, 2, 3]
+ # Array.wrap(0) # => [0]
+ #
+ # This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
+ #
+ # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
+ # moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns
+ # such a +nil+ right away.
+ # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
+ # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
+ # * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
+ #
+ # The last point is particularly worth comparing for some enumerables:
#
# Array(:foo => :bar) # => [[:foo, :bar]]
# Array.wrap(:foo => :bar) # => [{:foo => :bar}]
#
# Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
# Array.wrap("foo\nbar") # => ["foo\nbar"]
+ #
+ # There's also a related idiom that uses the splat operator:
+ #
+ # [*object]
+ #
+ # which returns <tt>[nil]</tt> for +nil+, and calls to <tt>Array(object)</tt> otherwise.
+ #
+ # Thus, in this case the behavior is different for +nil+, and the differences with
+ # <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
def self.wrap(object)
if object.nil?
[]
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index 576366e496..bfa57fe1f7 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -2,8 +2,8 @@ require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/remove_method'
class Class
- # Declare a class-level attribute whose value is inheritable and
- # overwritable by subclasses:
+ # Declare a class-level attribute whose value is inheritable by subclasses.
+ # Subclasses can change their own value and it will not impact parent class.
#
# class Base
# class_attribute :setting
@@ -18,12 +18,34 @@ class Class
# Subclass.setting # => false
# Base.setting # => true
#
+ # In the above case as long as Subclass does not assign a value to setting
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
+ # would read value assigned to parent class. Once Subclass assigns a value then
+ # the value assigned by Subclass would be returned.
+ #
# This matches normal Ruby method inheritance: think of writing an attribute
- # on a subclass as overriding the reader method.
+ # on a subclass as overriding the reader method. However, you need to be aware
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
+ # In such cases, you don't want to do changes in places but use setters:
+ #
+ # Base.setting = []
+ # Base.setting #=> []
+ # Subclass.setting #=> []
+ #
+ # # Appending in child changes both parent and child because it is the same object:
+ # Subclass.setting << :foo
+ # Base.setting #=> [:foo]
+ # Subclass.setting #=> [:foo]
+ #
+ # # Use setters to not propagate changes:
+ # Base.setting = []
+ # Subclass.setting += [:foo]
+ # Base.setting #=> []
+ # Subclass.setting #=> [:foo]
#
# For convenience, a query method is defined as well:
#
- # Subclass.setting? # => false
+ # Subclass.setting? # => false
#
# Instances may overwrite the class value in the same way:
#
diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
index feef5d2d57..4e35b1b488 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb
@@ -3,11 +3,27 @@ require 'active_support/core_ext/array/extract_options'
# Extends the class object with class and instance accessors for class attributes,
# just like the native attr* accessors for instance attributes.
#
+# Note that unlike +class_attribute+, if a subclass changes the value then that would
+# also change the value for parent class. Similarly if parent class changes the value
+# then that would change the value of subclasses too.
+#
# class Person
# cattr_accessor :hair_colors
# end
#
# Person.hair_colors = [:brown, :black, :blonde, :red]
+# Person.hair_colors #=> [:brown, :black, :blonde, :red]
+# Person.new.hair_colors #=> [:brown, :black, :blonde, :red]
+#
+# To opt out of the instance writer method, pass :instance_writer => false.
+# To opt out of the instance reader method, pass :instance_reader => false.
+#
+# class Person
+# cattr_accessor :hair_colors, :instance_writer => false, :instance_reader => false
+# end
+#
+# Person.new.hair_colors = [:brown] # => NoMethodError
+# Person.new.hair_colors # => NoMethodError
class Class
def cattr_reader(*syms)
options = syms.extract_options!
diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
index 7aff05dcdf..e844cf50d1 100644
--- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
+++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb
@@ -1,17 +1,39 @@
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/array/extract_options'
-# Retain for backward compatibility. Methods are now included in Class.
+# Retained for backward compatibility. Methods are now included in Class.
module ClassInheritableAttributes # :nodoc:
end
-# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
+# It is recommend to use <tt>class_attribute</tt> over methods defined in this file. Please
+# refer to documentation for <tt>class_attribute</tt> for more information. Officially it is not
+# deprected but <tt>class_attribute</tt> is faster.
+#
+# Allows attributes to be shared within an inheritance hierarchy. Each descendant gets a copy of
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
# to, for example, an array without those additions being shared with either their parent, siblings, or
-# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
+# children. This is unlike the regular class-level attributes that are shared across the entire hierarchy.
#
# The copies of inheritable parent attributes are added to subclasses when they are created, via the
# +inherited+ hook.
+#
+# class Person
+# class_inheritable_accessor :hair_colors
+# end
+#
+# Person.hair_colors = [:brown, :black, :blonde, :red]
+# Person.hair_colors #=> [:brown, :black, :blonde, :red]
+# Person.new.hair_colors #=> [:brown, :black, :blonde, :red]
+#
+# To opt out of the instance writer method, pass :instance_writer => false.
+# To opt out of the instance reader method, pass :instance_reader => false.
+#
+# class Person
+# class_inheritable_accessor :hair_colors :instance_writer => false, :instance_reader => false
+# end
+#
+# Person.new.hair_colors = [:brown] # => NoMethodError
+# Person.new.hair_colors # => NoMethodError
class Class # :nodoc:
def class_inheritable_reader(*syms)
options = syms.extract_options!
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index e6a213625c..c5b54318ce 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -40,23 +40,23 @@ class Date
end
end
- # Tells whether the Date object's date lies in the past
+ # Returns true if the Date object's date lies in the past. Otherwise returns false.
def past?
self < ::Date.current
end
- # Tells whether the Date object's date is today
+ # Returns true if the Date object's date is today.
def today?
self.to_date == ::Date.current # we need the to_date because of DateTime
end
- # Tells whether the Date object's date lies in the future
+ # Returns true if the Date object's date lies in the future.
def future?
self > ::Date.current
end
# Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
- # and then subtracts the specified number of seconds
+ # and then subtracts the specified number of seconds.
def ago(seconds)
to_time_in_current_zone.since(-seconds)
end
@@ -127,22 +127,22 @@ class Date
)
end
- # Returns a new Date/DateTime representing the time a number of specified months ago
+ # Returns a new Date/DateTime representing the time a number of specified months ago.
def months_ago(months)
advance(:months => -months)
end
- # Returns a new Date/DateTime representing the time a number of specified months in the future
+ # Returns a new Date/DateTime representing the time a number of specified months in the future.
def months_since(months)
advance(:months => months)
end
- # Returns a new Date/DateTime representing the time a number of specified years ago
+ # Returns a new Date/DateTime representing the time a number of specified years ago.
def years_ago(years)
advance(:years => -years)
end
- # Returns a new Date/DateTime representing the time a number of specified years in the future
+ # Returns a new Date/DateTime representing the time a number of specified years in the future.
def years_since(years)
advance(:years => years)
end
@@ -152,22 +152,22 @@ class Date
years_ago(1)
end unless method_defined?(:prev_year)
- # Short-hand for years_since(1)
+ # Shorthand for years_since(1)
def next_year
years_since(1)
end unless method_defined?(:next_year)
- # Short-hand for months_ago(1)
+ # Shorthand for months_ago(1)
def prev_month
months_ago(1)
end unless method_defined?(:prev_month)
- # Short-hand for months_since(1)
+ # Shorthand for months_since(1)
def next_month
months_since(1)
end unless method_defined?(:next_month)
- # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00)
+ # Returns a new Date/DateTime representing the "start" of this week (i.e, Monday; DateTime objects will have time set to 0:00).
def beginning_of_week
days_to_monday = self.wday!=0 ? self.wday-1 : 6
result = self - days_to_monday
@@ -176,7 +176,7 @@ class Date
alias :monday :beginning_of_week
alias :at_beginning_of_week :beginning_of_week
- # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59)
+ # Returns a new Date/DateTime representing the end of this week (Sunday, DateTime objects will have time set to 23:59:59).
def end_of_week
days_to_sunday = self.wday!=0 ? 7-self.wday : 0
result = self + days_to_sunday.days
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index d0821a7c68..f76ed401cd 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -66,7 +66,8 @@ module Enumerable
# +memo+ to the block. Handy for building up hashes or
# reducing collections down to one object. Examples:
#
- # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } #=> {'foo' => 'FOO', 'bar' => 'BAR'}
+ # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase }
+ # # => {'foo' => 'FOO', 'bar' => 'BAR'}
#
# *Note* that you can't use immutable objects like numbers, true or false as
# the memo. You would think the following returns 120, but since the memo is
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 565c9af7fb..2763af6121 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -83,7 +83,7 @@ class Hash
case value.class.to_s
when 'Hash'
if value['type'] == 'array'
- child_key, entries = Array.wrap(value.detect { |k,v| k != 'type' }) # child_key is throwaway
+ _, entries = Array.wrap(value.detect { |k,v| k != 'type' })
if entries.nil? || (c = value['__content__'] && c.blank?)
[]
else
diff --git a/activesupport/lib/active_support/core_ext/kernel/requires.rb b/activesupport/lib/active_support/core_ext/kernel/requires.rb
index d2238898d6..3bf46271d7 100644
--- a/activesupport/lib/active_support/core_ext/kernel/requires.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/requires.rb
@@ -11,13 +11,13 @@ module Kernel
# 1. Requiring the module is unsuccessful, maybe it's a gem and nobody required rubygems yet. Try.
begin
require 'rubygems'
- rescue LoadError => rubygems_not_installed
+ rescue LoadError # => rubygems_not_installed
raise cannot_require
end
# 2. Rubygems is installed and loaded. Try to load the library again
begin
require library_name
- rescue LoadError => gem_not_installed
+ rescue LoadError # => gem_not_installed
raise cannot_require
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index 9c4d5fae26..2d88cb57e5 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -5,9 +5,7 @@ class Module
options = syms.extract_options!
syms.each do |sym|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
+ @@#{sym} = nil unless defined? @@#{sym}
def self.#{sym}
@@#{sym}
@@ -28,10 +26,6 @@ class Module
options = syms.extract_options!
syms.each do |sym|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
- unless defined? @@#{sym}
- @@#{sym} = nil
- end
-
def self.#{sym}=(obj)
@@#{sym} = obj
end
diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 2714a46b28..b8c01aca0e 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -3,4 +3,9 @@ class Module
remove_method(method)
rescue NameError
end
+
+ def redefine_method(method, &block)
+ remove_possible_method(method)
+ define_method(method, &block)
+ end
end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 27618b55c6..d671da6711 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -2,12 +2,11 @@ require 'active_support/core_ext/object/acts_like'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
-require 'active_support/core_ext/object/misc'
-require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/to_json'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/object/to_query'
diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb
deleted file mode 100644
index 3e3af03cc5..0000000000
--- a/activesupport/lib/active_support/core_ext/object/misc.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-require 'active_support/core_ext/object/returning'
-require 'active_support/core_ext/object/with_options'
diff --git a/activesupport/lib/active_support/core_ext/object/returning.rb b/activesupport/lib/active_support/core_ext/object/returning.rb
index 0dc2e1266a..07250b2a27 100644
--- a/activesupport/lib/active_support/core_ext/object/returning.rb
+++ b/activesupport/lib/active_support/core_ext/object/returning.rb
@@ -25,7 +25,7 @@ class Object
# end
#
# foo # => ['bar', 'baz']
- #
+ #
# # returning with a block argument
# def foo
# returning [] do |values|
@@ -33,10 +33,11 @@ class Object
# values << 'baz'
# end
# end
- #
+ #
# foo # => ['bar', 'baz']
def returning(value)
+ ActiveSupport::Deprecation.warn('Object#returning has been deprecated in favor of Object#tap.', caller)
yield(value)
value
end
-end
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb
index 06f077e920..f2e7c2351e 100644
--- a/activesupport/lib/active_support/core_ext/object/to_param.rb
+++ b/activesupport/lib/active_support/core_ext/object/to_param.rb
@@ -44,6 +44,6 @@ class Hash
def to_param(namespace = nil)
collect do |key, value|
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
- end.sort * '&'
+ end * '&'
end
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 66c4034781..32913a06ad 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -1,3 +1,5 @@
+require 'active_support/inflector/methods'
+require 'active_support/inflector/inflections'
# String inflections define new methods on the String class to transform names for different purposes.
# For instance, you can figure out the name of a database from the name of a class.
#
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 16ccd36458..0b974f5e0a 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -12,11 +12,11 @@ class String
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsuled string.
#
# name = 'Claus Müller'
- # name.reverse #=> "rell??M sualC"
- # name.length #=> 13
+ # name.reverse # => "rell??M sualC"
+ # name.length # => 13
#
- # name.mb_chars.reverse.to_s #=> "rellüM sualC"
- # name.mb_chars.length #=> 12
+ # name.mb_chars.reverse.to_s # => "rellüM sualC"
+ # name.mb_chars.length # => 12
#
# In Ruby 1.9 and newer +mb_chars+ returns +self+ because String is (mostly) encoding aware. This means that
# it becomes easy to run one version of your code on multiple Ruby versions.
@@ -26,7 +26,7 @@ class String
# All the methods on the Chars proxy which normally return a string will return a Chars object. This allows
# method chaining on the result of any of these methods.
#
- # name.mb_chars.reverse.length #=> 12
+ # name.mb_chars.reverse.length # => 12
#
# == Interoperability and configuration
#
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 7d5143ba37..2b80bd214f 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -72,10 +72,6 @@ module ActiveSupport #:nodoc:
methods.each { |m| class_eval "def #{m}(*) lock { super } end", __FILE__, __LINE__ }
end
- def get(key)
- (val = assoc(key)) ? val[1] : []
- end
-
locked :concat, :each, :delete_if, :<<
def new_constants_for(frames)
@@ -85,7 +81,18 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
new_constants = mod.local_constant_names - prior_constants
- get(mod_name).concat(new_constants)
+
+ # If we are checking for constants under, say, :Object, nested under something
+ # else that is checking for constants also under :Object, make sure the
+ # parent knows that we have found, and taken care of, the constant.
+ #
+ # In particular, this means that since Kernel.require discards the constants
+ # it finds, parents will be notified that about those constants, and not
+ # consider them "new". As a result, they will not be added to the
+ # autoloaded_constants list.
+ each do |key, value|
+ value.concat(new_constants) if key == mod_name
+ end
new_constants.each do |suffix|
constants << ([mod_name, suffix] - ["Object"]).join("::")
@@ -592,7 +599,7 @@ module ActiveSupport #:nodoc:
# Convert the provided const desc to a qualified constant name (as a string).
# A module, class, symbol, or string may be provided.
def to_constant_name(desc) #:nodoc:
- name = case desc
+ case desc
when String then desc.sub(/^::/, '')
when Symbol then desc.to_s
when Module
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index dec56715be..deb33ab702 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -3,6 +3,13 @@ require 'active_support/inflector'
module ActiveSupport
module Deprecation
class DeprecationProxy #:nodoc:
+ def self.new(*args, &block)
+ object = args.first
+
+ return object unless object
+ super
+ end
+
instance_methods.each { |m| undef_method m unless m =~ /^__|^object_id$/ }
# Don't give a deprecation warning on inspect since test/unit and error
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 6cba84d79e..4d1cfacc95 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -11,9 +11,9 @@ module ActiveSupport
end
def self.descendants(klass)
- @@direct_descendants[klass].inject([]) do |descendants, klass|
- descendants << klass
- descendants.concat klass.descendants
+ @@direct_descendants[klass].inject([]) do |descendants, _klass|
+ descendants << _klass
+ descendants.concat _klass.descendants
end
end
@@ -40,4 +40,4 @@ module ActiveSupport
DescendantsTracker.descendants(self)
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index cd0d66a482..7da357730b 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -4,8 +4,8 @@ require 'active_support/core_ext/object/acts_like'
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
- # Time#advance, respectively. It mainly supports the methods on Numeric,
- # such as in this example:
+ # Time#advance, respectively. It mainly supports the methods on Numeric.
+ # Example:
#
# 1.month.ago # equivalent to Time.now.advance(:months => -1)
class Duration < BasicObject
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index f64f0f44cc..eec5d4cf47 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash/keys'
# This class has dubious semantics and we only have it so that
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index b3dc5b2f3a..de49750083 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -20,6 +20,11 @@ module ActiveSupport
# "active_record".camelize(:lower) # => "activeRecord"
# "active_record/errors".camelize # => "ActiveRecord::Errors"
# "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
+ #
+ # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
+ # though there are cases where that does not hold:
+ #
+ # "SSLError".underscore.camelize # => "SslError"
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
if first_letter_in_uppercase
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
@@ -28,13 +33,18 @@ module ActiveSupport
end
end
- # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
+ # Makes an underscored, lowercase form from the expression in the string.
#
# Changes '::' to '/' to convert namespaces to paths.
#
# Examples:
# "ActiveRecord".underscore # => "active_record"
# "ActiveRecord::Errors".underscore # => active_record/errors
+ #
+ # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
+ # though there are cases where that does not hold:
+ #
+ # "SSLError".underscore.camelize # => "SslError"
def underscore(camel_cased_word)
word = camel_cased_word.to_s.dup
word.gsub!(/::/, '/')
diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb
index 215b3d6f90..4cb9d01077 100644
--- a/activesupport/lib/active_support/json/backends/yaml.rb
+++ b/activesupport/lib/active_support/json/backends/yaml.rb
@@ -13,7 +13,7 @@ module ActiveSupport
json = json.read
end
YAML.load(convert_json_to_yaml(json))
- rescue ArgumentError => e
+ rescue ArgumentError
raise ParseError, "Invalid JSON string"
end
diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb
index 3664431a28..ef43fc0431 100644
--- a/activesupport/lib/active_support/lazy_load_hooks.rb
+++ b/activesupport/lib/active_support/lazy_load_hooks.rb
@@ -1,3 +1,22 @@
+# lazy_load_hooks allows rails to lazily load a lot of components and thus making the app boot faster. Because of
+# this feature now there is no need to require <tt>ActiveRecord::Base</tt> at boot time purely to apply configuration. Instead
+# a hook is registered that applies configuration once <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is used
+# as example but this feature can be applied elsewhere too.
+#
+# Here is an example where +on_load+ method is called to register a hook.
+#
+# initializer "active_record.initialize_timezone" do
+# ActiveSupport.on_load(:active_record) do
+# self.time_zone_aware_attributes = true
+# self.default_timezone = :utc
+# end
+# end
+#
+# When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked.
+# The very last line of +activerecord/lib/active_record/base.rb+ is:
+#
+# ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+#
module ActiveSupport
@load_hooks = Hash.new {|h,k| h[k] = [] }
@loaded = {}
@@ -24,4 +43,4 @@ module ActiveSupport
execute_hook(base, options, hook)
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index 891d718af3..83930b3f0d 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -21,7 +21,7 @@ module ActiveSupport
# ActiveRecord::LogSubscriber.attach_to :active_record
#
# Since we need to know all instance methods before attaching the log subscriber,
- # the line above shuold be called after your ActiveRecord::LogSubscriber definition.
+ # the line above should be called after your ActiveRecord::LogSubscriber definition.
#
# After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
@@ -63,15 +63,9 @@ module ActiveSupport
@@flushable_loggers = nil
log_subscriber.public_methods(false).each do |event|
- notifier.subscribe("#{event}.#{namespace}") do |*args|
- next if log_subscriber.logger.nil?
-
- begin
- log_subscriber.send(event, ActiveSupport::Notifications::Event.new(*args))
- rescue Exception => e
- log_subscriber.logger.error "Could not log #{args[0].inspect} event. #{e.class}: #{e.message}"
- end
- end
+ next if 'call' == event.to_s
+
+ notifier.subscribe("#{event}.#{namespace}", log_subscriber)
end
end
@@ -92,6 +86,17 @@ module ActiveSupport
flushable_loggers.each(&:flush)
end
+ def call(message, *args)
+ return unless logger
+
+ method = message.split('.').first
+ begin
+ send(method, ActiveSupport::Notifications::Event.new(message, *args))
+ rescue Exception => e
+ logger.error "Could not log #{message.inspect} event. #{e.class}: #{e.message}"
+ end
+ end
+
protected
%w(info debug warn error fatal unknown).each do |level|
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 96506a4b2b..9e52cb97a9 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -1,4 +1,5 @@
require 'active_support/log_subscriber'
+require 'active_support/buffered_logger'
module ActiveSupport
class LogSubscriber
@@ -33,7 +34,7 @@ module ActiveSupport
module TestHelper
def setup
@logger = MockLogger.new
- @notifier = ActiveSupport::Notifications::Notifier.new(queue)
+ @notifier = ActiveSupport::Notifications::Fanout.new
ActiveSupport::LogSubscriber.colorize_logging = false
@@ -47,10 +48,14 @@ module ActiveSupport
end
class MockLogger
+ include ActiveSupport::BufferedLogger::Severity
+
attr_reader :flush_count
+ attr_accessor :level
- def initialize
+ def initialize(level = DEBUG)
@flush_count = 0
+ @level = level
@logged = Hash.new { |h,k| h[k] = [] }
end
@@ -65,6 +70,14 @@ module ActiveSupport
def flush
@flush_count += 1
end
+
+ ActiveSupport::BufferedLogger::Severity.constants.each do |severity|
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
+ def #{severity.downcase}?
+ #{severity} >= @level
+ end
+ EOT
+ end
end
# Wait notifications to be published.
@@ -81,10 +94,6 @@ module ActiveSupport
def set_logger(logger)
ActiveSupport::LogSubscriber.logger = logger
end
-
- def queue
- ActiveSupport::Notifications::Fanout.new
- end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 1031662293..6c46b68eaf 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -47,11 +47,11 @@ module ActiveSupport
def secure_compare(a, b)
return false unless a.bytesize == b.bytesize
- l = a.unpack "C*"
+ l = a.unpack "C#{a.bytesize}"
- res = true
- b.each_byte { |byte| res = (byte == l.shift) && res }
- res
+ res = 0
+ b.each_byte { |byte| res |= byte ^ l.shift }
+ res == 0
end
def generate_digest(data)
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 51c2a0edac..019fb2df06 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -11,7 +11,7 @@ module ActiveSupport #:nodoc:
# String methods are proxied through the Chars object, and can be accessed through the +mb_chars+ method. Methods
# which would normally return a String object now return a Chars object so methods can be chained.
#
- # "The Perfect String ".mb_chars.downcase.strip.normalize #=> "the perfect string"
+ # "The Perfect String ".mb_chars.downcase.strip.normalize # => "the perfect string"
#
# Chars objects are perfectly interchangeable with String objects as long as no explicit class checks are made.
# If certain methods do explicitly check the class, call +to_s+ before you pass chars objects to them.
@@ -83,12 +83,13 @@ module ActiveSupport #:nodoc:
include Comparable
- # Returns <tt>-1</tt>, <tt>0</tt> or <tt>+1</tt> depending on whether the Chars object is to be sorted before,
- # equal or after the object on the right side of the operation. It accepts any object that implements +to_s+.
- # See <tt>String#<=></tt> for more details.
+ # Returns -1, 0, or 1, depending on whether the Chars object is to be sorted before,
+ # equal or after the object on the right side of the operation. It accepts any object
+ # that implements +to_s+:
#
- # Example:
- # 'é'.mb_chars <=> 'ü'.mb_chars #=> -1
+ # 'é'.mb_chars <=> 'ü'.mb_chars # => -1
+ #
+ # See <tt>String#<=></tt> for more details.
def <=>(other)
@wrapped_string <=> other.to_s
end
@@ -103,7 +104,7 @@ module ActiveSupport #:nodoc:
# Returns a new Chars object containing the _other_ object concatenated to the string.
#
# Example:
- # ('Café'.mb_chars + ' périferôl').to_s #=> "Café périferôl"
+ # ('Café'.mb_chars + ' périferôl').to_s # => "Café périferôl"
def +(other)
chars(@wrapped_string + other)
end
@@ -111,7 +112,7 @@ module ActiveSupport #:nodoc:
# Like <tt>String#=~</tt> only it returns the character offset (in codepoints) instead of the byte offset.
#
# Example:
- # 'Café périferôl'.mb_chars =~ /ô/ #=> 12
+ # 'Café périferôl'.mb_chars =~ /ô/ # => 12
def =~(other)
translate_offset(@wrapped_string =~ other)
end
@@ -119,7 +120,7 @@ module ActiveSupport #:nodoc:
# Inserts the passed string at specified codepoint offsets.
#
# Example:
- # 'Café'.mb_chars.insert(4, ' périferôl').to_s #=> "Café périferôl"
+ # 'Café'.mb_chars.insert(4, ' périferôl').to_s # => "Café périferôl"
def insert(offset, fragment)
unpacked = Unicode.u_unpack(@wrapped_string)
unless offset > unpacked.length
@@ -135,7 +136,7 @@ module ActiveSupport #:nodoc:
# Returns +true+ if contained string contains _other_. Returns +false+ otherwise.
#
# Example:
- # 'Café'.mb_chars.include?('é') #=> true
+ # 'Café'.mb_chars.include?('é') # => true
def include?(other)
# We have to redefine this method because Enumerable defines it.
@wrapped_string.include?(other)
@@ -144,8 +145,8 @@ module ActiveSupport #:nodoc:
# Returns the position _needle_ in the string, counting in codepoints. Returns +nil+ if _needle_ isn't found.
#
# Example:
- # 'Café périferôl'.mb_chars.index('ô') #=> 12
- # 'Café périferôl'.mb_chars.index(/\w/u) #=> 0
+ # 'Café périferôl'.mb_chars.index('ô') # => 12
+ # 'Café périferôl'.mb_chars.index(/\w/u) # => 0
def index(needle, offset=0)
wrapped_offset = first(offset).wrapped_string.length
index = @wrapped_string.index(needle, wrapped_offset)
@@ -157,8 +158,8 @@ module ActiveSupport #:nodoc:
# string. Returns +nil+ if _needle_ isn't found.
#
# Example:
- # 'Café périferôl'.mb_chars.rindex('é') #=> 6
- # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13
+ # 'Café périferôl'.mb_chars.rindex('é') # => 6
+ # 'Café périferôl'.mb_chars.rindex(/\w/u) # => 13
def rindex(needle, offset=nil)
offset ||= length
wrapped_offset = first(offset).wrapped_string.length
@@ -190,7 +191,7 @@ module ActiveSupport #:nodoc:
# Returns the codepoint of the first character in the string.
#
# Example:
- # 'こんにちは'.mb_chars.ord #=> 12371
+ # 'こんにちは'.mb_chars.ord # => 12371
def ord
Unicode.u_unpack(@wrapped_string)[0]
end
@@ -200,10 +201,10 @@ module ActiveSupport #:nodoc:
# Example:
#
# "¾ cup".mb_chars.rjust(8).to_s
- # #=> " ¾ cup"
+ # # => " ¾ cup"
#
# "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # #=> "   ¾ cup"
+ # # => "   ¾ cup"
def rjust(integer, padstr=' ')
justify(integer, :right, padstr)
end
@@ -213,10 +214,10 @@ module ActiveSupport #:nodoc:
# Example:
#
# "¾ cup".mb_chars.rjust(8).to_s
- # #=> "¾ cup "
+ # # => "¾ cup "
#
# "¾ cup".mb_chars.rjust(8, " ").to_s # Use non-breaking whitespace
- # #=> "¾ cup   "
+ # # => "¾ cup   "
def ljust(integer, padstr=' ')
justify(integer, :left, padstr)
end
@@ -226,10 +227,10 @@ module ActiveSupport #:nodoc:
# Example:
#
# "¾ cup".mb_chars.center(8).to_s
- # #=> " ¾ cup "
+ # # => " ¾ cup "
#
# "¾ cup".mb_chars.center(8, " ").to_s # Use non-breaking whitespace
- # #=> " ¾ cup  "
+ # # => " ¾ cup  "
def center(integer, padstr=' ')
justify(integer, :center, padstr)
end
@@ -244,7 +245,7 @@ module ActiveSupport #:nodoc:
# instances instead of String. This makes chaining methods easier.
#
# Example:
- # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } #=> ["CAF", " P", "RIFERÔL"]
+ # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
def split(*args)
@wrapped_string.split(*args).map { |i| i.mb_chars }
end
@@ -256,12 +257,12 @@ module ActiveSupport #:nodoc:
# s = "Müller"
# s.mb_chars[2] = "e" # Replace character with offset 2
# s
- # #=> "Müeler"
+ # # => "Müeler"
#
# s = "Müller"
# s.mb_chars[1, 2] = "ö" # Replace 2 characters at character offset 1
# s
- # #=> "Möler"
+ # # => "Möler"
def []=(*args)
replace_by = args.pop
# Indexed replace with regular expressions already works
@@ -292,7 +293,7 @@ module ActiveSupport #:nodoc:
# Reverses all characters in the string.
#
# Example:
- # 'Café'.mb_chars.reverse.to_s #=> 'éfaC'
+ # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
def reverse
chars(Unicode.g_unpack(@wrapped_string).reverse.flatten.pack('U*'))
end
@@ -301,7 +302,7 @@ module ActiveSupport #:nodoc:
# character.
#
# Example:
- # 'こんにちは'.mb_chars.slice(2..3).to_s #=> "にち"
+ # 'こんにちは'.mb_chars.slice(2..3).to_s # => "にち"
def slice(*args)
if args.size > 2
raise ArgumentError, "wrong number of arguments (#{args.size} for 1)" # Do as if we were native
@@ -330,7 +331,7 @@ module ActiveSupport #:nodoc:
#
# Example:
# s = 'こんにちは'
- # s.mb_chars.limit(7) #=> "こに"
+ # s.mb_chars.limit(7) # => "こに"
def limit(limit)
slice(0...translate_offset(limit))
end
@@ -338,7 +339,7 @@ module ActiveSupport #:nodoc:
# Convert characters in the string to uppercase.
#
# Example:
- # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s #=> "LAURENT, OÙ SONT LES TESTS ?"
+ # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
def upcase
chars(Unicode.apply_mapping @wrapped_string, :uppercase_mapping)
end
@@ -346,7 +347,7 @@ module ActiveSupport #:nodoc:
# Convert characters in the string to lowercase.
#
# Example:
- # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s #=> "věda a výzkum"
+ # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
def downcase
chars(Unicode.apply_mapping @wrapped_string, :lowercase_mapping)
end
@@ -354,7 +355,7 @@ module ActiveSupport #:nodoc:
# Converts the first character to uppercase and the remainder to lowercase.
#
# Example:
- # 'über'.mb_chars.capitalize.to_s #=> "Über"
+ # 'über'.mb_chars.capitalize.to_s # => "Über"
def capitalize
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
end
@@ -382,8 +383,8 @@ module ActiveSupport #:nodoc:
# Performs canonical decomposition on all the characters.
#
# Example:
- # 'é'.length #=> 2
- # 'é'.mb_chars.decompose.to_s.length #=> 3
+ # 'é'.length # => 2
+ # 'é'.mb_chars.decompose.to_s.length # => 3
def decompose
chars(Unicode.decompose_codepoints(:canonical, Unicode.u_unpack(@wrapped_string)).pack('U*'))
end
@@ -391,8 +392,8 @@ module ActiveSupport #:nodoc:
# Performs composition on all the characters.
#
# Example:
- # 'é'.length #=> 3
- # 'é'.mb_chars.compose.to_s.length #=> 2
+ # 'é'.length # => 3
+ # 'é'.mb_chars.compose.to_s.length # => 2
def compose
chars(Unicode.compose_codepoints(Unicode.u_unpack(@wrapped_string)).pack('U*'))
end
@@ -400,8 +401,8 @@ module ActiveSupport #:nodoc:
# Returns the number of grapheme clusters in the string.
#
# Example:
- # 'क्षि'.mb_chars.length #=> 4
- # 'क्षि'.mb_chars.g_length #=> 3
+ # 'क्षि'.mb_chars.length # => 4
+ # 'क्षि'.mb_chars.g_length # => 3
def g_length
Unicode.g_unpack(@wrapped_string).length
end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 11c72d873b..1139783b65 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -64,7 +64,7 @@ module ActiveSupport
# valid UTF-8.
#
# Example:
- # Unicode.u_unpack('Café') #=> [67, 97, 102, 233]
+ # Unicode.u_unpack('Café') # => [67, 97, 102, 233]
def u_unpack(string)
begin
string.unpack 'U*'
@@ -85,8 +85,8 @@ module ActiveSupport
# Unpack the string at grapheme boundaries. Returns a list of character lists.
#
# Example:
- # Unicode.g_unpack('क्षि') #=> [[2325, 2381], [2359], [2367]]
- # Unicode.g_unpack('Café') #=> [[67], [97], [102], [233]]
+ # Unicode.g_unpack('क्षि') # => [[2325, 2381], [2359], [2367]]
+ # Unicode.g_unpack('Café') # => [[67], [97], [102], [233]]
def g_unpack(string)
codepoints = u_unpack(string)
unpacked = []
@@ -99,15 +99,15 @@ module ActiveSupport
current = codepoints[pos]
if (
# CR X LF
- one = ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
+ ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
# L X (L|V|LV|LVT)
- two = ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
+ ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
# (LV|V) X (V|T)
- three = ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
+ ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
# (LVT|T) X (T)
- four = ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
+ ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
# X Extend
- five = (database.boundary[:extend] === current)
+ (database.boundary[:extend] === current)
)
else
unpacked << codepoints[marker..pos-1]
@@ -120,7 +120,7 @@ module ActiveSupport
# Reverse operation of g_unpack.
#
# Example:
- # Unicode.g_pack(Unicode.g_unpack('क्षि')) #=> 'क्षि'
+ # Unicode.g_pack(Unicode.g_unpack('क्षि')) # => 'क्षि'
def g_pack(unpacked)
(unpacked.flatten).pack('U*')
end
@@ -238,7 +238,6 @@ module ActiveSupport
bytes.each_index do |i|
byte = bytes[i]
- is_ascii = byte < 128
is_cont = byte > 127 && byte < 192
is_lead = byte > 191 && byte < 245
is_unused = byte > 240
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index 1444fc1609..fd79188ba4 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -22,9 +22,9 @@ module ActiveSupport
# end
#
# event = @events.first
- # event.name #=> :render
- # event.duration #=> 10 (in milliseconds)
- # event.payload #=> { :extra => :information }
+ # event.name # => :render
+ # event.duration # => 10 (in milliseconds)
+ # event.payload # => { :extra => :information }
#
# When subscribing to Notifications, you can pass a pattern, to only consume
# events that match the pattern:
@@ -41,39 +41,37 @@ module ActiveSupport
autoload :Event, 'active_support/notifications/instrumenter'
autoload :Fanout, 'active_support/notifications/fanout'
+ @instrumenters = Hash.new { |h,k| h[k] = notifier.listening?(k) }
+
class << self
attr_writer :notifier
- delegate :publish, :subscribe, :unsubscribe, :to => :notifier
- delegate :instrument, :to => :instrumenter
-
- def notifier
- @notifier ||= Notifier.new
- end
-
- def instrumenter
- Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
- end
- end
+ delegate :publish, :to => :notifier
- class Notifier
- def initialize(queue = Fanout.new)
- @queue = queue
+ def instrument(name, payload = {})
+ if @instrumenters[name]
+ instrumenter.instrument(name, payload) { yield payload if block_given? }
+ else
+ yield payload if block_given?
+ end
end
- def publish(*args)
- @queue.publish(*args)
+ def subscribe(*args, &block)
+ notifier.subscribe(*args, &block).tap do
+ @instrumenters.clear
+ end
end
- def subscribe(pattern = nil, &block)
- @queue.bind(pattern).subscribe(&block)
+ def unsubscribe(*args)
+ notifier.unsubscribe(*args)
+ @instrumenters.clear
end
- def unsubscribe(subscriber)
- @queue.unsubscribe(subscriber)
+ def notifier
+ @notifier ||= Fanout.new
end
- def wait
- @queue.wait
+ def instrumenter
+ Thread.current[:"instrumentation_#{notifier.object_id}"] ||= Instrumenter.new(notifier)
end
end
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 300ec842a9..adc34f3286 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -8,85 +8,53 @@ module ActiveSupport
@listeners_for = {}
end
- def bind(pattern)
- Binding.new(self, pattern)
- end
-
- def subscribe(pattern = nil, &block)
+ def subscribe(pattern = nil, block = Proc.new)
+ subscriber = Subscriber.new(pattern, block).tap do |s|
+ @subscribers << s
+ end
@listeners_for.clear
- @subscribers << Subscriber.new(pattern, &block)
- @subscribers.last
+ subscriber
end
def unsubscribe(subscriber)
- @listeners_for.clear
@subscribers.reject! {|s| s.matches?(subscriber)}
+ @listeners_for.clear
end
def publish(name, *args)
- if listeners = @listeners_for[name]
- listeners.each { |s| s.publish(name, *args) }
- else
- @listeners_for[name] = @subscribers.select { |s| s.publish(name, *args) }
- end
+ listeners_for(name).each { |s| s.publish(name, *args) }
end
- # This is a sync queue, so there is not waiting.
- def wait
+ def listeners_for(name)
+ @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
end
- # Used for internal implementation only.
- class Binding #:nodoc:
- def initialize(queue, pattern)
- @queue = queue
- @pattern =
- case pattern
- when Regexp, NilClass
- pattern
- else
- /^#{Regexp.escape(pattern.to_s)}$/
- end
- end
+ def listening?(name)
+ listeners_for(name).any?
+ end
- def subscribe(&block)
- @queue.subscribe(@pattern, &block)
- end
+ # This is a sync queue, so there is not waiting.
+ def wait
end
class Subscriber #:nodoc:
- def initialize(pattern, &block)
+ def initialize(pattern, delegate)
@pattern = pattern
- @block = block
+ @delegate = delegate
end
- def publish(*args)
- return unless subscribed_to?(args.first)
- push(*args)
- true
- end
-
- def drained?
- true
+ def publish(message, *args)
+ @delegate.call(message, *args)
end
def subscribed_to?(name)
- !@pattern || @pattern =~ name.to_s
+ !@pattern || @pattern === name.to_s
end
def matches?(subscriber_or_name)
- case subscriber_or_name
- when String
- @pattern && @pattern =~ subscriber_or_name
- when self
- true
- end
+ self === subscriber_or_name ||
+ @pattern && @pattern === subscriber_or_name
end
-
- private
-
- def push(*args)
- @block.call(*args)
- end
end
end
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 7e89402822..441fefb491 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -15,14 +15,15 @@ module ActiveSupport
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block
def instrument(name, payload={})
- time = Time.now
+ started = Time.now
+
begin
- yield(payload) if block_given?
+ yield
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
raise e
ensure
- @notifier.publish(name, time, Time.now, @id, payload)
+ @notifier.publish(name, started, Time.now, @id, payload)
end
end
@@ -33,7 +34,7 @@ module ActiveSupport
end
class Event
- attr_reader :name, :time, :end, :transaction_id, :payload
+ attr_reader :name, :time, :end, :transaction_id, :payload, :duration
def initialize(name, start, ending, transaction_id, payload)
@name = name
@@ -41,14 +42,11 @@ module ActiveSupport
@time = start
@transaction_id = transaction_id
@end = ending
- end
-
- def duration
- @duration ||= 1000.0 * (@end - @time)
+ @duration = 1000.0 * (@end - @time)
end
def parent_of?(event)
- start = (self.time - event.time) * 1000
+ start = (time - event.time) * 1000
start <= 0 && (start + duration >= event.duration)
end
end
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 6b563b9063..2e8d538d0b 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -4,8 +4,17 @@ YAML.add_builtin_type("omap") do |type, val|
ActiveSupport::OrderedHash[val.map(&:to_a).map(&:first)]
end
-# OrderedHash is namespaced to prevent conflicts with other implementations
module ActiveSupport
+ # The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
+ # order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
+ # implements a hash that preserves insertion order, as in Ruby 1.9:
+ #
+ # oh = ActiveSupport::OrderedHash.new
+ # oh[:a] = 1
+ # oh[:b] = 2
+ # oh.keys # => [:a, :b], this order is guaranteed
+ #
+ # <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
class OrderedHash < ::Hash #:nodoc:
def to_yaml_type
"!tag:yaml.org,2002:omap"
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index 61ccb79211..7fc2b45b51 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -1,5 +1,21 @@
require 'active_support/ordered_hash'
+# Usually key value pairs are handled something like this:
+#
+# h = ActiveSupport::OrderedOptions.new
+# h[:boy] = 'John'
+# h[:girl] = 'Mary'
+# h[:boy] # => 'John'
+# h[:girl] # => 'Mary'
+#
+# Using <tt>OrderedOptions</tt> above code could be reduced to:
+#
+# h = ActiveSupport::OrderedOptions.new
+# h.boy = 'John'
+# h.girl = 'Mary'
+# h.boy # => 'John'
+# h.girl # => 'Mary'
+#
module ActiveSupport #:nodoc:
class OrderedOptions < OrderedHash
def []=(key, value)
diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb
index cfbce4d754..73344498cb 100644
--- a/activesupport/lib/active_support/secure_random.rb
+++ b/activesupport/lib/active_support/secure_random.rb
@@ -26,25 +26,25 @@ module ActiveSupport
# == Example
#
# # random hexadecimal string.
- # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362"
- # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559"
- # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8"
- # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306"
- # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23"
+ # p SecureRandom.hex(10) # => "52750b30ffbc7de3b362"
+ # p SecureRandom.hex(10) # => "92b15d6c8dc4beb5f559"
+ # p SecureRandom.hex(11) # => "6aca1b5c58e4863e6b81b8"
+ # p SecureRandom.hex(12) # => "94b2fff3e7fd9b9c391a2306"
+ # p SecureRandom.hex(13) # => "39b290146bea6ce975c37cfc23"
# ...
#
# # random base64 string.
- # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA=="
- # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w=="
- # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg=="
- # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY="
- # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8"
- # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg=="
+ # p SecureRandom.base64(10) # => "EcmTPZwWRAozdA=="
+ # p SecureRandom.base64(10) # => "9b0nsevdwNuM/w=="
+ # p SecureRandom.base64(10) # => "KO1nIU+p9DKxGg=="
+ # p SecureRandom.base64(11) # => "l7XEiFja+8EKEtY="
+ # p SecureRandom.base64(12) # => "7kJSM/MzBJI+75j8"
+ # p SecureRandom.base64(13) # => "vKLJ0tXBHqQOuIcSIg=="
# ...
#
# # random binary string.
- # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
- # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
+ # p SecureRandom.random_bytes(10) # => "\016\t{\370g\310pbr\301"
+ # p SecureRandom.random_bytes(10) # => "\323U\030TO\234\357\020\a\337"
# ...
module SecureRandom
# SecureRandom.random_bytes generates a random binary string.
diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb
index d8942c3974..b2d9ddfeb7 100644
--- a/activesupport/lib/active_support/testing/setup_and_teardown.rb
+++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb
@@ -96,7 +96,7 @@ module ActiveSupport
protected
def retrieve_mocha_counter(result) #:nodoc:
- if using_mocha = respond_to?(:mocha_verify)
+ if respond_to?(:mocha_verify) # using mocha
if defined?(Mocha::TestCaseAdapter::AssertionCounter)
Mocha::TestCaseAdapter::AssertionCounter.new(result)
else
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 62d02bdeb6..ad6c3de1f5 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -5,9 +5,9 @@ module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are
# limited to UTC and the system's <tt>ENV['TZ']</tt> zone.
#
- # You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> -- instead, Rails provides the methods
- # +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances, for a more
- # user-friendly syntax. Examples:
+ # You shouldn't ever need to create a TimeWithZone instance directly via <tt>new</tt> . Instead use methods
+ # +local+, +parse+, +at+ and +now+ on TimeZone instances, and +in_time_zone+ on Time and DateTime instances.
+ # Examples:
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
@@ -18,7 +18,8 @@ module ActiveSupport
#
# See Time and TimeZone for further documentation of these methods.
#
- # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable. Examples:
+ # TimeWithZone instances implement the same API as Ruby Time instances, so that Time and TimeWithZone instances are interchangeable.
+ # Examples:
#
# t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00
# t.hour # => 13
@@ -113,8 +114,8 @@ module ActiveSupport
end
alias_method :iso8601, :xmlschema
- # Coerces the date to a string for JSON encoding. The default format is ISO 8601. You can get
- # %Y/%m/%d %H:%M:%S +offset style by setting ActiveSupport::JSON::Encoding.use_standard_json_time_format
+ # Coerces time to a string for JSON encoding. The default format is ISO 8601. You can get
+ # %Y/%m/%d %H:%M:%S +offset style by setting <tt>ActiveSupport::JSON::Encoding.use_standard_json_time_format</tt>
# to false.
#
# ==== Examples
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 49dd8a1b99..abd585b64f 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -8,10 +8,10 @@ require 'active_support/core_ext/object/try'
# * Lazily load TZInfo::Timezone instances only when they're needed.
# * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, +parse+, +at+ and +now+ methods.
#
-# If you set <tt>config.time_zone</tt> in the Rails Initializer, you can access this TimeZone object via <tt>Time.zone</tt>:
+# If you set <tt>config.time_zone</tt> in the Rails Application, you can access this TimeZone object via <tt>Time.zone</tt>:
#
-# # environment.rb:
-# Rails::Initializer.run do |config|
+# # application.rb:
+# class Application < Rails::Application
# config.time_zone = "Eastern Time (US & Canada)"
# end
#
diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb
index 52612c27cb..9d2cf13260 100644
--- a/activesupport/lib/active_support/version.rb
+++ b/activesupport/lib/active_support/version.rb
@@ -3,7 +3,7 @@ module ActiveSupport
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 7594d7b68b..352172027b 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -9,7 +9,7 @@ module ActiveSupport
module XmlMini
extend self
- # This module exists to decorate files deserialized using Hash.from_xml with
+ # This module decorates files deserialized using Hash.from_xml with
# the <tt>original_filename</tt> and <tt>content_type</tt> methods.
module FileLike #:nodoc:
attr_writer :original_filename, :content_type
@@ -129,12 +129,16 @@ module ActiveSupport
camelize = options.has_key?(:camelize) && options[:camelize]
dasherize = !options.has_key?(:dasherize) || options[:dasherize]
key = key.camelize if camelize
- key = key.dasherize if dasherize
+ key = _dasherize(key) if dasherize
key
end
protected
+ def _dasherize(key)
+ key.gsub(/(?!^[_]*)_(?![_]*$)/, '-')
+ end
+
# TODO: Add support for other encodings
def _parse_binary(bin, entity) #:nodoc:
case entity['encoding']
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 9cf187302f..7fdcb11465 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,5 +1,4 @@
require 'libxml'
-require 'active_support/core_ext/object/returning'
require 'active_support/core_ext/object/blank'
# = XmlMini LibXML implementation
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index eb61a7fc22..e03a744257 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -1,4 +1,9 @@
-require 'nokogiri'
+begin
+ require 'nokogiri'
+rescue LoadError => e
+ $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
+ raise e
+end
require 'active_support/core_ext/object/blank'
# = XmlMini Nokogiri implementation
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 8af7b5e565..38c8685390 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -1,4 +1,9 @@
-require 'nokogiri'
+begin
+ require 'nokogiri'
+rescue LoadError => e
+ $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install"
+ raise e
+end
require 'active_support/core_ext/object/blank'
# = XmlMini Nokogiri implementation using a SAX-based parser
@@ -80,4 +85,4 @@ module ActiveSupport
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb
new file mode 100644
index 0000000000..e3d1218c96
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/load_path/loaded_constant.rb
@@ -0,0 +1,3 @@
+module LoadedConstant
+end
+
diff --git a/activesupport/test/autoloading_fixtures/loads_constant.rb b/activesupport/test/autoloading_fixtures/loads_constant.rb
new file mode 100644
index 0000000000..0b30dc8bca
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/loads_constant.rb
@@ -0,0 +1,5 @@
+module LoadsConstant
+end
+
+# The _ = assignment is to prevent warnings
+_ = RequiresConstant
diff --git a/activesupport/test/autoloading_fixtures/requires_constant.rb b/activesupport/test/autoloading_fixtures/requires_constant.rb
new file mode 100644
index 0000000000..14804a0de0
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/requires_constant.rb
@@ -0,0 +1,5 @@
+require "loaded_constant"
+
+module RequiresConstant
+end
+
diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb
index 850febb959..97c0ef14db 100644
--- a/activesupport/test/buffered_logger_test.rb
+++ b/activesupport/test/buffered_logger_test.rb
@@ -1,9 +1,12 @@
require 'abstract_unit'
+require 'multibyte_test_helpers'
require 'stringio'
require 'fileutils'
require 'active_support/buffered_logger'
class BufferedLoggerTest < Test::Unit::TestCase
+ include MultibyteTestHelpers
+
Logger = ActiveSupport::BufferedLogger
def setup
@@ -146,4 +149,16 @@ class BufferedLoggerTest < Test::Unit::TestCase
@logger.expects :clear_buffer
@logger.flush
end
+
+ def test_buffer_multibyte
+ @logger.auto_flushing = 2
+ @logger.info(UNICODE_STRING)
+ @logger.info(BYTE_STRING)
+ assert @output.string.include?(UNICODE_STRING)
+ byte_string = @output.string.dup
+ if byte_string.respond_to?(:force_encoding)
+ byte_string.force_encoding("ASCII-8BIT")
+ end
+ assert byte_string.include?(BYTE_STRING)
+ end
end
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 3e14c754b7..b79a7bbaec 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -7,6 +7,49 @@ class CacheKeyTest < ActiveSupport::TestCase
assert_equal '1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true])
assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name)
end
+
+ def test_expand_cache_key_with_rails_cache_id
+ begin
+ ENV['RAILS_CACHE_ID'] = 'c99'
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key([:foo])
+ assert_equal 'c99/c99/foo/c99/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar])
+ assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key(:foo, :nm)
+ assert_equal 'nm/c99/foo', ActiveSupport::Cache.expand_cache_key([:foo], :nm)
+ assert_equal 'nm/c99/c99/foo/c99/bar', ActiveSupport::Cache.expand_cache_key([:foo, :bar], :nm)
+ ensure
+ ENV['RAILS_CACHE_ID'] = nil
+ end
+ end
+
+ def test_expand_cache_key_with_rails_app_version
+ begin
+ ENV['RAILS_APP_VERSION'] = 'rails3'
+ assert_equal 'rails3/foo', ActiveSupport::Cache.expand_cache_key(:foo)
+ ensure
+ ENV['RAILS_APP_VERSION'] = nil
+ end
+ end
+
+ def test_expand_cache_key_rails_cache_id_should_win_over_rails_app_version
+ begin
+ ENV['RAILS_CACHE_ID'] = 'c99'
+ ENV['RAILS_APP_VERSION'] = 'rails3'
+ assert_equal 'c99/foo', ActiveSupport::Cache.expand_cache_key(:foo)
+ ensure
+ ENV['RAILS_CACHE_ID'] = nil
+ ENV['RAILS_APP_VERSION'] = nil
+ end
+ end
+
+ def test_respond_to_cache_key
+ key = 'foo'
+ def key.cache_key
+ :foo_key
+ end
+ assert_equal 'foo_key', ActiveSupport::Cache.expand_cache_key(key)
+ end
+
end
class CacheStoreSettingTest < ActiveSupport::TestCase
@@ -279,7 +322,7 @@ module CacheStoreBehavior
assert_equal 'bar', @cache.read('foo')
raise ArgumentError.new
end
- rescue ArgumentError => e
+ rescue ArgumentError
end
assert_equal "bar", @cache.read('foo')
Time.stubs(:now).returns(time + 71)
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 54376deee5..009a254c64 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -398,6 +398,18 @@ class ArrayWrapperTests < Test::Unit::TestCase
def method_missing(*a) @target.send(*a) end
end
+ class DoubtfulToAry
+ def to_ary
+ :not_an_array
+ end
+ end
+
+ class NilToAry
+ def to_ary
+ nil
+ end
+ end
+
def test_array
ary = %w(foo bar)
assert_same ary, Array.wrap(ary)
@@ -438,4 +450,12 @@ class ArrayWrapperTests < Test::Unit::TestCase
o = Struct.new(:foo).new(123)
assert_equal [o], Array.wrap(o)
end
+
+ def test_wrap_returns_nil_if_to_ary_returns_nil
+ assert_nil Array.wrap(NilToAry.new)
+ end
+
+ def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array
+ assert_equal DoubtfulToAry.new.to_ary, Array.wrap(DoubtfulToAry.new)
+ end
end
diff --git a/activesupport/test/core_ext/class_test.rb b/activesupport/test/core_ext/class_test.rb
index 08bb13dd35..60ba3b8f88 100644
--- a/activesupport/test/core_ext/class_test.rb
+++ b/activesupport/test/core_ext/class_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/core_ext/class'
+require 'set'
class ClassTest < Test::Unit::TestCase
class Parent; end
@@ -12,16 +13,16 @@ class ClassTest < Test::Unit::TestCase
class C < B; end
def test_descendants
- assert_equal [Foo, Bar, Baz, A, B, C], Parent.descendants
- assert_equal [Bar, Baz], Foo.descendants
+ assert_equal [Foo, Bar, Baz, A, B, C].to_set, Parent.descendants.to_set
+ assert_equal [Bar, Baz].to_set, Foo.descendants.to_set
assert_equal [Baz], Bar.descendants
assert_equal [], Baz.descendants
end
def test_subclasses
- assert_equal [Foo, A], Parent.subclasses
+ assert_equal [Foo, A].to_set, Parent.subclasses.to_set
assert_equal [Bar], Foo.subclasses
assert_equal [Baz], Bar.subclasses
assert_equal [], Baz.subclasses
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 19d7935211..e8506f5222 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -171,22 +171,29 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase
end
def test_advance
- assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1)
- assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4)
- assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3)
- assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5)
- assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7)
- assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5)
+ assert_equal DateTime.civil(2006,2,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 1)
+ assert_equal DateTime.civil(2005,6,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:months => 4)
+ assert_equal DateTime.civil(2005,3,21,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:weeks => 3)
+ assert_equal DateTime.civil(2005,3,5,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:days => 5)
+ assert_equal DateTime.civil(2012,9,28,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 7)
+ assert_equal DateTime.civil(2013,10,3,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :days => 5)
assert_equal DateTime.civil(2013,10,17,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5)
assert_equal DateTime.civil(2001,12,27,15,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:years => -3, :months => -2, :days => -1)
- assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year
- assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5)
- assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7)
- assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9)
- assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9)
- assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9)
+ assert_equal DateTime.civil(2005,2,28,15,15,10), DateTime.civil(2004,2,29,15,15,10).advance(:years => 1) #leap day plus one year
+ assert_equal DateTime.civil(2005,2,28,20,15,10), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5)
+ assert_equal DateTime.civil(2005,2,28,15,22,10), DateTime.civil(2005,2,28,15,15,10).advance(:minutes => 7)
+ assert_equal DateTime.civil(2005,2,28,15,15,19), DateTime.civil(2005,2,28,15,15,10).advance(:seconds => 9)
+ assert_equal DateTime.civil(2005,2,28,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:hours => 5, :minutes => 7, :seconds => 9)
+ assert_equal DateTime.civil(2005,2,28,10,8,1), DateTime.civil(2005,2,28,15,15,10).advance(:hours => -5, :minutes => -7, :seconds => -9)
assert_equal DateTime.civil(2013,10,17,20,22,19), DateTime.civil(2005,2,28,15,15,10).advance(:years => 7, :months => 19, :weeks => 2, :days => 5, :hours => 5, :minutes => 7, :seconds => 9)
+ end
+ def test_advanced_processes_first_the_date_deltas_and_then_the_time_deltas
+ # If the time deltas were processed first, the following datetimes would be advanced to 2010/04/01 instead.
+ assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59, 59).advance(:months => 1, :seconds => 1)
+ assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23, 59).advance(:months => 1, :minutes => 1)
+ assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 23).advance(:months => 1, :hours => 1)
+ assert_equal DateTime.civil(2010, 3, 29), DateTime.civil(2010, 2, 28, 22, 58, 59).advance(:months => 1, :hours => 1, :minutes => 1, :seconds => 1)
end
def test_next_week
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 7b2c10908f..5d9846a216 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -2,6 +2,8 @@ require 'abstract_unit'
require 'active_support/core_ext/hash'
require 'bigdecimal'
require 'active_support/core_ext/string/access'
+require 'active_support/ordered_hash'
+require 'active_support/core_ext/object/conversions'
class HashExtTest < Test::Unit::TestCase
def setup
@@ -449,6 +451,33 @@ class IWriteMyOwnXML
end
end
+class HashExtToParamTests < Test::Unit::TestCase
+ class ToParam < String
+ def to_param
+ "#{self}-1"
+ end
+ end
+
+ def test_string_hash
+ assert_equal '', {}.to_param
+ assert_equal 'hello=world', { :hello => "world" }.to_param
+ assert_equal 'hello=10', { "hello" => 10 }.to_param
+ assert_equal 'hello=world&say_bye=true', ActiveSupport::OrderedHash[:hello, "world", "say_bye", true].to_param
+ end
+
+ def test_number_hash
+ assert_equal '10=20&30=40&50=60', ActiveSupport::OrderedHash[10, 20, 30, 40, 50, 60].to_param
+ end
+
+ def test_to_param_hash
+ assert_equal 'custom=param-1&custom2=param2-1', ActiveSupport::OrderedHash[ToParam.new('custom'), ToParam.new('param'), ToParam.new('custom2'), ToParam.new('param2')].to_param
+ end
+
+ def test_to_param_hash_escapes_its_keys_and_values
+ assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param
+ end
+end
+
class HashToXmlTest < Test::Unit::TestCase
def setup
@xml_options = { :root => :person, :skip_instruct => true, :indent => 0 }
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 39ee0ac748..5d9cdf22c2 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -147,7 +147,7 @@ class ModuleTest < Test::Unit::TestCase
end
assert_nothing_raised do
- child = Class.new(parent) do
+ Class.new(parent) do
class << self
delegate :parent_method, :to => :superclass
end
diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb
index 4d655913cc..e28b4cd493 100644
--- a/activesupport/test/core_ext/object/to_query_test.rb
+++ b/activesupport/test/core_ext/object/to_query_test.rb
@@ -1,4 +1,5 @@
require 'abstract_unit'
+require 'active_support/ordered_hash'
require 'active_support/core_ext/object/to_query'
class ToQueryTest < Test::Unit::TestCase
@@ -18,12 +19,12 @@ class ToQueryTest < Test::Unit::TestCase
def test_nested_conversion
assert_query_equal 'person[login]=seckar&person[name]=Nicholas',
- :person => {:name => 'Nicholas', :login => 'seckar'}
+ :person => ActiveSupport::OrderedHash[:login, 'seckar', :name, 'Nicholas']
end
def test_multiple_nested
assert_query_equal 'account[person][id]=20&person[id]=10',
- :person => {:id => 10}, :account => {:person => {:id => 20}}
+ ActiveSupport::OrderedHash[:account, {:person => {:id => 20}}, :person, {:id => 10}]
end
def test_array_values
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index c7088638c7..f98d823f00 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -117,6 +117,7 @@ class DependenciesTest < Test::Unit::TestCase
assert_equal true, $checked_verbose, 'After first load warnings should be left alone.'
assert ActiveSupport::Dependencies.loaded.include?(expanded)
+ ActiveSupport::Dependencies.warnings_on_first_load = old_warnings
end
end
@@ -212,6 +213,50 @@ class DependenciesTest < Test::Unit::TestCase
end
end
+ def test_doesnt_break_normal_require
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_autoloading_fixtures do
+ # The _ = assignments are to prevent warnings
+ _ = RequiresConstant
+ assert defined?(RequiresConstant)
+ assert defined?(LoadedConstant)
+ ActiveSupport::Dependencies.clear
+ _ = RequiresConstant
+ assert defined?(RequiresConstant)
+ assert defined?(LoadedConstant)
+ end
+ ensure
+ remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
+ def test_doesnt_break_normal_require_nested
+ path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
+ original_path = $:.dup
+ original_features = $".dup
+ $:.push(path)
+
+ with_autoloading_fixtures do
+ # The _ = assignments are to prevent warnings
+ _ = LoadsConstant
+ assert defined?(LoadsConstant)
+ assert defined?(LoadedConstant)
+ ActiveSupport::Dependencies.clear
+ _ = LoadsConstant
+ assert defined?(LoadsConstant)
+ assert defined?(LoadedConstant)
+ end
+ ensure
+ remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant)
+ $".replace(original_features)
+ $:.replace(original_path)
+ end
+
def failing_test_access_thru_and_upwards_fails
with_autoloading_fixtures do
assert ! defined?(ModuleFolder)
@@ -418,7 +463,6 @@ class DependenciesTest < Test::Unit::TestCase
def test_removal_from_tree_should_be_detected
with_loading 'dependencies' do
- root = ActiveSupport::Dependencies.autoload_paths.first
c = ServiceOne
ActiveSupport::Dependencies.clear
assert ! defined?(ServiceOne)
@@ -433,7 +477,6 @@ class DependenciesTest < Test::Unit::TestCase
def test_references_should_work
with_loading 'dependencies' do
- root = ActiveSupport::Dependencies.autoload_paths.first
c = ActiveSupport::Dependencies.ref("ServiceOne")
service_one_first = ServiceOne
assert_equal service_one_first, c.get
@@ -798,4 +841,11 @@ class DependenciesTest < Test::Unit::TestCase
ensure
ActiveSupport::Dependencies.hook!
end
+
+private
+ def remove_constants(*constants)
+ constants.each do |constant|
+ Object.send(:remove_const, constant) if Object.const_defined?(constant)
+ end
+ end
end
diff --git a/activesupport/test/deprecation/proxy_wrappers_test.rb b/activesupport/test/deprecation/proxy_wrappers_test.rb
new file mode 100644
index 0000000000..aa887f274d
--- /dev/null
+++ b/activesupport/test/deprecation/proxy_wrappers_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+require 'active_support/deprecation'
+
+class ProxyWrappersTest < Test::Unit::TestCase
+ Waffles = false
+ NewWaffles = :hamburgers
+
+ def test_deprecated_object_proxy_doesnt_wrap_falsy_objects
+ proxy = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(nil, "message")
+ assert !proxy
+ end
+
+ def test_deprecated_instance_variable_proxy_doesnt_wrap_falsy_objects
+ proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(nil, :waffles)
+ assert !proxy
+ end
+
+ def test_deprecated_constant_proxy_doesnt_wrap_falsy_objects
+ proxy = ActiveSupport::Deprecation::DeprecatedConstantProxy.new(Waffles, NewWaffles)
+ assert !proxy
+ end
+end
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index a679efb41e..1527d02d16 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -148,7 +148,7 @@ class TestJSONEncoding < Test::Unit::TestCase
:latitude => 123.234
}
}
- result = ActiveSupport::JSON.encode(hash)
+ ActiveSupport::JSON.encode(hash)
end
end
diff --git a/activesupport/test/load_paths_test.rb b/activesupport/test/load_paths_test.rb
index 9c83d6f061..36e3726a02 100644
--- a/activesupport/test/load_paths_test.rb
+++ b/activesupport/test/load_paths_test.rb
@@ -8,8 +8,8 @@ class LoadPathsTest < Test::Unit::TestCase
paths[expanded_path] += 1
paths
}
+ load_paths_count[File.expand_path('../../lib', __FILE__)] -= 1
- # CI has a bunch of duplicate load paths
- # assert_equal [], load_paths_count.select { |k, v| v > 1 }, $LOAD_PATH.inspect
+ assert load_paths_count.select { |k, v| v > 1 }.empty?, $LOAD_PATH.inspect
end
end
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index 195e3eaa42..b11fa8d346 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -134,7 +134,6 @@ class MemoizableTest < ActiveSupport::TestCase
end
def test_reloadable
- counter = @calculator.counter
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
assert_equal 2, @calculator.counter
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 78232d8eb5..6ebbfdf334 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -234,7 +234,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase
def test_include_raises_when_nil_is_passed
@chars.include?(nil)
flunk "Expected chars.include?(nil) to raise TypeError or NoMethodError"
- rescue Exception => e
+ rescue Exception
end
def test_index_should_return_character_offset
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 597f949059..8839b75601 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -4,6 +4,9 @@ module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'
ASCII_STRING = 'ohayo'
BYTE_STRING = "\270\236\010\210\245"
+ if BYTE_STRING.respond_to?(:force_encoding)
+ BYTE_STRING.force_encoding("ASCII-8BIT")
+ end
def chars(str)
ActiveSupport::Multibyte::Chars.new(str)
@@ -16,4 +19,4 @@ module MultibyteTestHelpers
def assert_equal_codepoints(expected, actual, message=nil)
assert_equal(inspect_codepoints(expected), inspect_codepoints(actual), message)
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index e11de5f67a..9faa11efbc 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -11,14 +11,11 @@ module Notifications
@named_subscription = @notifier.subscribe("named.subscription") { |*args| @named_events << event(*args) }
end
- private
- def event(*args)
- ActiveSupport::Notifications::Event.new(*args)
- end
+ private
- def drain
- @notifier.wait
- end
+ def event(*args)
+ ActiveSupport::Notifications::Event.new(*args)
+ end
end
class UnsubscribeTest < TestCase
@@ -132,13 +129,10 @@ module Notifications
def test_instrument_returns_block_result
assert_equal 2, instrument(:awesome) { 1 + 1 }
- drain
end
def test_instrument_yields_the_paylod_for_further_modification
assert_equal 2, instrument(:awesome) { |p| p[:result] = 1 + 1 }
- drain
-
assert_equal 1, @events.size
assert_equal :awesome, @events.first.name
assert_equal Hash[:result => 2], @events.first.payload
@@ -154,15 +148,11 @@ module Notifications
1 + 1
end
- drain
-
assert_equal 1, @events.size
assert_equal :wot, @events.first.name
assert_equal Hash[:payload => "child"], @events.first.payload
end
- drain
-
assert_equal 2, @events.size
assert_equal :awesome, @events.last.name
assert_equal Hash[:payload => "notifications"], @events.last.payload
@@ -177,7 +167,6 @@ module Notifications
assert_equal "FAIL", e.message
end
- drain
assert_equal 1, @events.size
assert_equal Hash[:payload => "notifications",
:exception => ["RuntimeError", "FAIL"]], @events.last.payload
@@ -185,8 +174,6 @@ module Notifications
def test_event_is_pushed_even_without_block
instrument(:awesome, :payload => "notifications")
- drain
-
assert_equal 1, @events.size
assert_equal :awesome, @events.last.name
assert_equal Hash[:payload => "notifications"], @events.last.payload
@@ -198,9 +185,9 @@ module Notifications
time = Time.now
event = event(:foo, time, time + 0.01, random_id, {})
- assert_equal :foo, event.name
- assert_equal time, event.time
- assert_equal 10.0, event.duration
+ assert_equal :foo, event.name
+ assert_equal time, event.time
+ assert_in_delta 10.0, event.duration, 0.00001
end
def test_events_consumes_information_given_as_payload
diff --git a/activesupport/test/option_merger_test.rb b/activesupport/test/option_merger_test.rb
index b898292c9c..33e3e69666 100644
--- a/activesupport/test/option_merger_test.rb
+++ b/activesupport/test/option_merger_test.rb
@@ -1,5 +1,5 @@
require 'abstract_unit'
-require 'active_support/core_ext/object/misc'
+require 'active_support/core_ext/object/with_options'
class OptionMergerTest < Test::Unit::TestCase
def setup
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index d340bed444..f47e896487 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -214,7 +214,7 @@ class OrderedHashTest < Test::Unit::TestCase
def test_alternate_initialization_raises_exception_on_odd_length_args
begin
- alternate = ActiveSupport::OrderedHash[1,2,3,4,5]
+ ActiveSupport::OrderedHash[1,2,3,4,5]
flunk "Hash::[] should have raised an exception on initialization " +
"with an odd number of parameters"
rescue
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index ff77e16edd..1c74ce8b2a 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -9,11 +9,16 @@ end
class MadRonon < StandardError
end
+class CoolError < StandardError
+end
+
class Stargate
attr_accessor :result
include ActiveSupport::Rescuable
+ rescue_from WraithAttack, :with => :sos_first
+
rescue_from WraithAttack, :with => :sos
rescue_from NuclearExplosion do
@@ -45,11 +50,30 @@ class Stargate
def sos
@result = 'killed'
end
+
+ def sos_first
+ @result = 'sos_first'
+ end
+
+end
+
+class CoolStargate < Stargate
+ attr_accessor :result
+
+ include ActiveSupport::Rescuable
+
+ rescue_from CoolError, :with => :sos_cool_error
+
+ def sos_cool_error
+ @result = 'sos_cool_error'
+ end
end
+
class RescueableTest < Test::Unit::TestCase
def setup
@stargate = Stargate.new
+ @cool_stargate = CoolStargate.new
end
def test_rescue_from_with_method
@@ -66,4 +90,17 @@ class RescueableTest < Test::Unit::TestCase
@stargate.dispatch :ronanize
assert_equal 'dex', @stargate.result
end
+
+ def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"]
+ result = @stargate.send(:rescue_handlers).collect {|e| e.first}
+ assert_equal expected, result
+ end
+
+ def test_children_should_inherit_rescue_defintions_from_parents_and_child_rescue_should_be_appended
+ expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"]
+ result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first}
+ assert_equal expected, result
+ end
+
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index 3092fe01ae..633d3b212b 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -49,8 +49,8 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
def test_expression_is_evaluated_in_the_appropriate_scope
- local_scope = 'foo'
silence_warnings do
+ local_scope = 'foo'
assert_difference('local_scope; @object.num') { @object.increment }
end
end
diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/test_xml_mini.rb
new file mode 100644
index 0000000000..585eb15c6e
--- /dev/null
+++ b/activesupport/test/test_xml_mini.rb
@@ -0,0 +1,49 @@
+require 'abstract_unit'
+require 'active_support/xml_mini'
+
+class XmlMiniTest < Test::Unit::TestCase
+ def test_rename_key_dasherizes_by_default
+ assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
+ end
+
+ def test_rename_key_does_nothing_with_dasherize_true
+ assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true)
+ end
+
+ def test_rename_key_does_nothing_with_dasherize_false
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
+ end
+
+ def test_rename_key_camelizes_with_camelize_true
+ assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
+ end
+
+ def test_rename_key_camelizes_with_camelize_true
+ assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
+ end
+
+ def test_rename_key_does_not_dasherize_leading_underscores
+ assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id")
+ end
+
+ def test_rename_key_with_leading_underscore_dasherizes_interior_underscores
+ assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key")
+ end
+
+ def test_rename_key_does_not_dasherize_trailing_underscores
+ assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_")
+ end
+
+ def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores
+ assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_")
+ end
+
+ def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id")
+ end
+
+ def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
+ end
+
+end
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index ee6c357519..f9933cb922 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -19,7 +19,7 @@ puts "[CruiseControl] Rails build"
build_results = {}
# Install required version of bundler.
-bundler_install_cmd = "gem install bundler --no-ri --no-rdoc"
+bundler_install_cmd = "sudo gem install bundler --pre --no-ri --no-rdoc"
puts "Running command: #{bundler_install_cmd}"
build_results[:install_bundler] = system bundler_install_cmd
@@ -27,7 +27,7 @@ cd root_dir do
puts
puts "[CruiseControl] Bundling RubyGems"
puts
- build_results[:bundle] = system 'rm -rf ~/.bundle; env CI=1 bundle install'
+ build_results[:bundle] = system 'rm -rf ~/.bundle; env CI=1 bundle update'
end
cd "#{root_dir}/activesupport" do
diff --git a/doc/template/horo.rb b/doc/template/horo.rb
deleted file mode 100644
index b38fa28cde..0000000000
--- a/doc/template/horo.rb
+++ /dev/null
@@ -1,618 +0,0 @@
-# Horo RDoc template
-# Author: Hongli Lai - http://izumi.plan99.net/blog/
-#
-# Based on the Jamis template:
-# http://weblog.jamisbuck.org/2005/4/8/rdoc-template
-
-if defined?(RDoc::Diagram)
- RDoc::Diagram.class_eval do
- remove_const(:FONT)
- const_set(:FONT, "\"Bitstream Vera Sans\"")
- end
-end
-
-require 'rdoc/generator/html'
-
-module RDoc
-module Generator
-class HTML
-class HORO
-
-FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif"
-
-STYLE = <<CSS
-a {
- color: #00F;
- text-decoration: none;
-}
-
-a:hover {
- color: #77F;
- text-decoration: underline;
-}
-
-body, td, p {
- font-family: <%= values['fonts'] %>;
- background: #FFF;
- color: #000;
- margin: 0px;
- font-size: small;
-}
-
-p {
- margin-top: 0.5em;
- margin-bottom: 0.5em;
-}
-
-#content {
- margin: 2em;
- margin-left: 3.5em;
- margin-right: 3.5em;
-}
-
-#description p {
- margin-bottom: 0.5em;
-}
-
-.sectiontitle {
- margin-top: 1em;
- margin-bottom: 1em;
- padding: 0.5em;
- padding-left: 2em;
- background: #005;
- color: #FFF;
- font-weight: bold;
-}
-
-.attr-rw {
- padding-left: 1em;
- padding-right: 1em;
- text-align: center;
- color: #055;
-}
-
-.attr-name {
- font-weight: bold;
-}
-
-.attr-desc {
-}
-
-.attr-value {
- font-family: monospace;
-}
-
-.file-title-prefix {
- font-size: large;
-}
-
-.file-title {
- font-size: large;
- font-weight: bold;
- background: #005;
- color: #FFF;
-}
-
-.banner {
- background: #005;
- color: #FFF;
- border: 1px solid black;
- padding: 1em;
-}
-
-.banner td {
- background: transparent;
- color: #FFF;
-}
-
-h1 a, h2 a, .sectiontitle a, .banner a {
- color: #FF0;
-}
-
-h1 a:hover, h2 a:hover, .sectiontitle a:hover, .banner a:hover {
- color: #FF7;
-}
-
-.dyn-source {
- display: none;
- background: #fffde8;
- color: #000;
- border: #ffe0bb dotted 1px;
- margin: 0.5em 2em 0.5em 2em;
- padding: 0.5em;
-}
-
-.dyn-source .cmt {
- color: #00F;
- font-style: italic;
-}
-
-.dyn-source .kw {
- color: #070;
- font-weight: bold;
-}
-
-.method {
- margin-left: 1em;
- margin-right: 1em;
- margin-bottom: 1em;
-}
-
-.description pre {
- padding: 0.5em;
- border: #ffe0bb dotted 1px;
- background: #fffde8;
-}
-
-.method .title {
- font-family: monospace;
- font-size: large;
- border-bottom: 1px dashed black;
- margin-bottom: 0.3em;
- padding-bottom: 0.1em;
-}
-
-.method .description, .method .sourcecode {
- margin-left: 1em;
-}
-
-.description p, .sourcecode p {
- margin-bottom: 0.5em;
-}
-
-.method .sourcecode p.source-link {
- text-indent: 0em;
- margin-top: 0.5em;
-}
-
-.method .aka {
- margin-top: 0.3em;
- margin-left: 1em;
- font-style: italic;
- text-indent: 2em;
-}
-
-h1 {
- padding: 1em;
- margin-left: -1.5em;
- font-size: x-large;
- font-weight: bold;
- color: #FFF;
- background: #007;
-}
-
-h2 {
- padding: 0.5em 1em 0.5em 1em;
- margin-left: -1.5em;
- font-size: large;
- font-weight: bold;
- color: #FFF;
- background: #009;
-}
-
-h3, h4, h5, h6 {
- color: #220088;
- border-bottom: #5522bb solid 1px;
-}
-
-.sourcecode > pre {
- padding: 0.5em;
- border: 1px dotted black;
- background: #FFE;
-}
-
-dt {
- font-weight: bold
-}
-
-dd {
- margin-bottom: 0.7em;
-}
-CSS
-
-XHTML_PREAMBLE = %{<?xml version="1.0" encoding="<%= values['charset'] %>"?>
-<!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-}
-
-XHTML_FRAMESET_PREAMBLE = %{
-<!DOCTYPE html
- PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
-}
-
-HEADER = XHTML_PREAMBLE + <<ENDHEADER
-<html>
- <head>
- <title><%= values['title'] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
- <link rel="stylesheet" href="<%= values['style_url'] %>" type="text/css" media="screen" />
-
- <script language="JavaScript" type="text/javascript">
- // <![CDATA[
-
- function toggleSource( id )
- {
- var elem
- var link
-
- if( document.getElementById )
- {
- elem = document.getElementById( id )
- link = document.getElementById( "l_" + id )
- }
- else if ( document.all )
- {
- elem = eval( "document.all." + id )
- link = eval( "document.all.l_" + id )
- }
- else
- return false;
-
- if( elem.style.display == "block" )
- {
- elem.style.display = "none"
- link.innerHTML = "show source"
- }
- else
- {
- elem.style.display = "block"
- link.innerHTML = "hide source"
- }
- }
-
- function openCode( url )
- {
- window.open( url, "SOURCE_CODE", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=480,width=750" ).focus();
- }
- // ]]>
- </script>
- </head>
-
- <body>
-ENDHEADER
-
-FILE_PAGE = <<HTML
-<table border='0' cellpadding='0' cellspacing='0' width="100%" class='banner'>
- <tr><td>
- <table width="100%" border='0' cellpadding='0' cellspacing='0'><tr>
- <td class="file-title" colspan="2"><span class="file-title-prefix">File</span><br /><%= values['short_name'] %></td>
- <td align="right">
- <table border='0' cellspacing="0" cellpadding="2">
- <tr>
- <td>Path:</td>
- <td><%= values['full_path'] %>
-<% if values['cvsurl'] %>
- &nbsp;(<a href="<%= values['cvsurl'] %>">CVS</a>)
-<% end %>
- </td>
- </tr>
- <tr>
- <td>Modified:</td>
- <td><%= values['dtm_modified'] %></td>
- </tr>
- </table>
- </td></tr>
- </table>
- </td></tr>
-</table><br />
-HTML
-
-###################################################################
-
-CLASS_PAGE = <<HTML
-<table width="100%" border='0' cellpadding='0' cellspacing='0' class='banner'><tr>
- <td class="file-title"><span class="file-title-prefix"><%= values['classmod'] %></span><br /><%= values['full_name'] %></td>
- <td align="right">
- <table cellspacing="0" cellpadding="2">
- <tr valign="top">
- <td>In:</td>
- <td>
-<% values['infiles'].each do |infile| %>
-<%= href infile['full_path_url'], infile['full_path'] %>:
-<% if infile['cvsurl'] %>
-&nbsp;(<a href="<%= infile['cvsurl'] %>">CVS</a>)
-<% end %>
-<% end %>
- </td>
- </tr>
-<% if values['parent'] %>
- <tr>
- <td>Parent:</td>
- <td>
-<% if values['par_url'] %>
- <a href="<%= values['par_url'] %>">
-<% end %>
-<%= values['parent'] %>
-<% if values['par_url'] %>
- </a>
-<% end %>
- </td>
- </tr>
-<% end %>
- </table>
- </td>
- </tr>
- </table>
-HTML
-
-###################################################################
-
-METHOD_LIST = <<HTML
- <div id="content">
-<% if values['diagram'] %>
- <table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center">
- <%= values['diagram'] %>
- </td></tr></table>
-<% end %>
-
-<% if values['description'] %>
- <div class="description"><%= values['description'] %></div>
-<% end %>
-
-<% if values['requires'] %>
- <div class="sectiontitle">Required Files</div>
- <ul>
-<% values['requires'].each do |require| %>
- <li><%= href require['aref'], require['name'] %>:</li>
-<% end %>
- </ul>
-<% end %>
-
-<% if values['toc'] %>
- <div class="sectiontitle">Contents</div>
- <ul>
-<% values['toc'].each do |toc| %>
- <li><a href="#<%= toc['href'] %>"><%= toc['secname'] %></a></li>
-<% end %>
- </ul>
-<% end %>
-
-<% if values['methods'] %>
- <div class="sectiontitle">Methods</div>
- <ul>
-<% values['methods'].each do |method| %>
- <li><%= href method['aref'], method['name'] %></li>
-<% end %>
- </ul>
-<% end %>
-
-<% if values['includes'] %>
-<div class="sectiontitle">Included Modules</div>
-<ul>
-<% values['includes'].each do |include| %>
- <li><%= href include['aref'], include['name'] %>:</li>
-<% end %>
-</ul>
-<% end %>
-
-<% values['sections'].each do |section| %>
-<% if section['sectitle'] %>
-<div class="sectiontitle"><a name="<%= section['secsequence'] %>"><%= section['sectitle'] %></a></div>
-<% if section['seccomment'] %>
-<div class="description">
-<%= section['seccomment'] %>
-</div>
-<% end %>
-<% end %>
-
-<% if section['classlist'] %>
- <div class="sectiontitle">Classes and Modules</div>
- <%= section['classlist'] %>
-<% end %>
-
-<% if section['constants'] %>
- <div class="sectiontitle">Constants</div>
- <table border='0' cellpadding='5'>
-<% section['constants'].each do |constant| %>
- <tr valign='top'>
- <td class="attr-name"><%= constant['name'] %></td>
- <td>=</td>
- <td class="attr-value"><%= constant['value'] %></td>
- </tr>
-<% if constant['desc'] %>
- <tr valign='top'>
- <td>&nbsp;</td>
- <td colspan="2" class="attr-desc"><%= constant['desc'] %></td>
- </tr>
-<% end %>
-<% end %>
- </table>
-<% end %>
-
-<% if section['attributes'] %>
- <div class="sectiontitle">Attributes</div>
- <table border='0' cellpadding='5'>
-<% section['attributes'].each do |attribute| %>
- <tr valign='top'>
- <td class='attr-rw'>
-<% if attribute['rw'] %>
-[<%= attribute['rw'] %>]
-<% end %>
- </td>
- <td class='attr-name'><%= attribute['name'] %></td>
- <td class='attr-desc'><%= attribute['a_desc'] %></td>
- </tr>
-<% end %>
- </table>
-<% end %>
-
-<% if section['method_list'] %>
-<% section['method_list'].each do |method_list| %>
-<% if method_list['methods'] %>
-<div class="sectiontitle"><%= method_list['type'] %> <%= method_list['category'] %> methods</div>
-<% method_list['methods'].each do |method| %>
-<div class="method">
- <div class="title">
-<% if method['callseq'] %>
- <a name="<%= method['aref'] %>"></a><b><%= method['callseq'] %></b>
-<% end %>
-<% unless method['callseq'] %>
- <a name="<%= method['aref'] %>"></a><b><%= method['name'] %></b><%= method['params'] %>
-<% end %>
-<% if method['codeurl'] %>
-[&nbsp;<a href="<%= method['codeurl'] %>" target="SOURCE_CODE" onclick="javascript:openCode('<%= method['codeurl'] %>'); return false;">source</a>&nbsp;]
-<% end %>
- </div>
-<% if method['m_desc'] %>
- <div class="description">
- <%= method['m_desc'] %>
- </div>
-<% end %>
-<% if method['aka'] %>
-<div class="aka">
- This method is also aliased as
-<% method['aka'].each do |aka| %>
- <a href="<%= aka['aref'] %>"><%= aka['name'] %></a>
-<% end %>
-</div>
-<% end %>
-<% if method['sourcecode'] %>
-<div class="sourcecode">
- <p class="source-link">[ <a href="javascript:toggleSource('<%= method['aref'] %>_source')" id="l_<%= method['aref'] %>_source">show source</a> ]</p>
- <div id="<%= method['aref'] %>_source" class="dyn-source">
-<pre>
-<%= method['sourcecode'] %>
-</pre>
- </div>
-</div>
-<% end %>
-</div>
-<% end %>
-<% end %>
-<% end %>
-<% end %>
-<% end %>
-</div>
-HTML
-
-FOOTER = <<ENDFOOTER
- </body>
-</html>
-ENDFOOTER
-
-BODY = HEADER + <<ENDBODY
- <%= template_include %> <!-- banner header -->
-
- <div id="bodyContent">
- #{METHOD_LIST}
- </div>
-
- #{FOOTER}
-ENDBODY
-
-########################## Source code ##########################
-
-SRC_PAGE = XHTML_PREAMBLE + <<HTML
-<html>
-<head><title><%= values['title'] %></title>
-<meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
-<style type="text/css">
-.ruby-comment { color: green; font-style: italic }
-.ruby-constant { color: #4433aa; font-weight: bold; }
-.ruby-identifier { color: #222222; }
-.ruby-ivar { color: #2233dd; }
-.ruby-keyword { color: #3333FF; font-weight: bold }
-.ruby-node { color: #777777; }
-.ruby-operator { color: #111111; }
-.ruby-regexp { color: #662222; }
-.ruby-value { color: #662222; font-style: italic }
- .kw { color: #3333FF; font-weight: bold }
- .cmt { color: green; font-style: italic }
- .str { color: #662222; font-style: italic }
- .re { color: #662222; }
-</style>
-</head>
-<body bgcolor="white">
-<pre><%= values['code'] %></pre>
-</body>
-</html>
-HTML
-
-########################## Index ################################
-
-FR_INDEX_BODY = <<HTML
-<%= template_include %>
-HTML
-
-FILE_INDEX = XHTML_PREAMBLE + <<HTML
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
-<title>Index</title>
-<style type="text/css">
-<!--
- body {
- background-color: #EEE;
- font-family: #{FONTS};
- color: #000;
- margin: 0px;
- }
- .banner {
- background: #005;
- color: #FFF;
- padding: 0.2em;
- font-size: small;
- font-weight: bold;
- text-align: center;
- }
- .entries {
- margin: 0.25em 1em 0 1em;
- font-size: x-small;
- }
- a {
- color: #00F;
- text-decoration: none;
- white-space: nowrap;
- }
- a:hover {
- color: #77F;
- text-decoration: underline;
- }
--->
-</style>
-<base target="docwin" />
-</head>
-<body>
-<div class="banner"><%= values['list_title'] %></div>
-<div class="entries">
-<% values['entries'].each do |entrie| %>
-<a href="<%= entrie['href'] %>"><%= entrie['name'] %></a><br />
-<% end %>
-</div>
-</body></html>
-HTML
-
-CLASS_INDEX = FILE_INDEX
-METHOD_INDEX = FILE_INDEX
-
-INDEX = XHTML_FRAMESET_PREAMBLE + <<HTML
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head>
- <title><%= values['title'] %></title>
- <meta http-equiv="Content-Type" content="text/html; charset=<%= values['charset'] %>" />
-</head>
-
-<frameset cols="20%,*">
- <frameset rows="15%,55%,30%">
- <frame src="fr_file_index.html" title="Files" name="Files" />
- <frame src="fr_class_index.html" name="Classes" />
- <frame src="fr_method_index.html" name="Methods" />
- </frameset>
- <frame src="<%= values['initial_page'] %>" name="docwin" />
- <noframes>
- <body bgcolor="white">
- Click <a href="html/index.html">here</a> for a non-frames
- version of this page.
- </body>
- </noframes>
-</frameset>
-
-</html>
-HTML
-
-end
-end
-end
-end
diff --git a/install.rb b/install.rb
new file mode 100644
index 0000000000..05bba27a14
--- /dev/null
+++ b/install.rb
@@ -0,0 +1,11 @@
+version = ARGV.pop
+
+%w( activesupport activemodel activerecord activeresource actionpack actionmailer railties ).each do |framework|
+ puts "Installing #{framework}..."
+ `cd #{framework} && gem build #{framework}.gemspec && gem install #{framework}-#{version}.gem --no-ri --no-rdoc && rm #{framework}-#{version}.gem`
+end
+
+puts "Installing Rails..."
+`gem build rails.gemspec`
+`gem install rails-#{version}.gem --no-ri --no-rdoc `
+`rm rails-#{version}.gem` \ No newline at end of file
diff --git a/rails.gemspec b/rails.gemspec
index 2d10ce78a0..8c90584470 100644
--- a/rails.gemspec
+++ b/rails.gemspec
@@ -25,5 +25,5 @@ Gem::Specification.new do |s|
s.add_dependency('activeresource', version)
s.add_dependency('actionmailer', version)
s.add_dependency('railties', version)
- s.add_dependency('bundler', '>= 1.0.0.beta.3')
+ s.add_dependency('bundler', '>= 1.0.0.rc.2')
end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 8e2bac7f00..fc1ec340c7 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,4 +1,9 @@
-*Rails 3.0.0 [Release Candidate] (unreleased)*
+*Rails 3.0.0 [release candidate] (July 26th, 2010)*
+
+* Application generation: --skip-testunit and --skip-activerecord become --skip-test-unit
+ and --skip-active-record respectively. [fxn]
+
+* Added console to Rails::Railtie as a hook called just after console starts. [José Valim]
* Rails no longer autoload code in lib for application. You need to explicitly require it. [José Valim]
@@ -13,26 +18,26 @@
*Rails 3.0.0 [beta 4] (June 8th, 2010)*
-* Version bump
-* Removed Rails Metal [YK & JV].
+* Removed Rails Metal [Yehuda Katz, José Valim].
+
*Rails 3.0.0 [beta 3] (April 13th, 2010)*
-* Renamed config.cookie_secret to config.secret_token and pass it as env key. [JV]
+* Renamed config.cookie_secret to config.secret_token and pass it as env key. [José Valim]
*Rails 3.0.0 [beta 2] (April 1st, 2010)*
-* Session store configuration has changed [YK & CL]
+* Session store configuration has changed [Yehuda Katz, Carl Lerche]
config.session_store :cookie_store, {:key => "..."}
config.cookie_secret = "fdsfhisdghfidugnfdlg"
* railtie_name and engine_name are deprecated. You can now add any object to
- the configuration object: config.your_plugin = {} [JV]
+ the configuration object: config.your_plugin = {} [José Valim]
* Added config.generators.templates to provide alternative paths for the generators
- to look for templates [JV]
+ to look for templates [José Valim]
*Rails 3.0.0 [beta 1] (February 4, 2010)*
diff --git a/railties/README b/railties/README
deleted file mode 100644
index d8be15e346..0000000000
--- a/railties/README
+++ /dev/null
@@ -1,281 +0,0 @@
-== Welcome to Rails
-
-Rails is a web-application framework that includes everything needed to create
-database-backed web applications according to the Model-View-Control pattern.
-
-This pattern splits the view (also called the presentation) into "dumb"
-templates that are primarily responsible for inserting pre-built data in between
-HTML tags. The model contains the "smart" domain objects (such as Account,
-Product, Person, Post) that holds all the business logic and knows how to
-persist themselves to a database. The controller handles the incoming requests
-(such as Save New Account, Update Product, Show Post) by manipulating the model
-and directing data to the view.
-
-In Rails, the model is handled by what's called an object-relational mapping
-layer entitled Active Record. This layer allows you to present the data from
-database rows as objects and embellish these data objects with business logic
-methods. You can read more about Active Record in
-link:files/vendor/rails/activerecord/README.html.
-
-The controller and view are handled by the Action Pack, which handles both
-layers by its two parts: Action View and Action Controller. These two layers
-are bundled in a single package due to their heavy interdependence. This is
-unlike the relationship between the Active Record and Action Pack that is much
-more separate. Each of these packages can be used independently outside of
-Rails. You can read more about Action Pack in
-link:files/vendor/rails/actionpack/README.html.
-
-
-== Getting Started
-
-1. At the command prompt, create a new Rails application:
- <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
-
-2. Change directory to <tt>myapp</tt> and start the web server:
- <tt>cd myapp; rails server</tt> (run with --help for options)
-
-3. Go to http://localhost:3000/ and you'll see:
- "Welcome aboard: You're riding Ruby on Rails!"
-
-4. Follow the guidelines to start developing your application. You can find
-the following resources handy:
-
-* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
-* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
-
-
-== Web Servers
-
-By default, Rails will try to use Mongrel if it's installed when started with
-<tt>rails server</tt>, otherwise Rails will use WEBrick, the web server that
-ships with Ruby.
-
-Mongrel is a Ruby-based web server with a C component (which requires
-compilation) that is suitable for development. If you have Ruby Gems installed,
-getting up and running with mongrel is as easy as:
- <tt>sudo gem install mongrel</tt>.
-
-You can find more info at: http://mongrel.rubyforge.org
-
-You can alternatively run Rails applications with other Ruby web servers, e.g.,
-{Thin}[http://code.macournoyer.com/thin/], {Ebb}[http://ebb.rubyforge.org/], and
-Apache with {mod_rails}[http://www.modrails.com/]. However, <tt>rails server</tt>
-doesn't search for or start them.
-
-For production use, often a web/proxy server, e.g., {Apache}[http://apache.org],
-{Nginx}[http://nginx.net/], {LiteSpeed}[http://litespeedtech.com/],
-{Lighttpd}[http://www.lighttpd.net/], or {IIS}[http://www.iis.net/], is deployed
-as the front end server with the chosen Ruby web server running in the back end
-and receiving the proxied requests via one of several protocols (HTTP, CGI, FCGI).
-
-
-== Debugging Rails
-
-Sometimes your application goes wrong. Fortunately there are a lot of tools that
-will help you debug it and get it back on the rails.
-
-First area to check is the application log files. Have "tail -f" commands
-running on the server.log and development.log. Rails will automatically display
-debugging and runtime information to these files. Debugging info will also be
-shown in the browser on requests from 127.0.0.1.
-
-You can also log your own messages directly into the log file from your code
-using the Ruby logger class from inside your controllers. Example:
-
- class WeblogController < ActionController::Base
- def destroy
- @weblog = Weblog.find(params[:id])
- @weblog.destroy
- logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
- end
- end
-
-The result will be a message in your log file along the lines of:
-
- Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
-
-More information on how to use the logger is at http://www.ruby-doc.org/core/
-
-Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
-several books available online as well:
-
-* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
-* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
-
-These two books will bring you up to speed on the Ruby language and also on
-programming in general.
-
-
-== Debugger
-
-Debugger support is available through the debugger command when you start your
-Mongrel or WEBrick server with --debugger. This means that you can break out of
-execution at any point in the code, investigate and change the model, and then,
-resume execution! You need to install ruby-debug to run the server in debugging
-mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
-
- class WeblogController < ActionController::Base
- def index
- @posts = Post.find(:all)
- debugger
- end
- end
-
-So the controller will accept the action, run the first line, then present you
-with a IRB prompt in the server window. Here you can do things like:
-
- >> @posts.inspect
- => "[#<Post:0x14a6be8
- @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
- #<Post:0x14a6620
- @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
- >> @posts.first.title = "hello from a debugger"
- => "hello from a debugger"
-
-...and even better, you can examine how your runtime objects actually work:
-
- >> f = @posts.first
- => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
- >> f.
- Display all 152 possibilities? (y or n)
-
-Finally, when you're ready to resume execution, you can enter "cont".
-
-
-== Console
-
-The console is a Ruby shell, which allows you to interact with your
-application's domain model. Here you'll have all parts of the application
-configured, just like it is when the application is running. You can inspect
-domain models, change values, and save to the database. Starting the script
-without arguments will launch it in the development environment.
-
-To start the console, run <tt>rails console</tt> from the application
-directory.
-
-Options:
-
-* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
- made to the database.
-* Passing an environment name as an argument will load the corresponding
- environment. Example: <tt>rails console production</tt>.
-
-To reload your controllers and models after launching the console run
-<tt>reload!</tt>
-
-More information about irb can be found at:
-link:http://www.rubycentral.com/pickaxe/irb.html
-
-
-== dbconsole
-
-You can go to the command line of your database directly through <tt>rails
-dbconsole</tt>. You would be connected to the database with the credentials
-defined in database.yml. Starting the script without arguments will connect you
-to the development database. Passing an argument will connect you to a different
-database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
-PostgreSQL and SQLite 3.
-
-== Description of Contents
-
-The default directory structure of a generated Ruby on Rails application:
-
- |-- app
- | |-- controllers
- | |-- helpers
- | |-- models
- | `-- views
- | `-- layouts
- |-- config
- | |-- environments
- | |-- initializers
- | `-- locales
- |-- db
- |-- doc
- |-- lib
- | `-- tasks
- |-- log
- |-- public
- | |-- images
- | |-- javascripts
- | `-- stylesheets
- |-- script
- | `-- performance
- |-- test
- | |-- fixtures
- | |-- functional
- | |-- integration
- | |-- performance
- | `-- unit
- |-- tmp
- | |-- cache
- | |-- pids
- | |-- sessions
- | `-- sockets
- `-- vendor
- `-- plugins
-
-app
- Holds all the code that's specific to this particular application.
-
-app/controllers
- Holds controllers that should be named like weblogs_controller.rb for
- automated URL mapping. All controllers should descend from
- ApplicationController which itself descends from ActionController::Base.
-
-app/models
- Holds models that should be named like post.rb. Models descend from
- ActiveRecord::Base by default.
-
-app/views
- Holds the template files for the view that should be named like
- weblogs/index.html.erb for the WeblogsController#index action. All views use
- eRuby syntax by default.
-
-app/views/layouts
- Holds the template files for layouts to be used with views. This models the
- common header/footer method of wrapping views. In your views, define a layout
- using the <tt>layout :default</tt> and create a file named default.html.erb.
- Inside default.html.erb, call <% yield %> to render the view using this
- layout.
-
-app/helpers
- Holds view helpers that should be named like weblogs_helper.rb. These are
- generated for you automatically when using generators for controllers.
- Helpers can be used to wrap functionality for your views into methods.
-
-config
- Configuration files for the Rails environment, the routing map, the database,
- and other dependencies.
-
-db
- Contains the database schema in schema.rb. db/migrate contains all the
- sequence of Migrations for your schema.
-
-doc
- This directory is where your application documentation will be stored when
- generated using <tt>rake doc:app</tt>
-
-lib
- Application specific libraries. Basically, any kind of custom code that
- doesn't belong under controllers, models, or helpers. This directory is in
- the load path.
-
-public
- The directory available for the web server. Contains subdirectories for
- images, stylesheets, and javascripts. Also contains the dispatchers and the
- default HTML files. This should be set as the DOCUMENT_ROOT of your web
- server.
-
-script
- Helper scripts for automation and generation.
-
-test
- Unit and functional tests along with fixtures. When using the rails generate
- command, template test files will be generated for you and placed in this
- directory.
-
-vendor
- External libraries that the application depends on. Also includes the plugins
- subdirectory. If the app has frozen rails, those gems also go here, under
- vendor/rails/. This directory is in the load path.
diff --git a/railties/README.rdoc b/railties/README.rdoc
new file mode 100644
index 0000000000..a1718a7d96
--- /dev/null
+++ b/railties/README.rdoc
@@ -0,0 +1,25 @@
+= Railties -- Gluing the Engine to the Rails
+
+Railties is responsible to glue all frameworks together. Overall, it:
+
+* handles all the bootstrapping process for a Rails application;
+
+* manager rails command line interface;
+
+* provides Rails generators core;
+
+
+== Download
+
+The latest version of Railties can be installed with Rubygems:
+
+* gem install railties
+
+Documentation can be found at
+
+* http://api.rubyonrails.org
+
+
+== License
+
+Railties is released under the MIT license.
diff --git a/railties/Rakefile b/railties/Rakefile
index ddc872e18b..8e78d2ff4a 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -1,8 +1,8 @@
-gem 'rdoc', '= 2.2'
+gem 'rdoc', '>= 2.5.9'
require 'rdoc'
require 'rake'
require 'rake/testtask'
-require 'rake/rdoctask'
+require 'rdoc/task'
require 'rake/gempackagetask'
require 'date'
@@ -39,7 +39,7 @@ desc "Updates application README to the latest version Railties README"
task :update_readme do
readme = "lib/rails/generators/rails/app/templates/README"
rm readme
- cp "./README", readme
+ cp "./README.rdoc", readme
end
desc 'Generate guides (for authors), use ONLY=foo to process just "foo.textile"'
@@ -60,13 +60,13 @@ end
# Generate documentation ------------------------------------------------------------------
-Rake::RDocTask.new { |rdoc|
+RDoc::Task.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Railties -- Gluing the Engine to the Rails"
- rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object'
+ rdoc.options << '-f' << 'horo'
+ rdoc.options << '--main' << 'README.rdoc'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
- rdoc.rdoc_files.include('README', 'CHANGELOG')
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/rails/generators/**/templates/*')
}
diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb
index e6f0b694a6..dfbb06cc76 100644
--- a/railties/guides/rails_guides.rb
+++ b/railties/guides/rails_guides.rb
@@ -1,6 +1,13 @@
pwd = File.dirname(__FILE__)
$:.unshift pwd
+# This is a predicate useful for the doc:guides task of applications.
+def bundler?
+ # Note that rake sets the cwd to the one that contains the Rakefile
+ # being executed.
+ File.exists?('Gemfile')
+end
+
# Loading Action Pack requires rack and erubis.
require 'rubygems'
@@ -20,7 +27,19 @@ begin
gem 'RedCloth', '>= 4.1.1'
require 'redcloth'
rescue Gem::LoadError
- $stderr.puts %(Generating Guides requires RedCloth 4.1.1+)
+ $stderr.puts('Generating guides requires RedCloth 4.1.1+.')
+ $stderr.puts(<<ERROR) if bundler?
+Please add
+
+ gem 'RedCloth', '>= 4.1.1'
+
+to the Gemfile, run
+
+ bundle install
+
+and try again.
+ERROR
+
exit 1
end
diff --git a/railties/guides/source/3_0_release_notes.textile b/railties/guides/source/3_0_release_notes.textile
index 7dcaf508c6..14da650e83 100644
--- a/railties/guides/source/3_0_release_notes.textile
+++ b/railties/guides/source/3_0_release_notes.textile
@@ -1,6 +1,6 @@
h2. Ruby on Rails 3.0 Release Notes
-Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before it arrived. It's the Best Version of Rails We've Ever Done!
+Rails 3.0 is ponies and rainbows! It's going to cook you dinner and fold your laundry. You're going to wonder how life was ever possible before it arrived. It's the Best Version of Rails We've Ever Done!
But seriously now, it's really good stuff. There are all the good ideas brought over from when the Merb team joined the party and brought a focus on framework agnosticism, slimmer and faster internals, and a handful of tasty APIs. If you're coming to Rails 3.0 from Merb 1.x, you should recognize lots. If you're coming from Rails 2.x, you're going to love it too.
@@ -12,7 +12,7 @@ Even if you don't give a hoot about any of our internal cleanups, Rails 3.0 is g
* Unobtrusive JavaScript helpers with drivers for Prototype, jQuery, and more coming (end of inline JS)
* Explicit dependency management with Bundler
-On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices.
+On top of all that, we've tried our best to deprecate the old APIs with nice warnings. That means that you can move your existing application to Rails 3 without immediately rewriting all your old code to the latest best practices.
These release notes cover the major upgrades, but don't include every little bug fix and change. Rails 3.0 consists of almost 4,000 commits by more than 250 authors! If you want to see everything, check out the "list of commits":http://github.com/rails/rails/commits/master in the main Rails repository on GitHub.
@@ -66,7 +66,7 @@ To help with the upgrade process, a plugin named "Rails Upgrade":http://github.c
Simply install the plugin, then run +rake rails:upgrade:check+ to check your app for pieces that need to be updated (with links to information on how to update them). It also offers a task to generate a +Gemfile+ based on your current +config.gem+ calls and a task to generate a new routes file from your current one. To get the plugin, simply run the following:
<shell>
-rails plugin install git://github.com/rails/rails_upgrade.git
+ruby script/plugin install git://github.com/rails/rails_upgrade.git
</shell>
You can see an example of how that works at "Rails Upgrade is now an Official Plugin":http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin
@@ -596,3 +596,4 @@ h3. Credits
See the "full list of contributors to Rails":http://contributors.rubyonrails.org/ for the many people who spent many hours making Rails 3. Kudos to all of them.
Rails 3.0 Release Notes were compiled by "Mikel Lindsaar":http://lindsaar.net.
+
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index 038ca903c1..ec2d5b2787 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -162,23 +162,38 @@ If you need a different session storage mechanism, you can change it in the +con
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rake db:sessions:create")
-# ActionController::Base.session_store = :active_record_store
+# YourApp::Application.config.session_store :active_record_store
</ruby>
-Rails sets up a session key (the name of the cookie) and (for the CookieStore) a secret key used when signing the session data. These can also be changed in +config/initializers/session_store.rb+:
+Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in +config/initializers/session_store.rb+:
<ruby>
-# Your secret key for verifying cookie session data integrity.
-# If you change this key, all old sessions will become invalid!
-# Make sure the secret is at least 30 characters and all random,
+# Be sure to restart your server when you modify this file.
+
+YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session'
+</ruby>
+
+You can also pass a +:domain+ key and specify the domain name for the cookie:
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+
+YourApp::Application.config.session_store :cookie_store, :key => '_your_app_session', :domain => ".example.com"
+</ruby>
+
+Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in +config/initializers/secret_token.rb+
+
+<ruby>
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-ActionController::Base.session = {
- :key => '_yourappname_session',
- :secret => '4f50711b8f0f49572...'
-}
+YourApp::Application.config.secret_token = '49d3f3de9ed86c74b94ad6bd0...'
</ruby>
-NOTE: Changing the secret when using the CookieStore will invalidate all existing sessions.
+NOTE: Changing the secret when using the +CookieStore+ will invalidate all existing sessions.
h4. Accessing the Session
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index f5e70aef41..53095a2bd3 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -337,7 +337,7 @@ Just like in Ruby. If you want a shorter syntax be sure to check out the "Hash C
h4. Hash Conditions
-Active Record also allows you to pass in a hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
+Active Record also allows you to pass in hash conditions which can increase the readability of your conditions syntax. With hash conditions, you pass in a hash with keys of the fields you want conditionalised and the values of how you want to conditionalise them:
NOTE: Only equality, range and subset checking are possible with Hash conditions.
@@ -347,7 +347,7 @@ h5. Equality Conditions
Client.where({ :locked => true })
</ruby>
-The field name does not have to be a symbol it can also be a string:
+The field name can also be a string:
<ruby>
Client.where({ 'locked' => true })
@@ -447,33 +447,33 @@ h4. Limit and Offset
To apply +LIMIT+ to the SQL fired by the +Model.find+, you can specify the +LIMIT+ using +limit+ and +offset+ methods on the relation.
-If you want to limit the amount of records to a certain subset of all the records retrieved you usually use +limit+ for this, sometimes coupled with +offset+. Limit is the maximum number of records that will be retrieved from a query, and offset is the number of records it will start reading from from the first record of the set. For example:
+You can use +limit+ to specify the number of records to be retrieved, and use +offset+ to specify the number of records to skip before starting to return the records. For example
<ruby>
Client.limit(5)
</ruby>
-This code will return a maximum of 5 clients and because it specifies no offset it will return the first 5 clients in the table. The SQL it executes will look like this:
+will return a maximum of 5 clients and because it specifies no offset it will return the first 5 in the table. The SQL it executes looks like this:
<sql>
SELECT * FROM clients LIMIT 5
</sql>
-Or chaining both +limit+ and +offset+:
+Adding +offset+ to that
<ruby>
-Client.limit(5).offset(5)
+Client.limit(5).offset(30)
</ruby>
-This code will return a maximum of 5 clients and because it specifies an offset this time, it will return these records starting from the 5th client in the clients table. The SQL looks like:
+will return instead a maximum of 5 clients beginning with the 31st. The SQL looks like:
<sql>
-SELECT * FROM clients LIMIT 5, 5
+SELECT * FROM clients LIMIT 5, 30
</sql>
h4. Group
-To apply +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find.
+To apply a +GROUP BY+ clause to the SQL fired by the finder, you can specify the +group+ method on the find.
For example, if you want to find a collection of the dates orders were created on:
@@ -491,7 +491,7 @@ SELECT * FROM orders GROUP BY date(created_at)
h4. Having
-SQL uses +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can specify the +HAVING+ clause to the SQL fired by the +Model.find+ using +:having+ option on the find.
+SQL uses the +HAVING+ clause to specify conditions on the +GROUP BY+ fields. You can add the +HAVING+ clause to the SQL fired by the +Model.find+ by adding the +:having+ option to the find.
For example:
@@ -517,7 +517,7 @@ Any attempt to alter or destroy the readonly records will not succeed, raising a
Client.first.readonly(true)
</ruby>
-If you assign this record to a variable client, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception:
+For example, calling the following code will raise an +ActiveRecord::ReadOnlyRecord+ exception:
<ruby>
client = Client.first.readonly(true)
@@ -527,7 +527,9 @@ client.save
h4. Locking Records for Update
-Locking is helpful for preventing the race conditions when updating records in the database and ensuring atomic updated. Active Record provides two locking mechanism:
+Locking is helpful for preventing race conditions when updating records in the database and ensuring atomic updates.
+
+Active Record provides two locking mechanisms:
* Optimistic Locking
* Pessimistic Locking
@@ -538,7 +540,7 @@ Optimistic locking allows multiple users to access the same record for edits, an
<strong>Optimistic locking column</strong>
-In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice will let the last one saved raise an +ActiveRecord::StaleObjectError+ exception if the first was also updated. Example:
+In order to use optimistic locking, the table needs to have a column called +lock_version+. Each time the record is updated, Active Record increments the +lock_version+ column. If an update request is made with a lower value in the +lock_version+ field than is currently in the +lock_version+ column in the database, the update request will fail with an +ActiveRecord::StaleObjectError+. Example:
<ruby>
c1 = Client.find(1)
@@ -569,7 +571,7 @@ end
h5. Pessimistic Locking
-Pessimistic locking uses locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions.
+Pessimistic locking uses a locking mechanism provided by the underlying database. Passing +:lock => true+ to +Model.find+ obtains an exclusive lock on the selected rows. +Model.find+ using +:lock+ are usually wrapped inside a transaction for preventing deadlock conditions.
For example:
@@ -601,7 +603,7 @@ end
h3. Joining Tables
-<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There multiple different ways to specify the +:joins+ option:
+<tt>Model.find</tt> provides a +:joins+ option for specifying +JOIN+ clauses on the resulting SQL. There are multiple ways to specify the +:joins+ option:
h4. Using a String SQL Fragment
@@ -698,7 +700,7 @@ time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.joins(:orders).where('orders.created_at' => time_range)
</ruby>
-An alternative and cleaner syntax to this is to nest the hash conditions:
+An alternative and cleaner syntax is to nest the hash conditions:
<ruby>
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
@@ -727,7 +729,7 @@ This code looks fine at the first sight. But the problem lies within the total n
<strong>Solution to N <plus> 1 queries problem</strong>
-Active Record lets you specify all the associations in advanced that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all the specified associations are loaded using minimum possible number of queries.
+Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the +includes+ method of the +Model.find+ call. With +includes+, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
Revisiting the above case, we could rewrite +Client.all+ to use eager load addresses:
@@ -749,7 +751,7 @@ SELECT addresses.* FROM addresses
h4. Eager Loading Multiple Associations
-Active Record lets you eager load any possible number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method.
+Active Record lets you eager load any number of associations with a single +Model.find+ call by using an array, hash, or a nested hash of array/hash with the +includes+ method.
h5. Array of Multiple Associations
@@ -762,10 +764,10 @@ This loads all the posts and the associated category and comments for each post.
h5. Nested Associations Hash
<ruby>
-Category.find(1).includes(:posts => [{:comments => :guest}, :tags])
+Category.includes(:posts => [{:comments => :guest}, :tags]).find(1)
</ruby>
-The above code finds the category with id 1 and eager loads all the posts associated with the found category. Additionally, it will also eager load every posts' tags and comments. Every comment's guest association will get eager loaded as well.
+This will find the category with id 1 and eager load all of the associated posts, the associated posts' tags and comments, and every comment's guest association.
h4. Specifying Conditions on Eager Loaded Associations
@@ -782,7 +784,7 @@ You can specify an exclamation point (<tt>!</tt>) on the end of the dynamic find
If you want to find both by name and locked, you can chain these finders together by simply typing +and+ between the fields for example +Client.find_by_first_name_and_locked("Ryan", true)+.
-There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will firstly perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
+There's another set of dynamic finders that let you find or create/initialize objects if they aren't found. These work in a similar fashion to the other finders and can be used like +find_or_create_by_first_name(params[:first_name])+. Using this will first perform a find and then create if the find returns +nil+. The SQL looks like this for +Client.find_or_create_by_first_name("Ryan")+:
<sql>
SELECT * FROM clients WHERE (clients.first_name = 'Ryan') LIMIT 1
@@ -792,7 +794,7 @@ INSERT INTO clients (first_name, updated_at, created_at, orders_count, locked)
COMMIT
</sql>
-+find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similar to calling +new+ with the arguments you passed in. For example:
++find_or_create+'s sibling, +find_or_initialize+, will find an object and if it does not exist will act similarly to calling +new+ with the arguments you passed in. For example:
<ruby>
client = Client.find_or_initialize_by_first_name('Ryan')
@@ -836,7 +838,7 @@ Client.exists?(1,2,3)
Client.exists?([1,2,3])
</ruby>
-Further more, +exists+ takes a +conditions+ option much like find:
+The +exists+ method may also take a +conditions+ option much like find:
<ruby>
Client.exists?(:conditions => "first_name = 'Ryan'")
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 84c33e34f9..37a65d211e 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -1,22 +1,22 @@
h2. Active Record Validations and Callbacks
-This guide teaches you how to hook into the lifecycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object lifecycle.
+This guide teaches you how to hook into the life cycle of your Active Record objects. You will learn how to validate the state of objects before they go into the database, and how to perform custom operations at certain points in the object life cycle.
After reading this guide and trying out the presented concepts, we hope that you'll be able to:
-* Understand the lifecycle of Active Record objects
+* Understand the life cycle of Active Record objects
* Use the built-in Active Record validation helpers
* Create your own custom validation methods
* Work with the error messages generated by the validation process
-* Create callback methods that respond to events in the object lifecycle
+* Create callback methods that respond to events in the object life cycle
* Create special classes that encapsulate common behavior for your callbacks
-* Create Observers that respond to lifecycle events outside of the original class
+* Create Observers that respond to life cycle events outside of the original class
endprologue.
-h3. The Object Lifecycle
+h3. The Object Life Cycle
-During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object lifecycle</em> so that you can control your application and its data.
+During the normal operation of a Rails application, objects may be created, updated, and destroyed. Active Record provides hooks into this <em>object life cycle</em> so that you can control your application and its data.
Validations allow you to ensure that only valid data is stored in your database. Callbacks and observers allow you to trigger logic before or after an alteration of an object's state.
@@ -57,7 +57,7 @@ We can see how it works by looking at some +rails console+ output:
=> false
</shell>
-Creating and saving a new record will send an SQL +INSERT+ operation to the database. Updating an existing record will send an SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
+Creating and saving a new record will send a SQL +INSERT+ operation to the database. Updating an existing record will send a SQL +UPDATE+ operation instead. Validations are typically run before these commands are sent to the database. If any validations fail, the object will be marked as invalid and Active Record will not perform the +INSERT+ or +UPDATE+ operation. This helps to avoid storing an invalid object in the database. You can choose to have specific validations run when an object is created, saved, or updated.
CAUTION: There are many ways to change the state of an object in the database. Some methods will trigger validations, but some will not. This means that it's possible to save an object in the database in an invalid state if you aren't careful.
@@ -799,7 +799,7 @@ h4. Customizing the Error Messages CSS
The selectors to customize the style of error messages are:
-* +.fieldWithErrors+ - Style for the form fields and labels with errors.
+* +.field_with_errors+ - Style for the form fields and labels with errors.
* +#errorExplanation+ - Style for the +div+ element with the error messages.
* +#errorExplanation h2+ - Style for the header of the +div+ element.
* +#errorExplanation p+ - Style for the paragraph that holds the message that appears right below the header of the +div+ element.
@@ -811,7 +811,7 @@ The name of the class and the id can be changed with the +:class+ and +:id+ opti
h4. Customizing the Error Messages HTML
-By default, form fields with errors are displayed enclosed by a +div+ element with the +fieldWithErrors+ CSS class. However, it's possible to override that.
+By default, form fields with errors are displayed enclosed by a +div+ element with the +field_with_errors+ CSS class. However, it's possible to override that.
The way form fields with errors are treated is defined by +ActionView::Base.field_error_proc+. This is a +Proc+ that receives two parameters:
@@ -838,7 +838,7 @@ This will result in something like the following:
h3. Callbacks Overview
-Callbacks are methods that get called at certain moments of an object's lifecycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
+Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it's possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
h4. Callback Registration
@@ -984,7 +984,7 @@ h3. Halting Execution
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
-The whole callback chain is wrapped in a transaction. If any before callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued. After callbacks can only accomplish that by raising an exception.
+The whole callback chain is wrapped in a transaction. If any <em>before</em> callback method returns exactly +false+ or raises an exception the execution chain gets halted and a ROLLBACK is issued; <em>after</em> callbacks can only accomplish that by raising an exception.
WARNING. Raising an arbitrary exception may break code that expects +save+ and friends not to fail like that. The +ActiveRecord::Rollback+ exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised.
@@ -1020,7 +1020,7 @@ Like in validations, we can also make our callbacks conditional, calling them on
h4. Using +:if+ and +:unless+ with a Symbol
-You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. If this method returns +false+ the callback won't be executed. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
+You can associate the +:if+ and +:unless+ options with a symbol corresponding to the name of a method that will get called right before the callback. When using the +:if+ option, the callback won't be executed if the method returns false; when using the +:unless+ option, the callback won't be executed if the method returns true. This is the most common option. Using this form of registration it's also possible to register several different methods that should be called to check if the callback should be executed.
<ruby>
class Order < ActiveRecord::Base
@@ -1135,7 +1135,7 @@ Observers are conventionally placed inside of your +app/models+ directory and re
config.active_record.observers = :user_observer
</ruby>
-As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer not run in all environments, you can simply register it in a specific environment instead.
+As usual, settings in +config/environments+ take precedence over those in +config/environment.rb+. So, if you prefer that an observer doesn't run in all environments, you can simply register it in a specific environment instead.
h4. Sharing Observers
@@ -1162,6 +1162,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213/tickets/26-active-record-validations-and-callbacks
+* July 20, 2010: Fixed typos and rephrased some paragraphs for clarity. "Jaime Iniesta":http://jaimeiniesta.com
* May 24, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* May 15, 2010: Validation Errors section updated by "Emili Parreño":http://www.eparreno.com
* March 7, 2009: Callbacks revision by Trevor Turk
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index 58824d7aeb..54f766fffd 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -157,21 +157,6 @@ WARNING. Using +duplicable?+ is discouraged because it depends on a hard-coded l
NOTE: Defined in +active_support/core_ext/object/duplicable.rb+.
-h4. +returning+
-
-The method +returning+ yields its argument to a block and returns it. You typically use it with a mutable object that gets modified in the block:
-
-<ruby>
-def html_options_for_form(url_for_options, options, *parameters_for_url)
- returning options.stringify_keys do |html_options|
- html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
- html_options["action"] = url_for(url_for_options, *parameters_for_url)
- end
-end
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/object/returning.rb+.
-
h4. +try+
Sometimes you want to call a method provided the receiver object is not +nil+, which is something you usually check first.
@@ -682,107 +667,6 @@ end
NOTE: Defined in +active_support/core_ext/module/attribute_accessors.rb+.
-h4. Method Delegation
-
-The class method +delegate+ offers an easy way to forward methods.
-
-For example, if +User+ has some details like the age factored out to +Profile+, it could be handy to still be able to access such attributes directly, <tt>user.age</tt>, instead of having to explicit the chain <tt>user.profile.age</tt>.
-
-That can be accomplished by hand:
-
-<ruby>
-class User
- has_one :profile
-
- def age
- profile.age
- end
-end
-</ruby>
-
-But with +delegate+ you can make that shorter and the intention even more obvious:
-
-<ruby>
-class User
- has_one :profile
-
- delegate :age, to => :profile
-end
-</ruby>
-
-The macro accepts more than one method:
-
-<ruby>
-class User
- has_one :profile
-
- delegate :age, :avatar, :twitter_username, to => :profile
-end
-</ruby>
-
-Methods can be delegated to objects returned by methods, as in the examples above, but also to instance variables, class variables, and constants. Just pass their names as symbols or strings, including the at signs in the last cases.
-
-For example, +ActionView::Base+ delegates +erb_trim_mode=+:
-
-<ruby>
-module ActionView
- class Base
- delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
- end
-end
-</ruby>
-
-In fact, you can delegate to any expression passed as a string. It will be evaluated in the context of the receiver. Controllers for example delegate alerts and notices to the current flash:
-
-<ruby>
-delegate :alert, :notice, :to => "request.flash"
-</ruby>
-
-If the target is +nil+ calling any delegated method will raise an exception even if +nil+ responds to such method. You can override this behavior setting the option +:allow_nil+ to true, in which case the forwarded call will simply return +nil+.
-
-If the target is a method, the name of delegated methods can also be prefixed. If the +:prefix+ option is set to (exactly) the +true+ object, the value of the +:to+ option is prefixed:
-
-<ruby>
-class Invoice
- belongs_to :customer
-
- # defines a method called customer_name
- delegate :name, :to => :customer, :prefix => true
-end
-</ruby>
-
-And a custom prefix can be set as well, in that case it does not matter wheter the target is a method or not:
-
-<ruby>
-class Account
- belongs_to :user
-
- # defines a method called admin_email
- delegate :email, :to => :user, :prefix => 'admin'
-end
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/module/delegation.rb+.
-
-h4. Method Removal
-
-h5. +remove_possible_method+
-
-The method +remove_possible_method+ is like the standard +remove_method+, except it silently returns on failure:
-
-<ruby>
-class A; end
-
-A.class_eval do
- remove_method(:nonexistent) # raises NameError
- remove_possible_method(:nonexistent) # no problem, continue
-end
-</ruby>
-
-This may come in handy if you need to define a method that may already exist, since redefining a method issues a warning "method redefined; discarding old redefined_method_name".
-
-NOTE: Defined in +active_support/core_ext/module/remove_method.rb+.
-
h4. Parents
h5. +parent+
@@ -977,11 +861,128 @@ though an anonymous module is unreachable by definition.
NOTE: Defined in +active_support/core_ext/module/anonymous.rb+.
+h4. Method Delegation
+
+The macro +delegate+ offers an easy way to forward methods.
+
+Let's imagine that users in some application have login information in the +User+ model but name and other data in a separate +Profile+ model:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+end
+</ruby>
+
+With that configuration you get a user's name via his profile, +user.profile.name+, but it could be handy to still be able to access such attribute directly:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+
+ def name
+ profile.name
+ end
+end
+</ruby>
+
+That is what +delegate+ does for you:
+
+<ruby>
+class User < ActiveRecord::Base
+ has_one :profile
+
+ delegate :name, :to => :profile
+end
+</ruby>
+
+It is shorter, and the intention more obvious.
+
+The macro accepts several methods:
+
+<ruby>
+delegate :name, :age, :address, :twitter, :to => :profile
+</ruby>
+
+When interpolated into a string, the +:to+ option should become an expression that evaluates to the object the method is delegated to. Typically a string or symbol. Such a expression is evaluated in the context of the receiver:
+
+<ruby>
+# delegates to the Rails constant
+delegate :logger, :to => :Rails
+
+# delegates to the receiver's class
+delegate :table_name, :to => 'self.class'
+</ruby>
+
+WARNING: If the +:prefix+ option is +true+ this is less generic, see below.
+
+By default, if the delegation raises +NoMethodError+ and the target is +nil+ the exception is propagated. You can ask that +nil+ is returned instead with the +:allow_nil+ option:
+
+<ruby>
+delegate :name, :to => :profile, :allow_nil => true
+</ruby>
+
+With +:allow_nil+ the call +user.name+ returns +nil+ if the user has no profile.
+
+The option +:prefix+ adds a prefix to the name of the generated method. This may be handy for example to get a better name:
+
+<ruby>
+delegate :street, :to => :address, :prefix => true
+</ruby>
+
+The previous example generates +address_street+ rather than +street+.
+
+WARNING: Since in this case the name of the generated method is composed of the target object and target method names, the +:to+ option must be a method name.
+
+A custom prefix may also be configured:
+
+<ruby>
+delegate :size, :to => :attachment, :prefix => :avatar
+</ruby>
+
+In the previous example the macro generates +avatar_size+ rather than +size+.
+
+NOTE: Defined in +active_support/core_ext/module/delegation.rb+
+
+h4. Method Names
+
+The builtin methods +instance_methods+ and +methods+ return method names as strings or symbols depending on the Ruby version. Active Support defines +instance_method_names+ and +method_names+ to be equivalent to them, respectively, but always getting strings back.
+
+For example, +ActionView::Helpers::FormBuilder+ knows this array difference is going to work no matter the Ruby version:
+
+<ruby>
+self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/module/method_names.rb+
+
+h4. Redefining Methods
+
+There are cases where you need to define a method with +define_method+, but don't know whether a method with that name already exists. If it does, a warning is issued if they are enabled. No big deal, but not clean either.
+
+The method +redefine_method+ prevents such a potential warning, removing the existing method before if needed. Rails uses it in a few places, for instance when it generates an association's API:
+
+<ruby>
+redefine_method("#{reflection.name}=") do |new_value|
+ association = association_instance_get(reflection.name)
+
+ if association.nil? || association.target != new_value
+ association = association_proxy_class.new(self, reflection)
+ end
+
+ association.replace(new_value)
+ association_instance_set(reflection.name, new_value.nil? ? nil : association)
+end
+</ruby>
+
+NOTE: Defined in +active_support/core_ext/module/remove_method.rb+
+
h3. Extensions to +Class+
h4. Class Attributes
-The method +Class#class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy:
+h5. +class_attribute+
+
+The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy:
<ruby>
class A
@@ -1005,17 +1006,50 @@ A.x # => :a
B.x # => :b
</ruby>
-For example that's the way the +allow_forgery_protection+ flag is implemented for controllers:
+For example +ActionMailer::Base+ defines:
+
+<ruby>
+class_attribute :default_params
+self.default_params = {
+ :mime_version => "1.0",
+ :charset => "UTF-8",
+ :content_type => "text/plain",
+ :parts_order => [ "text/plain", "text/enriched", "text/html" ]
+}.freeze
+</ruby>
+
+They can be also accessed and overridden at the instance level:
<ruby>
-class_attribute :allow_forgery_protection
-self.allow_forgery_protection = true
+A.x = 1
+
+a1 = A.new
+a2 = A.new
+a2.x = 2
+
+a1.x # => 1, comes from A
+a2.x # => 2, overridden in a2
</ruby>
-For convenience +class_attribute+ defines also a predicate, so that declaration also generates +allow_forgery_protection?+. Such predicate returns the double boolean negation of the value.
+The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in
+
+<ruby>
+module AcitveRecord
+ class Base
+ class_attribute :table_name_prefix, :instance_writer => false
+ self.table_name_prefix = ""
+ end
+end
+</ruby>
+
+A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
+
+For convenience +class_attribute+ defines also an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+.
NOTE: Defined in +active_support/core_ext/class/attribute.rb+
+h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+
+
The macros +cattr_reader+, +cattr_writer+, and +cattr_accessor+ are analogous to their +attr_*+ counterparts but for classes. They initialize a class variable to +nil+ unless it already exists, and generate the corresponding class methods to access it:
<ruby>
@@ -1026,17 +1060,18 @@ class MysqlAdapter < AbstractAdapter
end
</ruby>
-Instance methods are created as well for convenience. For example given
+Instance methods are created as well for convenience, they are just proxies to the class attribute. So, instances can change the class attribute, but cannot override it as it happens with +class_attribute+ (see above). For example given
<ruby>
-module ActionController
+module ActionView
class Base
- cattr_accessor :logger
+ cattr_accessor :field_error_proc
+ @@field_error_proc = Proc.new{ ... }
end
end
</ruby>
-we can access +logger+ in actions. The generation of the writer instance method can be prevented setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
+we can access +field_error_proc+ in views. The generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+ (not any false value, but exactly +false+):
<ruby>
module ActiveRecord
@@ -1047,7 +1082,7 @@ module ActiveRecord
end
</ruby>
-A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
+A model may find that option useful as a way to prevent mass-assignment from setting the attribute.
NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+.
@@ -1092,21 +1127,6 @@ Since values are copied when a subclass is defined, if the base class changes th
NOTE: Defined in +active_support/core_ext/class/inheritable_attributes.rb+.
-There's a related macro called +superclass_delegating_accessor+, however, that does not copy the value when the base class is subclassed. Instead, it delegates reading to the superclass as long as the attribute is not set via its own writer. For example, +ActionMailer::Base+ defines +delivery_method+ this way:
-
-<ruby>
-module ActionMailer
- class Base
- superclass_delegating_accessor :delivery_method
- self.delivery_method = :smtp
- end
-end
-</ruby>
-
-If for whatever reason an application loads the definition of a mailer class and after that sets +ActionMailer::Base.delivery_method+, the mailer class will still see the new value. In addition, the mailer class is able to change the +delivery_method+ without affecting the value in the parent using its own inherited class attribute writer.
-
-NOTE: Defined in +active_support/core_ext/class/delegating_attributes.rb+.
-
h4. Subclasses & Descendants
h5. +subclasses+
@@ -1433,13 +1453,15 @@ end
That may be handy to compute method names in a language that follows that convention, for example JavaScript.
+INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>.
+
+camelize+ is aliased to +camelcase+.
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h5. +underscore+
-The method +underscore+ is the inverse of +camelize+, explained above:
+The method +underscore+ goes the other way around, from camel case to paths:
<ruby>
"Product".underscore # => "product"
@@ -1472,6 +1494,8 @@ def load_missing_constant(from_mod, const_name)
end
</ruby>
+INFO: As a rule of thumb you can think of +underscore+ as the inverse of +camelize+, though there are cases where that does not hold. For example, <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>.
+
NOTE: Defined in +active_support/core_ext/string/inflections.rb+.
h5. +titleize+
@@ -1840,9 +1864,7 @@ h3. Extensions to +Enumerable+
h4. +group_by+
-Ruby 1.8.7 and up define +group_by+, and Active Support does it for previous versions.
-
-This iterator takes a block and builds an ordered hash with its return values as keys. Each key is mapped to the array of elements for which the block returned that value:
+Active Support redefines +group_by+ in Ruby 1.8.7 so that it returns an ordered hash as in 1.9:
<ruby>
entries_by_surname_initial = address_book.group_by do |entry|
@@ -1850,7 +1872,7 @@ entries_by_surname_initial = address_book.group_by do |entry|
end
</ruby>
-WARNING. Active Support redefines +group_by+ in Ruby 1.8.7 so that it still returns an ordered hash.
+Distinct block return values are added to the hash as they come, so that's the resulting order.
NOTE: Defined in +active_support/core_ext/enumerable.rb+.
@@ -2184,7 +2206,27 @@ NOTE: Defined in +active_support/core_ext/array/conversions.rb+.
h4. Wrapping
-The class method +Array.wrap+ behaves like the function +Array()+ except that it does not try to call +to_a+ on its argument. That changes the behavior for enumerables:
+The method +Array.wrap+ wraps its argument in an array unless it is already an array (or array-like).
+
+Specifically:
+
+* If the argument is +nil+ an empty list is returned.
+* Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
+* Otherwise, returns an array with the argument as its single element.
+
+<ruby>
+Array.wrap(nil) # => []
+Array.wrap([1, 2, 3]) # => [1, 2, 3]
+Array.wrap(0) # => [0]
+</ruby>
+
+This method is similar in purpose to <tt>Kernel#Array</tt>, but there are some differences:
+
+* If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> moves on to try +to_a+ if the returned value is +nil+, but <tt>Arraw.wrap</tt> returns such a +nil+ right away.
+* If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
+* It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array.
+
+The last point is particularly worth comparing for some enumerables:
<ruby>
Array.wrap(:foo => :bar) # => [{:foo => :bar}]
@@ -2194,6 +2236,16 @@ Array.wrap("foo\nbar") # => ["foo\nbar"]
Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8
</ruby>
+There's also a related idiom that uses the splat operator:
+
+<ruby>
+[*object]
+</ruby>
+
+which returns +[nil]+ for +nil+, and calls to <tt>Array(object)</tt> otherwise
+
+Thus, in this case the behavior is different for +nil+, and the differences with <tt>Kernel#Array</tt> explained above apply to the rest of +object+s.
+
NOTE: Defined in +active_support/core_ext/array/wrap.rb+.
h4. Grouping
@@ -2919,11 +2971,11 @@ Note in the previous example that increments may be negative.
To perform the computation the method first increments years, then months, then weeks, and finally days. This order is important towards the end of months. Say for example we are at the end of February of 2010, and we want to move one month and one day forward.
-The method +advance+ advances first one month, and the one day, the result is:
+The method +advance+ advances first one month, and then one day, the result is:
<ruby>
-Date.new(2010, 2, 28).advance(:months => 1, :day => 1)
-# => Sun, 28 Mar 2010
+Date.new(2010, 2, 28).advance(:months => 1, :days => 1)
+# => Sun, 29 Mar 2010
</ruby>
While if it did it the other way around the result would be different:
@@ -2949,6 +3001,26 @@ Date.new(2010, 1, 31).change(:month => 2)
# => ArgumentError: invalid date
</ruby>
+h5. Durations
+
+Durations can be added and substracted to dates:
+
+<ruby>
+d = Date.current
+# => Mon, 09 Aug 2010
+d + 1.year
+# => Tue, 09 Aug 2011
+d - 3.hours
+# => Sun, 08 Aug 2010 21:00:00 UTC +00:00
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+Date.new(1582, 10, 4) + 1.day
+# => Fri, 15 Oct 1582
+</ruby>
+
h5. Timestamps
INFO: The following methods return a +Time+ object if possible, otherwise a +DateTime+. If set, they honor the user time zone.
@@ -2993,30 +3065,30 @@ h4(#date-conversions). Conversions
h3. Extensions to +DateTime+
-NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+.
-
WARNING: +DateTime+ is not aware of DST rules and so some of these methods have edge cases when a DST change is going on. For example +seconds_since_midnight+ might not return the real amount in such a day.
h4(#calculations-datetime). Calculations
+NOTE: All the following methods are defined in +active_support/core_ext/date_time/calculations.rb+.
+
The class +DateTime+ is a subclass of +Date+ so by loading +active_support/core_ext/date/calculations.rb+ you inherit these methods and their aliases, except that they will always return datetimes:
<ruby>
yesterday
tomorrow
-beginning_of_week
-end_on_week
+beginning_of_week (monday, at_beginning_of_week)
+end_on_week (at_end_of_week)
next_week
months_ago
months_since
-beginning_of_month
-end_of_month
+beginning_of_month (at_beginning_of_month)
+end_of_month (at_end_of_month)
prev_month
next_month
-beginning_of_quarter
-end_of_quarter
-beginning_of_year
-end_of_year
+beginning_of_quarter (at_beginning_of_quarter)
+end_of_quarter (at_end_of_quarter)
+beginning_of_year (at_beginning_of_year)
+end_of_year (at_end_of_year)
years_ago
years_since
prev_year
@@ -3026,10 +3098,10 @@ next_year
The following methods are reimplemented so you do *not* need to load +active_support/core_ext/date/calculations.rb+ for these ones:
<ruby>
-beginning_of_day
+beginning_of_day (midnight, at_midnight, at_beginning_of_day)
end_of_day
ago
-since
+since (in)
</ruby>
On the other hand, +advance+ and +change+ are also defined and support more options, they are documented below.
@@ -3072,6 +3144,37 @@ now.utc? # => false
now.utc.utc? # => true
</ruby>
+h6(#datetime-advance). +advance+
+
+The most generic way to jump to another datetime is +advance+. This method receives a hash with keys +:years+, +:months+, +:weeks+, +:days+, +:hours+, +:minutes+, and +:seconds+, and returns a datetime advanced as much as the present keys indicate.
+
+<ruby>
+d = DateTime.current
+# => Thu, 05 Aug 2010 11:33:31 +0000
+d.advance(:years => 1, :months => 1, :days => 1, :hours => 1, :minutes => 1, :seconds => 1)
+# => Tue, 06 Sep 2011 12:34:32 +0000
+</ruby>
+
+This method first computes the destination date passing +:years+, +:months+, +:weeks+, and +:days+ to +Date#advance+ documented above. After that, it adjusts the time calling +since+ with the number of seconds to advance. This order is relevant, a different ordering would give different datetimes in some edge-cases. The example in +Date#advance+ applies, and we can extend it to show order relevance related to the time bits.
+
+If we first move the date bits (that have also a relative order of processing, as documented before), and then the time bits we get for example the following computation:
+
+<ruby>
+d = DateTime.new(2010, 2, 28, 23, 59, 59)
+# => Sun, 28 Feb 2010 23:59:59 +0000
+d.advance(:months => 1, :seconds => 1)
+# => Mon, 29 Mar 2010 00:00:00 +0000
+</ruby>
+
+but if we computed them the other way around, the result would be different:
+
+<ruby>
+d.advance(:seconds => 1).advance(:months => 1)
+# => Thu, 01 Apr 2010 00:00:00 +0000
+</ruby>
+
+WARNING: Since +DateTime+ is not DST-aware you can end up in a non-existing point in time with no warning or error telling you so.
+
h5(#datetime-changing-components). Changing Components
The method +change+ allows you to get a new datetime which is the same as the receiver except for the given options, which may include +:year+, +:month+, +:day+, +:hour+, +:min+, +:sec+, +:offset+, +:start+:
@@ -3104,15 +3207,144 @@ DateTime.current.change(:month => 2, :day => 30)
# => ArgumentError: invalid date
</ruby>
-h4(#datetime-conversions). Conversions
+h5. Durations
+
+Durations can be added and substracted to datetimes:
+
+<ruby>
+now = DateTime.current
+# => Mon, 09 Aug 2010 23:15:17 +0000
+now + 1.year
+# => Tue, 09 Aug 2011 23:15:17 +0000
+now - 1.week
+# => Mon, 02 Aug 2010 23:15:17 +0000
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+DateTime.new(1582, 10, 4, 23) + 1.hour
+# => Fri, 15 Oct 1582 00:00:00 +0000
+</ruby>
h3. Extensions to +Time+
-...
+h4(#time-calculations). Calculations
+
+NOTE: All the following methods are defined in +active_support/core_ext/time/calculations.rb+.
+
+Active Support adds to +Time+ many of the methods available for +DateTime+:
+
+<ruby>
+past?
+today?
+future?
+yesterday
+tomorrow
+seconds_since_midnight
+change
+advance
+ago
+since (in)
+beginning_of_day (midnight, at_midnight, at_beginning_of_day)
+end_of_day
+beginning_of_week (monday, at_beginning_of_week)
+end_on_week (at_end_of_week)
+next_week
+months_ago
+months_since
+beginning_of_month (at_beginning_of_month)
+end_of_month (at_end_of_month)
+prev_month
+next_month
+beginning_of_quarter (at_beginning_of_quarter)
+end_of_quarter (at_end_of_quarter)
+beginning_of_year (at_beginning_of_year)
+end_of_year (at_end_of_year)
+years_ago
+years_since
+prev_year
+next_year
+</ruby>
+
+They are analogous. Please refer to their documentation above and take into account the following differences:
+
+* +change+ accepts an additional +:usec+ option.
+* +Time+ understands DST, so you get correct DST calculations as in
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+
+# In Barcelona, 2010/03/28 02:00 +0100 becomes 2010/03/28 03:00 +0200 due to DST.
+t = Time.local_time(2010, 3, 28, 1, 59, 59)
+# => Sun Mar 28 01:59:59 +0100 2010
+t.advance(:seconds => 1)
+# => Sun Mar 28 03:00:00 +0200 2010
+</ruby>
+
+* If +since+ or +ago+ jump to a time that can't be expressed with +Time+ a +DateTime+ object is returned instead.
+
+h4. Time Constructors
+
+Active Support defines +Time.current+ to be +Time.zone.now+ if there's a user time zone defined, with fallback to +Time.now+:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.current
+# => Fri, 06 Aug 2010 17:11:58 CEST +02:00
+</ruby>
+
+Analogously to +DateTime+, the predicates +past?+, and +future?+ are relative to +Time.current+.
+
+Use the +local_time+ class method to create time objects honoring the user time zone:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.local_time(2010, 8, 15)
+# => Sun Aug 15 00:00:00 +0200 2010
+</ruby>
+
+The +utc_time+ class method returns a time in UTC:
+
+<ruby>
+Time.zone_default
+# => #<ActiveSupport::TimeZone:0x7f73654d4f38 @utc_offset=nil, @name="Madrid", ...>
+Time.utc_time(2010, 8, 15)
+# => Sun Aug 15 00:00:00 UTC 2010
+</ruby>
+
+Both +local_time+ and +utc_time+ accept up to seven positional arguments: year, month, day, hour, min, sec, usec. Year is mandatory, month and day default to 1, and the rest default to 0.
+
+If the time to be constructed lies beyond the range supported by +Time+ in the runtime platform, usecs are discarded and a +DateTime+ object is returned instead.
+
+h5. Durations
+
+Durations can be added and substracted to time objects:
+
+<ruby>
+now = Time.current
+# => Mon, 09 Aug 2010 23:20:05 UTC +00:00
+now + 1.year
+# => Tue, 09 Aug 2011 23:21:11 UTC +00:00
+now - 1.week
+# => Mon, 02 Aug 2010 23:21:11 UTC +00:00
+</ruby>
+
+They translate to calls to +since+ or +advance+. For example here we get the correct jump in the calendar reform:
+
+<ruby>
+Time.utc_time(1582, 10, 3) + 5.days
+# => Mon Oct 18 00:00:00 UTC 1582
+</ruby>
h3. Extensions to +Process+
-...
+h4. +daemon+
+
+Ruby 1.9 provides +Process.daemon+, and Active Support defines it for previous versions. It accepts the same two arguments, whether it should chdir to the root directory (default, true), and whether it should inherit the standard file descriptors from the parent (default, false).
h3. Extensions to +File+
@@ -3188,4 +3420,5 @@ h3. Changelog
"Lighthouse ticket":https://rails.lighthouseapp.com/projects/16213/tickets/67
+* August 10, 2010: Starts to take shape, added to the index.
* April 18, 2009: Initial version by "Xavier Noria":credits.html#fxn
diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile
new file mode 100644
index 0000000000..9f201de49b
--- /dev/null
+++ b/railties/guides/source/api_documentation_guidelines.textile
@@ -0,0 +1,187 @@
+h2. API Documentation Guidelines
+
+This guide documents the Ruby on Rails API documentation guidelines.
+
+endprologue.
+
+h3. RDoc
+
+The Rails API documentation is generated with RDoc 2.5. Please consult the "RDoc documentation":http://rdoc.rubyforge.org/RDoc.htmlFor for help with its markup.
+
+h3. Wording
+
+Write simple, declarative sentences. Brevity is a plus: get to the point.
+
+Write in present tense: "Returns a hash that...", rather than "Returned a hash that..." or "Will return a hash that...".
+
+Start comments in upper case, follow regular punctuation rules:
+
+<ruby>
+# Declares an attribute reader backed by an internally-named instance variable.
+def attr_internal_reader(*attrs)
+ ...
+end
+</ruby>
+
+Communicate to the reader the current way of doing things, both explicitly and implicitly. Use the recommended idioms in edge, reorder sections to emphasize favored approaches if needed, etc. The documentation should be a model for best practices and canonical, modern Rails usage.
+
+Documentation has to be concise but comprehensive. Explore and document edge cases. What happens if a module is anonymous? What if a collection is empty? What if an argument is nil?
+
+The proper names of Rails components have a space in between the words, like "Active Support". +ActiveRecord+ is a Ruby module, whereas Active Record is an ORM. Historically there has been lack of consistency regarding this, but we checked with David when docrails started. All Rails documentation consistently refer to Rails components by their proper name, and if in your next blog post or presentation you remember this tidbit and take it into account that'd be fenomenal :).
+
+Spell names correctly: HTML, MySQL, JavaScript, ERb. Use the article "an" for "SQL", as in "an SQL statement". Also "an SQLite database".
+
+h3. Example Code
+
+Choose meaningful examples that depict and cover the basics as well as interesting points or gotchas.
+
+Use two spaces to indent chunks of code.—that is two spaces with respect to the left margin; the examples
+themselves should use "Rails code conventions":http://rails.lighthouseapp.com/projects/8994/source-style.
+
+Short docs do not need an explicit "Examples" label to introduce snippets, they just follow paragraphs:
+
+<ruby>
+# Converts a collection of elements into a formatted string by calling
+# <tt>to_s</tt> on all elements and joining them.
+#
+# Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
+</ruby>
+
+On the other hand big chunks of structured documentation may have a separate "Examples" section:
+
+<ruby>
+# ==== Examples
+#
+# Person.exists?(5)
+# Person.exists?('5')
+# Person.exists?(:name => "David")
+# Person.exists?(['name LIKE ?', "%#{query}%"])
+</ruby>
+
+The result of expressions follow them and are introduced by "# => ", vertically aligned:
+
+<ruby>
+# For checking if a fixnum is even or odd.
+#
+# 1.even? # => false
+# 1.odd? # => true
+# 2.even? # => true
+# 2.odd? # => false
+</ruby>
+
+If a line is too long, the comment may be placed on the next line:
+
+<ruby>
+ # label(:post, :title)
+ # # => <label for="post_title">Title</label>
+ #
+ # label(:post, :title, "A short title")
+ # # => <label for="post_title">A short title</label>
+ #
+ # label(:post, :title, "A short title", :class => "title_label")
+ # # => <label for="post_title" class="title_label">A short title</label>
+</ruby>
+
+Avoid using any printing methods like +puts+ or +p+ for that purpose.
+
+On the other hand, regular comments do not use an arrow:
+
+<ruby>
+# polymorphic_url(record) # same as comment_url(record)
+</ruby>
+
+h3. Filenames
+
+As a rule of thumb use filenames relative to the application root:
+
+<plain>
+config/routes.rb # YES
+routes.rb # NO
+RAILS_ROOT/config/routes.rb # NO
+</plain>
+
+
+h3. Fonts
+
+h4. Fixed-width Font
+
+Use fixed-width fonts for:
+* constants, in particular class and module names
+* method names
+* literals like +nil+, +false+, +true+, +self+
+* symbols
+* method parameters
+* file names
+
+<ruby>
+# Copies the instance variables of +object+ into +self+.
+#
+# Instance variable names in the +exclude+ array are ignored. If +object+
+# responds to <tt>protected_instance_variables</tt> the ones returned are
+# also ignored. For example, Rails controllers implement that method.
+# ...
+def copy_instance_variables_from(object, exclude = [])
+ ...
+end
+</ruby>
+
+WARNING: Using a pair of +&#43;...&#43;+ for fixed-width font only works with *words*; that is: anything matching <tt>\A\w&#43;\z</tt>. For anything else use +&lt;tt&gt;...&lt;/tt&gt;+, notably symbols, setters, inline snippets, etc:
+
+h4. Regular Font
+
+When "true" and "false" are English words rather than Ruby keywords use a regular font:
+
+<ruby>
+# If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt>
+# to make it reloadable:
+#
+# Dependencies.load_once_paths.delete lib_path
+</ruby>
+
+h3. Description Lists
+
+In lists of options, parameters, etc. use a hyphen between the item and its description (reads better than a colon because normally options are symbols):
+
+<ruby>
+# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+</ruby>
+
+The description starts in upper case and ends with a full stop—it's standard English.
+
+h3. Dynamically Generated Methods
+
+Methods created with +(module|class)_eval(STRING)+ have a comment by their side with an instance of the generated code. That comment is 2 spaces apart from the template:
+
+<ruby>
+for severity in Severity.constants
+ class_eval <<-EOT, __FILE__, __LINE__
+ def #{severity.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
+ add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
+ end # end
+ #
+ def #{severity.downcase}? # def debug?
+ #{severity} >= @level # DEBUG >= @level
+ end # end
+ EOT
+end
+</ruby>
+
+If the resulting lines are too wide, say 200 columns or more, we put the comment above the call:
+
+<ruby>
+# def self.find_by_login_and_activated(*args)
+# options = args.extract_options!
+# ...
+# end
+self.class_eval %{
+ def self.#{method_id}(*args)
+ options = args.extract_options!
+ ...
+ end
+}
+</ruby>
+
+h3. Changelog
+
+* July 17, 2010: ported from the docrails wiki and revised by "Xavier Noria":credits.html#fxn
+
diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile
index c69f2ae8c9..b1ee4b8be4 100644
--- a/railties/guides/source/association_basics.textile
+++ b/railties/guides/source/association_basics.textile
@@ -1371,7 +1371,41 @@ The +:through+ option specifies a join model through which to perform the query.
h6(#has_many-uniq). +:uniq+
-Specify the +:uniq => true+ option to remove duplicates from the collection. This is most useful in conjunction with the +:through+ option.
+Set the +:uniq+ option to true to keep the collection free of duplicates. This is mostly useful together with the +:through+ option.
+
+<ruby>
+class Person < ActiveRecord::Base
+ has_many :readings
+ has_many :posts, :through => :readings
+end
+
+person = Person.create(:name => 'john')
+post = Post.create(:name => 'a1')
+person.posts << post
+person.posts << post
+person.posts.inspect # => [#<Post id: 5, name: "a1">, #<Post id: 5, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 12, person_id: 5, post_id: 5>, #<Reading id: 13, person_id: 5, post_id: 5>]
+</ruby>
+
+In the above case there are two readings and +person.posts+ brings out both of them even though these records are pointing to the same post.
+
+Now let's set +:uniq+ to true:
+
+<ruby>
+class Person
+ has_many :readings
+ has_many :posts, :through => :readings, :uniq => true
+end
+
+person = Person.create(:name => 'honda')
+post = Post.create(:name => 'a1')
+person.posts << post
+person.posts << post
+person.posts.inspect # => [#<Post id: 7, name: "a1">]
+Reading.all.inspect # => [#<Reading id: 16, person_id: 7, post_id: 7>, #<Reading id: 17, person_id: 7, post_id: 7>]
+</ruby>
+
+In the above case there are still two readings. However +person.posts+ shows only one post because the collection loads only unique records.
h6(#has_many-validate). +:validate+
diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile
index 983c04c3d9..fb625f7a44 100644
--- a/railties/guides/source/command_line.textile
+++ b/railties/guides/source/command_line.textile
@@ -23,18 +23,18 @@ There are a few commands that are absolutely critical to your everyday usage of
* <tt>rake</tt>
* <tt>rails generate</tt>
* <tt>rails dbconsole</tt>
-* <tt>rails app_name</tt>
+* <tt>rails new app_name</tt>
Let's create a simple Rails application to step through each of these commands in context.
-h4. +rails+
+h4. +rails new+
-The first thing we'll want to do is create a new Rails application by running the +rails+ command after installing Rails.
+The first thing we'll want to do is create a new Rails application by running the +rails new+ command after installing Rails.
WARNING: You know you need the rails gem installed by typing +gem install rails+ first, if you don't have this installed, follow the instructions in the "Rails 3 Release Notes":/3_0_release_notes.html
<shell>
-$ rails commandsapp
+$ rails new commandsapp
create
create README
create .gitignore
@@ -352,7 +352,7 @@ $ mkdir gitapp
$ cd gitapp
$ git init
Initialized empty Git repository in .git/
-$ rails . --git --database=postgresql
+$ rails new . --git --database=postgresql
exists
create app/controllers
create app/helpers
@@ -397,7 +397,7 @@ development:
...
</shell>
-It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails+ command to generate the basis of your app.
+It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app.
h4. +server+ with Different Backends
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 86655746e4..9e0c7cd060 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -181,7 +181,7 @@ There are only a few configuration options for Action View, starting with four o
* +config.action_view.warn_cache_misses+ tells Rails to display a warning whenever an action results in a cache miss on your view paths. The default is +false+.
-* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| "&lt;div class=\"fieldWithErrors\"&gt;#{html_tag}&lt;/div&gt;" }</tt>
+* +config.action_view.field_error_proc+ provides an HTML generator for displaying errors that come from Active Record. The default is <tt>Proc.new{ |html_tag, instance| %Q(%&lt;div class=&quot;field_with_errors&quot;&gt;#{html_tag}&lt;/div&gt;).html_safe }</tt>
* +config.action_view.default_form_builder+ tells Rails which form builder to use by default. The default is +ActionView::Helpers::FormBuilder+.
diff --git a/railties/guides/source/contributing_to_rails.textile b/railties/guides/source/contributing_to_rails.textile
index 5590895508..fb81bab98d 100644
--- a/railties/guides/source/contributing_to_rails.textile
+++ b/railties/guides/source/contributing_to_rails.textile
@@ -48,7 +48,7 @@ h4. Install git
Rails uses git for source code control. You won’t be able to do anything without the Rails source code, and this is a prerequisite. The "git homepage":http://git-scm.com/ has installation instructions. If you’re on OS X, use the "Git for OS X":http://code.google.com/p/git-osx-installer/ installer. If you're unfamiliar with git, there are a variety of resources on the net that will help you learn more:
-* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
+* "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by.
* The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow.
* "GitHub":http://github.com/guides/home offers links to a variety of git resources.
* "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license.
@@ -58,30 +58,45 @@ h4. Get the Rails Source Code
Don’t fork the main Rails repository. Instead, you want to clone it to your own computer. Navigate to the folder where you want the source code (it will create its own /rails subdirectory) and run:
<shell>
-git clone git://github.com/rails/rails.git
+git clone git://github.com/rails/rails.git
cd rails
</shell>
-h4. Pick a Branch
+h4. Set up and Run the Tests
+
+All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. First, you need to install all Rails dependencies with bundler:
-Currently, there is active work being done on both the 2-3-stable branch of Rails and on the master branch (which will become Rails 3.0). If you want to work with the master branch, you're all set. To work with 2.3, you'll need to set up and switch to your own local tracking branch:
+NOTE: Ensure you install bundler v1.0
<shell>
-git branch --track 2-3-stable origin/2-3-stable
-git checkout 2-3-stable
+gem install -v=1.0.0.rc.2 bundler
+bundle install --without db
</shell>
-TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with.
+The second command will install all dependencies, except MySQL and PostgreSQL. We will come back at these soon. With dependencies installed, you can run the whole Rails test suite with:
-h4. Set up and Run the Tests
+<shell>
+rake test
+</shell>
-All of the Rails tests must pass with any code you submit, otherwise you have no chance of getting code accepted. This means you need to be able to run the tests. Rails needs the +mocha+ gem for running some tests, so install it with:
+You can also run tests for an specific framework, like Action Pack, by going into its directory and executing the same command:
<shell>
-gem install mocha
+cd actionpack
+rake test
</shell>
-For the tests that touch the database, this means creating test databases. If you're using MySQL, create a user named +rails+ with privileges on the test databases.
+h4. Testing Active Record
+
+By default, when you run Active Record tests, it will execute the test suite three times, one for each of the main databases: SQLite3, MySQL and PostgreSQL. If you are adding a feature that is not specific to the database, you can run the test suite (or just one file) for just one of them. Here is an example for SQLite3:
+
+<shell>
+cd activerecord
+rake test_sqlite3
+rake test_sqlite3 TEST=test/cases/validations_test.rb
+</shell>
+
+If you want to use another database, as MySQL, you need to create a user named +rails+ with privileges on the test databases.
<shell>
mysql> GRANT ALL PRIVILEGES ON activerecord_unittest.*
@@ -90,7 +105,13 @@ mysql> GRANT ALL PRIVILEGES ON activerecord_unittest2.*
to 'rails'@'localhost';
</shell>
-Enter this from the +activerecord+ directory to create the test databases:
+Then ensure you run bundle install without the +--without db+ option:
+
+<shell>
+bundle install
+</shell>
+
+Finally, enter this from the +activerecord+ directory to create the test databases:
<shell>
rake mysql:build_databases
@@ -100,18 +121,26 @@ NOTE: Using the rake task to create the test databases ensures they have the cor
If you’re using another database, check the files under +activerecord/test/connections+ in the Rails source code for default connection information. You can edit these files if you _must_ on your machine to provide different credentials, but obviously you should not push any such changes back to Rails.
-Now if you go back to the root of the Rails source on your machine and run +rake+ with no parameters, you should see every test in all of the Rails components pass. If you want to run the all ActiveRecord tests (or just a single one) with another database adapter, enter this from the +activerecord+ directory:
+You can now run tests as you did for +sqlite3+:
<shell>
-rake test_sqlite3
-rake test_sqlite3 TEST=test/cases/validations_test.rb
+rake test_mysql
</shell>
-You can replace +sqlite3+ with +jdbcmysql+, +jdbcsqlite3+, +jdbcpostgresql+, +mysql+ or +postgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
+You can also +myqsl+ with +postgresql+, +jdbcmysql+, +jdbcsqlite3+ or +jdbcpostgresql+. Check out the file +activerecord/RUNNING_UNIT_TESTS+ for information on running more targeted database tests, or the file +ci/ci_build.rb+ to see the test suite that the Rails continuous integration server runs.
+NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
+h4. Older versions of Rails
-NOTE: If you're working with Active Record code, you _must_ ensure that the tests pass for at least MySQL, PostgreSQL, and SQLite 3. Subtle differences between the various Active Record database adapters have been behind the rejection of many patches that looked OK when tested only against MySQL.
+If you want to work add a fix to older versions of Rails, you'll need to set up and switch to your own local tracking branch. Here is an example to switch to Rails 2.3 branch:
+
+<shell>
+git branch --track 2-3-stable origin/2-3-stable
+git checkout 2-3-stable
+</shell>
+
+TIP: You may want to "put your git branch name in your shell prompt":http://github.com/guides/put-your-git-branch-name-in-your-shell-prompt to make it easier to remember which version of the code you're working with.
h3. Helping to Resolve Existing Issues
@@ -231,15 +260,15 @@ h4. Update Rails
Update your copy of Rails. It’s pretty likely that other changes to core Rails have happened while you were working. Go get them:
<shell>
-git checkout master
-git pull
+git checkout master
+git pull
</shell>
Now reapply your patch on top of the latest changes:
<shell>
-git checkout my_new_branch
-git rebase master
+git checkout my_new_branch
+git rebase master
</shell>
No conflicts? Tests still pass? Change still seems reasonable to you? Then move on.
@@ -249,8 +278,8 @@ h4. Create a Patch
Now you can create a patch file to share with other developers (and with the Rails core team). Still in your branch, run
<shell>
-git commit -a
-git format-patch master --stdout > my_new_patch.diff
+git commit -a
+git format-patch master --stdout > my_new_patch.diff
</shell>
Sanity check the results of this operation: open the diff file in your text editor of choice and make sure that no unintended changes crept in.
@@ -275,4 +304,5 @@ h3. Changelog
* April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com
* August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy
-* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy \ No newline at end of file
+* March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy
+
diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile
index 1f1b7d076e..146b75da3f 100644
--- a/railties/guides/source/form_helpers.textile
+++ b/railties/guides/source/form_helpers.textile
@@ -647,7 +647,7 @@ the +params+ hash will contain
{'person' => {'name' => 'Henry'}}
</erb>
-and +params["name"]+ will retrieve the submitted value in the controller.
+and +params[:person][:name]+ will retrieve the submitted value in the controller.
Hashes can be nested as many levels as required, for example
diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile
index f547f29087..ffb0310816 100644
--- a/railties/guides/source/getting_started.textile
+++ b/railties/guides/source/getting_started.textile
@@ -213,9 +213,9 @@ If you open this file in a new Rails application, you'll see a default database
* The +test+ environment is used to run automated tests
* The +production+ environment is used when you deploy your application for the world to use.
-h5. Configuring a SQLite3 Database
+h5. Configuring an SQLite3 Database
-Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using a SQLite database when creating a new project, but you can always change it later.
+Rails comes with built-in support for "SQLite3":http://www.sqlite.org, which is a lightweight serverless database application. While a busy production environment may overload SQLite, it works well for development and testing. Rails defaults to using an SQLite database when creating a new project, but you can always change it later.
Here's the section of the default configuration file (<tt>config/database.yml</tt>) with connection information for the development environment:
@@ -322,16 +322,15 @@ $ rm public/index.html
We need to do this as Rails will deliver any static file in the +public+ directory in preference to any dynamic contact we generate from the controllers.
-Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. There are only comments in this file, so we need to add at the top the following:
+Now, you have to tell Rails where your actual home page is located. Open the file +config/routes.rb+ in your editor. This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with +:root to+, uncomment it and change it like the following:
<ruby>
-Blog::Application.routes.draw do |map|
+Blog::Application.routes.draw do
- root :to => "home#index"
-
- # The priority is based upon order of creation:
- # first created -> highest priority.
#...
+ # You can have the root of your site routed with "root"
+ # just remember to delete public/index.html.
+ root :to => "home#index"
</ruby>
The +root :to => "home#index"+ tells Rails to map the root action to the home controller's index action.
@@ -475,7 +474,7 @@ $ rails console
After the console loads, you can use it to work with your application's models:
<shell>
->> p = Post.create(:content => "A new post")
+>> p = Post.new(:content => "A new post")
=> #<Post id: nil, name: nil, title: nil,
content: "A new post", created_at: nil,
updated_at: nil>
@@ -1194,7 +1193,7 @@ The +destroy+ action will find the post we are looking at, locate the comment wi
h4. Deleting Associated Objects
-If you delete a post then it's associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows:
+If you delete a post then its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the +dependent+ option of an association to achieve this. Modify the Post model, +app/models/post.rb+, as follows:
<ruby>
class Post < ActiveRecord::Base
@@ -1486,6 +1485,7 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/2
+* July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com
* May 16, 2010: Added a section on configuration gotchas to address common encoding problems that people might have by "Yehuda Katz":http://www.yehudakatz.com
* April 30, 2010: Fixes, editing and updating of code samples by "Rohit Arondekar":http://rohitarondekar.com
* April 25, 2010: Couple of more minor fixups "Mikel Lindsaar":credits.html#raasdnil
diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile
index b09bb470ee..63d22db485 100644
--- a/railties/guides/source/i18n.textile
+++ b/railties/guides/source/i18n.textile
@@ -287,7 +287,7 @@ You most probably have something like this in one of your applications:
<ruby>
# config/routes.rb
-Yourapp::Application.routes.draw do |map|
+Yourapp::Application.routes.draw do
root :to => "home#index"
end
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index a930db0f1d..a0db87c188 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -88,6 +88,10 @@ Ruby on Rails Guides
<dl>
+<%= guide("Active Support Core Extensions", 'active_support_core_extensions.html') do %>
+ <p>This guide documents the Ruby core extensions defined in Active Support.</p>
+<% end %>
+
<%= guide("Rails Internationalization API", 'i18n.html') do %>
<p>This guide covers how to add internationalization to your applications. Your application will be able to translate content to different languages, change pluralization rules, use correct date formats for each country and so on.</p>
<% end %>
@@ -123,10 +127,6 @@ Ruby on Rails Guides
<%= guide("Caching with Rails", 'caching_with_rails.html', :ticket => 10) do %>
<p>Various caching techniques provided by Rails.</p>
<% end %>
-
-<%= guide("Contributing to Rails", 'contributing_to_rails.html') do %>
- <p>Rails is not &quot;somebody else's framework.&quot; This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p>
-<% end %>
</dl>
<h3>Extending Rails</h3>
@@ -147,6 +147,18 @@ Ruby on Rails Guides
<% end %>
</dl>
+<h3>Contributing to Rails</h3>
+
+<dl>
+ <%= guide("Contributing to Rails", 'contributing_to_rails.html') do %>
+ <p>Rails is not &quot;somebody else's framework.&quot; This guide covers a variety of ways that you can get involved in the ongoing development of Rails.</p>
+ <% end %>
+
+ <%= guide('API Documentation Guidelines', 'api_documentation_guidelines.html') do %>
+ <p>This guide documents the Ruby on Rails API documentation guidelines.</p>
+ <% end %>
+</dl>
+
<h3>Release Notes</h3>
<dl>
diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile
index 7a44ef7c77..f80c00b280 100644
--- a/railties/guides/source/initialization.textile
+++ b/railties/guides/source/initialization.textile
@@ -118,7 +118,7 @@ Now with Rails 3 we have a Gemfile which defines the basics our application need
# Bundle the extra gems:
# gem 'bj'
- # gem 'nokogiri', '1.4.1'
+ # gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
@@ -141,20 +141,21 @@ Here the only two gems we need are +rails+ and +sqlite3-ruby+, so it seems. This
* activesupport-3.0.0.beta4.gem
* arel-0.4.0.gem
* builder-2.1.2.gem
-* bundler-1.0.0.beta.2.gem
+* bundler-1.0.0.rc.2.gem
* erubis-2.6.6.gem
* i18n-0.4.1.gem
-* mail-2.2.4.gem
-* memcache-client-1.8.3.gem
+* mail-2.2.5.gem
+* memcache-client-1.8.5.gem
* mime-types-1.16.gem
+* nokogiri-1.4.3.1.gem
* polyglot-0.3.1.gem
-* rack-1.1.0.gem
-* rack-mount-0.6.5.gem
+* rack-1.2.1.gem
+* rack-mount-0.6.9.gem
* rack-test-0.5.4.gem
* rails-3.0.0.beta4.gem
* railties-3.0.0.beta4.gem
* rake-0.8.7.gem
-* sqlite3-ruby-1.3.0.gem
+* sqlite3-ruby-1.3.1.gem
* text-format-1.0.0.gem
* text-hyphen-1.0.0.gem
* thor-0.13.7.gem
@@ -257,28 +258,23 @@ This file goes on to define some classes that will be automatically loaded using
h4. Lazy Hooks
-At the top if the +ActiveSupport::Autoload+ module is the +def self.extended+ method:
-
-<ruby>
- def self.extended(base)
- base.extend(LazyLoadHooks)
- end
-</ruby>
-
-This is called when we extend this module into one of our classes or modules, such is the case later on when we call +extend ActiveSupport::LazyLoadHooks+ not only in the +ActiveSupport+ module, but in all of the Railtie modules (+ActiveRecord+ and so on), as well as in a couple of places.
-
+ActiveSupport::LazyLoadHooks+ is responsible for defining methods used for running hooks that are defined during the initialization process, such as the one defined inside the +active_record.initialize_timezone+ initializer:
<ruby>
initializer "active_record.initialize_timezone" do
- ActiveRecord.base_hook do
+ ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
end
end
</ruby>
-When the initializer is ran it defines a +base_hook+ for +ActiveRecord+ and will only run it when +run_base_hooks+ is called, which in the case of Active Record, is ran after the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated.
+When the initializer runs it invokes method +on_load+ for +ActiveRecord+ and the block passed to it would be called only when +run_load_hooks+ is called.
+When the entirety of +activerecord/lib/active_record/base.rb+ has been evaluated then +run_load_hooks+ is invoked. The very last line of +activerecord/lib/active_record/base.rb+ is:
+
+<ruby>
+ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
+</ruby>
h4. +require 'active_support'+ cont'd.
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 501d8fef6d..cc7d54c256 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -62,6 +62,7 @@
</dl>
<dl class="R">
<dt>Digging Deeper</dt>
+ <dd><a href="active_support_core_extensions.html">Active Support Core Extensions</a></dd>
<dd><a href="i18n.html">Rails Internationalization API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer Basics</a></dd>
<dd><a href="testing.html">Testing Rails Applications</a></dd>
@@ -71,13 +72,16 @@
<dd><a href="configuring.html">Configuring Rails Applications</a></dd>
<dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd>
<dd><a href="caching_with_rails.html">Caching with Rails</a></dd>
- <dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd>
<dt>Extending Rails</dt>
<dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">Adding a Generator to Your Plugin</a></dd>
+ <dt>Contributing to Rails</dt>
+ <dd><a href="contributing_to_rails.html">Contributing to Rails</a></dd>
+ <dd><a href="api_documentation_guidelines.html">API Documentation Guidelines</a></dd>
+
<dt>Release Notes</dt>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 Release Notes</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 Release Notes</a></dd>
diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile
index f4ba6dd53b..b9a201e5f0 100644
--- a/railties/guides/source/layouts_and_rendering.textile
+++ b/railties/guides/source/layouts_and_rendering.textile
@@ -1168,7 +1168,7 @@ Suppose you have the following +ApplicationController+ layout:
<body>
<div id="top_menu">Top menu items here</div>
<div id="menu">Menu items here</div>
- <div id="content"><%= yield(:content) or yield %></div>
+ <div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</body>
</html>
</erb>
diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile
index f05c0589b5..16f616a5bc 100644
--- a/railties/guides/source/migrations.textile
+++ b/railties/guides/source/migrations.textile
@@ -1,6 +1,6 @@
h2. Migrations
-Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run it. You'd also have to keep track of which changes need to be run against the production machines next time you deploy.
+Migrations are a convenient way for you to alter your database in a structured and organized manner. You could edit fragments of SQL by hand but you would then be responsible for telling other developers that they need to go and run them. You'd also have to keep track of which changes need to be run against the production machines next time you deploy.
Active Record tracks which migrations have already been run so all you have to do is update your source and run +rake db:migrate+. Active Record will work out which migrations should be run. It will also update your +db/schema.rb+ file to match the structure of your database.
@@ -234,7 +234,7 @@ end
which creates a +products+ table with a column called +name+ (and as discussed below, an implicit +id+ column).
-The object yielded to the block allows you create columns on the table. There are two ways of doing this: The first (traditional) form looks like
+The object yielded to the block allows you to create columns on the table. There are two ways of doing this: The first (traditional) form looks like
<ruby>
create_table :products do |t|
@@ -250,7 +250,7 @@ create_table :products do |t|
end
</ruby>
-By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place an SQL fragment in the +:options+ option. For example
+By default +create_table+ will create a primary key called +id+. You can change the name of the primary key with the +:primary_key+ option (don't forget to update the corresponding model) or if you don't want a primary key at all (for example for a HABTM join table) you can pass +:id => false+. If you need to pass database specific options you can place a SQL fragment in the +:options+ option. For example
<ruby>
create_table :products, :options => "ENGINE=BLACKHOLE" do |t|
@@ -592,4 +592,5 @@ h3. Changelog
"Lighthouse ticket":http://rails.lighthouseapp.com/projects/16213-rails-guides/tickets/6
+* July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com
* September 14, 2008: initial version by "Frederick Cheung":credits.html#fcheung
diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile
index e853ba79e9..82f2276153 100644
--- a/railties/guides/source/plugins.textile
+++ b/railties/guides/source/plugins.textile
@@ -1274,7 +1274,7 @@ class YaffleMigrationGenerator < Rails::Generator::NamedBase
end
def yaffle_local_assigns
- returning(assigns = {}) do
+ {}.tap do |assigns|
assigns[:migration_action] = "add"
assigns[:class_name] = "add_yaffle_fields_to_#{custom_file_name}"
assigns[:table_name] = custom_file_name
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index 72a76e25bb..625941ba31 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -3,16 +3,16 @@ h2. Rails Routing from the Outside In
This guide covers the user-facing features of Rails routing. By referring to this guide, you will be able to:
* Understand the code in +routes.rb+
-* Construct your own routes, using either the preferred resourceful style or with the @match@ method
+* Construct your own routes, using either the preferred resourceful style or with the <tt>match</tt> method
* Identify what parameters to expect an action to receive
-* Automatically create URLs using route helpers
+* Automatically create paths and URLs using route helpers
* Use advanced techniques such as constraints and Rack endpoints
endprologue.
h3. The Purpose of the Rails Router
-The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate URLs, avoiding the need to hardcode URL strings in your views.
+The Rails router recognizes URLs and dispatches them to a controller's action. It can also generate paths and URLs, avoiding the need to hardcode strings in your views.
h4. Connecting URLs to Code
@@ -30,9 +30,9 @@ match "/patients/:id" => "patients#show"
the request is dispatched to the +patients+ controller's +show+ action with <tt>{ :id => "17" }</tt> in +params+.
-h4. Generating URLs from Code
+h4. Generating Paths and URLs from Code
-You can also generate URLs. If your application contains this code:
+You can also generate paths and URLs. If your application contains this code:
<ruby>
@patient = Patient.find(17)
@@ -76,7 +76,7 @@ resources :photos
creates seven different routes in your application, all mapping to the +Photos+ controller:
-|_. Verb |_.URL |_.action |_.used for|
+|_. Verb |_.Path |_.action |_.used for|
|GET |/photos |index |display a list of all photos|
|GET |/photos/new |new |return an HTML form for creating a new photo|
|POST |/photos |create |create a new photo|
@@ -85,7 +85,7 @@ creates seven different routes in your application, all mapping to the +Photos+
|PUT |/photos/:id |update |update a specific photo|
|DELETE |/photos/:id |destroy |delete a specific photo|
-h4. URLs and Paths
+h4. Paths and URLs
Creating a resourceful route will also expose a number of helpers to the controllers in your application. In the case of +resources :photos+:
@@ -130,7 +130,7 @@ resource :geocoder
creates six different routes in your application, all mapping to the +Geocoders+ controller:
-|_. Verb |_.URL |_.action |_.used for|
+|_. Verb |_.Path |_.action |_.used for|
|GET |/geocoder/new |new |return an HTML form for creating the geocoder|
|POST |/geocoder |create |create the new geocoder|
|GET |/geocoder |show |display the one and only geocoder resource|
@@ -160,7 +160,7 @@ end
This will create a number of routes for each of the +posts+ and +comments+ controller. For +Admin::PostsController+, Rails will create:
-|_. Verb |_.URL |_.action |_. helper |
+|_. Verb |_.Path |_.action |_. helper |
|GET |/admin/photos |index | admin_photos_path |
|GET |/admin/photos/new |new | new_admin_photos_path |
|POST |/admin/photos |create | admin_photos_path |
@@ -197,16 +197,16 @@ or, for a single case
resources :posts, :path => "/admin"
</ruby>
-In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following URLs map to +PostsController+:
+In each of these cases, the named routes remain the same as if you did not use +scope+. In the last case, the following paths map to +PostsController+:
-|_. Verb |_.URL |_.action |_. helper |
-|GET |photos |index | photos_path |
-|GET |photos/new |new | photos_path |
-|POST |photos |create | photos_path |
-|GET |photos/1 |show | photo_path(id) |
-|GET |photos/1/edit |edit | edit_photo_path(id) |
-|PUT |photos/1 |update | photo_path(id) |
-|DELETE |photos/1 |destroy | photo_path(id) |
+|_. Verb |_.Path |_.action |_. helper |
+|GET |/admin/photos |index | photos_path |
+|GET |/admin/photos/new |new | photos_path |
+|POST |/admin/photos |create | photos_path |
+|GET |/admin/photos/1 |show | photo_path(id) |
+|GET |/admin/photos/1/edit |edit | edit_photo_path(id) |
+|PUT |/admin/photos/1 |update | photo_path(id) |
+|DELETE |/admin/photos/1 |destroy | photo_path(id) |
h4. Nested Resources
@@ -232,7 +232,7 @@ end
In addition to the routes for magazines, this declaration will also route ads to an +AdsController+. The ad URLs require a magazine:
-|_.Verb |_.URL |_.action |_.used for|
+|_.Verb |_.Path |_.action |_.used for|
|GET |/magazines/1/ads |index |display a list of all ads for a specific magazine|
|GET |/magazines/1/ads/new |new |return an HTML form for creating a new ad belonging to a specific magazine|
|POST |/magazines/1/ads |create |create a new ad belonging to a specific magazine|
@@ -256,7 +256,7 @@ resources :publishers do
end
</ruby>
-Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize URLs such as
+Deeply-nested resources quickly become cumbersome. In this case, for example, the application would recognize paths such as
<pre>
/publishers/1/magazines/2/photos/3
@@ -266,9 +266,9 @@ The corresponding route helper would be +publisher_magazine_photo_url+, requirin
TIP: _Resources should never be nested more than 1 level deep._
-h4. Creating URLs From Objects
+h4. Creating Paths and URLs From Objects
-In addition to using the routing helpers, Rails can also create URLs from an array of parameters. For example, suppose you have this set of routes:
+In addition to using the routing helpers, Rails can also create paths and URLs from an array of parameters. For example, suppose you have this set of routes:
<ruby>
resources :magazines do
@@ -340,7 +340,7 @@ resources :photos do
end
</ruby>
-This will enable Rails to recognize URLs such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
+This will enable Rails to recognize paths such as +/photos/search+ with GET, and route to the +search+ action of +PhotosController+. It will also create the +search_photos_url+ and +search_photos_path+ route helpers.
Just as with member routes, you can pass +:on+ to a route:
@@ -380,7 +380,7 @@ You can set up as many dynamic segments within a regular route as you like. Anyt
match ':controller/:action/:id/:user_id'
</ruby>
-An incoming URL of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
+An incoming path of +/photos/show/1/2+ will be dispatched to the +show+ action of the +PhotosController+. +params[:id]+ will be +"1"+, and +params[:user_id]+ will be +"2"+.
NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. If you need to do this then use a constraint on :controller that matches the namespace you require. e.g:
@@ -396,7 +396,7 @@ You can specify static segments when creating a route:
match ':controller/:action/:id/with_user/:user_id'
</ruby>
-This route would respond to URLs such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
+This route would respond to paths such as +/photos/show/1/with_user/2+. In this case, +params+ would be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
h4. The Query String
@@ -406,7 +406,7 @@ The +params+ will also include any parameters from the query string. For example
match ':controller/:action/:id'
</ruby>
-An incoming URL of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
+An incoming path of +/photos/show/1?user_id=2+ will be dispatched to the +show+ action of the +Photos+ controller. +params+ will be <tt>{ :controller => "photos", :action => "show", :id => "1", :user_id => "2" }</tt>.
h4. Defining Defaults
@@ -416,7 +416,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within
match 'photos/:id' => 'photos#show'
</ruby>
-With this route, Rails will match an incoming URL of +/photos/12+ to the +show+ action of +PhotosController+.
+With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+.
You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example:
@@ -441,13 +441,13 @@ h4. Segment Constraints
You can use the +:constraints+ option to enforce a format for a dynamic segment:
<ruby>
-match 'photo/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
+match 'photos/:id' => 'photos#show', :constraints => { :id => /[A-Z]\d{5}/ }
</ruby>
-This route would match URLs such as +/photo/A12345+. You can more succinctly express the same route this way:
+This route would match paths such as +/photos/A12345+. You can more succinctly express the same route this way:
<ruby>
-match 'photo/:id' => 'photos#show', :id => /[A-Z]\d{5}/
+match 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/
</ruby>
+:constraints+ takes regular expression. However note that regexp anchors can't be used within constraints. For example following route will not work:
@@ -472,7 +472,7 @@ You can also constrain a route based on any method on the <a href="action_contro
You specify a request-based constraint the same way that you specify a segment constraint:
<ruby>
-match "photo", :constraints => {:subdomain => "admin"}
+match "photos", :constraints => {:subdomain => "admin"}
</ruby>
You can also specify constrains in a block form:
@@ -511,10 +511,10 @@ h4. Route Globbing
Route globbing is a way to specify that a particular parameter should be matched to all the remaining parts of a route. For example
<ruby>
-match 'photo/*other' => 'photos#unknown'
+match 'photos/*other' => 'photos#unknown'
</ruby>
-This route would match +photo/12+ or +/photo/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
+This route would match +photos/12+ or +/photos/long/path/to/12+, setting +params[:other]+ to +"12"+ or +"long/path/to/12"+.
h4. Redirection
@@ -573,9 +573,9 @@ The +:controller+ option lets you explicitly specify a controller to use for the
resources :photos, :controller => "images"
</ruby>
-will recognize incoming URLs beginning with +/photo+ but route to the +Images+ controller:
+will recognize incoming paths beginning with +/photo+ but route to the +Images+ controller:
-|_. Verb |_.URL |_.action |
+|_. Verb |_.Path |_.action |
|GET |/photos |index |
|GET |/photos/new |new |
|POST |/photos |create |
@@ -584,7 +584,7 @@ will recognize incoming URLs beginning with +/photo+ but route to the +Images+ c
|PUT |/photos/1 |update |
|DELETE |/photos/1 |destroy |
-NOTE: Use +photos_path+, +new_photos_path+, etc. to generate URLs for this resource.
+NOTE: Use +photos_path+, +new_photos_path+, etc. to generate paths for this resource.
h4. Specifying Constraints
@@ -615,9 +615,9 @@ The +:as+ option lets you override the normal naming for the named route helpers
resources :photos, :as => "images"
</ruby>
-will recognize incoming URLs beginning with +/photos+ and route the requests to +PhotosController+:
+will recognize incoming paths beginning with +/photos+ and route the requests to +PhotosController+:
-|_.HTTP verb|_.URL |_.action |_.named helper |
+|_.HTTP verb|_.Path |_.action |_.named helper |
|GET |/photos |index | images_path |
|GET |/photos/new |new | new_image_path |
|POST |/photos |create | images_path |
@@ -628,20 +628,20 @@ will recognize incoming URLs beginning with +/photos+ and route the requests to
h4. Overriding the +new+ and +edit+ Segments
-The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in URLs:
+The +:path_names+ option lets you override the automatically-generated "new" and "edit" segments in paths:
<ruby>
resources :photos, :path_names => { :new => 'make', :edit => 'change' }
</ruby>
-This would cause the routing to recognize URLs such as
+This would cause the routing to recognize paths such as
<plain>
/photos/make
/photos/1/change
</plain>
-NOTE: The actual action names aren't changed by this option. The two URLs shown would still route to the new and edit actions.
+NOTE: The actual action names aren't changed by this option. The two paths shown would still route to the +new+ and +edit+ actions.
TIP: If you find yourself wanting to change this option uniformly for all of your routes, you can use a scope:
@@ -709,7 +709,7 @@ end
Rails now creates routes to the +CategoriesControlleR+.
-|_.HTTP verb|_.URL |_.action |
+|_.HTTP verb|_.Path |_.action |
|GET |/kategorien |index |
|GET |/kategorien/neu |new |
|POST |/kategorien |create |
@@ -762,6 +762,12 @@ formatted_users GET /users.:format {:controller=>"users", :action=>"index"}
POST /users.:format {:controller=>"users", :action=>"create"}
</pre>
+You may restrict the listing to the routes that map to a particular controller setting the +CONTROLLER+ environment variable:
+
+<shell>
+$ CONTROLLER=users rake routes
+</shell>
+
TIP: You'll find that the output from +rake routes+ is much more readable if you widen your terminal window until the output lines don't wrap.
h4. Testing Routes
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 60108d5ab3..8ce0001080 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -286,7 +286,7 @@ When filtering user input file names, _(highlight)don't try to remove malicious
<ruby>
def sanitize_filename(filename)
- returning filename.strip do |name|
+ filename.strip.tap do |name|
# NOTE: File.basename doesn't work right with Windows paths on Unix
# get only the filename, not the whole path
name.sub! /\A.*(\\|\/)/, ''
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 458177b954..5b26333486 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -149,6 +149,13 @@ module Rails
self
end
+ def load_console(sandbox=false)
+ initialize_console(sandbox)
+ railties.all { |r| r.load_console }
+ super()
+ self
+ end
+
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
@@ -198,6 +205,7 @@ module Rails
middleware.use ::ActionDispatch::ParamsParser
middleware.use ::Rack::MethodOverride
middleware.use ::ActionDispatch::Head
+ middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support if config.action_dispatch.best_standards_support
end
end
@@ -212,5 +220,11 @@ module Rails
def initialize_generators
require "rails/generators"
end
+
+ def initialize_console(sandbox=false)
+ require "rails/console/app"
+ require "rails/console/sandbox" if sandbox
+ require "rails/console/helpers"
+ end
end
end
diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb
index b9353ba336..60a93c9848 100644
--- a/railties/lib/rails/commands.rb
+++ b/railties/lib/rails/commands.rb
@@ -70,4 +70,4 @@ In addition to those, there are:
All commands can be run with -h for more information.
EOT
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb
index 50df6ba405..834a120c01 100644
--- a/railties/lib/rails/commands/console.rb
+++ b/railties/lib/rails/commands/console.rb
@@ -23,10 +23,7 @@ module Rails
opt.parse!(ARGV)
end
- @app.initialize!
- require "rails/console/app"
- require "rails/console/sandbox" if options[:sandbox]
- require "rails/console/helpers"
+ @app.load_console(options[:sandbox])
if options[:debugger]
begin
diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb
index 0becb780de..e5af12b901 100644
--- a/railties/lib/rails/configuration.rb
+++ b/railties/lib/rails/configuration.rb
@@ -56,12 +56,11 @@ module Rails
return @options[method] if args.empty?
- if method == :rails
- namespace, configuration = :rails, args.shift
- elsif args.first.is_a?(Hash)
+ if method == :rails || args.first.is_a?(Hash)
namespace, configuration = method, args.shift
else
namespace, configuration = args.shift, args.shift
+ namespace = namespace.to_sym if namespace.respond_to?(:to_sym)
@options[:rails][method] = namespace
end
diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb
index 2f465670cf..521ed95447 100644
--- a/railties/lib/rails/engine/configuration.rb
+++ b/railties/lib/rails/engine/configuration.rb
@@ -50,4 +50,4 @@ module Rails
end
end
end
-end \ No newline at end of file
+end
diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb
index a27d38e23a..668ef48892 100644
--- a/railties/lib/rails/generators/actions.rb
+++ b/railties/lib/rails/generators/actions.rb
@@ -40,7 +40,7 @@ module Rails
end
end
- # Adds an entry into config/environment.rb for the supplied gem. If env
+ # Adds an entry into Gemfile for the supplied gem. If env
# is specified, add the gem to the given environment.
#
# ==== Example
@@ -100,7 +100,7 @@ module Rails
end
end
- # Adds a line inside the Initializer block for config/environment.rb.
+ # Adds a line inside the Application class for config/application.rb.
#
# If options :env is specified, the line is appended to the corresponding
# file in config/environments.
@@ -275,7 +275,7 @@ module Rails
#
def route(routing_code)
log :route, routing_code
- sentinel = /\.routes\.draw do(\s*\|map\|)?\s*$/
+ sentinel = /\.routes\.draw do(?:\s*\|map\|)?\s*$/
in_root do
inject_into_file 'config/routes.rb', "\n #{routing_code}\n", { :after => sentinel, :verbose => false }
diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb
index fe6321af30..4b828340d2 100644
--- a/railties/lib/rails/generators/active_model.rb
+++ b/railties/lib/rails/generators/active_model.rb
@@ -9,16 +9,16 @@ module Rails
# For example:
#
# ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]")
- # #=> "Foo.find(params[:id])"
+ # # => "Foo.find(params[:id])"
#
# Datamapper::Generators::ActiveModel.find(Foo, "params[:id]")
- # #=> "Foo.get(params[:id])"
+ # # => "Foo.get(params[:id])"
#
# On initialization, the ActiveModel accepts the instance name that will
# receive the calls:
#
# builder = ActiveRecord::Generators::ActiveModel.new "@foo"
- # builder.save #=> "@foo.save"
+ # builder.save # => "@foo.save"
#
# The only exception in ActiveModel for ActiveRecord is the use of self.build
# instead of self.new.
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index 67a9a6030d..fdfceb14ce 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -3,7 +3,7 @@ begin
rescue LoadError
puts "Thor is not available.\nIf you ran this command from a git checkout " \
"of Rails, please make sure thor is installed,\nand run this command " \
- "as `ruby /path/to/rails new myapp --dev`"
+ "as `ruby #{$0} #{ARGV.join(" ")} --dev`"
exit
end
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 7d50e7da67..a90f109844 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -115,6 +115,7 @@ module Rails
directory "public/javascripts"
else
empty_directory_with_gitkeep "public/javascripts"
+ create_file "public/javascripts/application.js"
end
end
@@ -167,7 +168,7 @@ module Rails
:desc => "Path to an application builder (can be a filesystem path or URL)"
class_option :template, :type => :string, :aliases => "-m",
- :desc => "Path to an application template (can be a filesystem path or URL)."
+ :desc => "Path to an application template (can be a filesystem path or URL)"
class_option :dev, :type => :boolean, :default => false,
:desc => "Setup the application with Gemfile pointing to your Rails checkout"
@@ -178,11 +179,11 @@ module Rails
class_option :skip_gemfile, :type => :boolean, :default => false,
:desc => "Don't create a Gemfile"
- class_option :skip_activerecord, :type => :boolean, :aliases => "-O", :default => false,
- :desc => "Skip ActiveRecord files"
+ class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false,
+ :desc => "Skip Active Record files"
- class_option :skip_testunit, :type => :boolean, :aliases => "-T", :default => false,
- :desc => "Skip TestUnit files"
+ class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false,
+ :desc => "Skip Test::Unit files"
class_option :skip_prototype, :type => :boolean, :aliases => "-J", :default => false,
:desc => "Skip Prototype files"
@@ -204,7 +205,7 @@ module Rails
super
- if !options[:skip_activerecord] && !DATABASES.include?(options[:database])
+ if !options[:skip_active_record] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
end
@@ -215,7 +216,7 @@ module Rails
empty_directory '.'
set_default_accessors!
- FileUtils.cd(destination_root)
+ FileUtils.cd(destination_root) unless options[:pretend]
end
def create_root_files
@@ -238,8 +239,8 @@ module Rails
template "config/boot.rb"
end
- def create_activerecord_files
- return if options[:skip_activerecord]
+ def create_active_record_files
+ return if options[:skip_active_record]
build(:database_yml)
end
@@ -280,7 +281,7 @@ module Rails
end
def create_test_files
- build(:test) unless options[:skip_testunit]
+ build(:test) unless options[:skip_test_unit]
end
def create_tmp_files
@@ -355,8 +356,13 @@ module Rails
@app_name ||= File.basename(destination_root)
end
+ def defined_app_const_base
+ Rails.respond_to?(:application) && defined?(Rails::Application) &&
+ Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "")
+ end
+
def app_const_base
- @app_const_base ||= app_name.gsub(/\W/, '_').squeeze('_').camelize
+ @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize
end
def app_const
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index a108968b97..1980684a94 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -28,7 +28,7 @@ gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= req
# Bundle the extra gems:
# gem 'bj'
-# gem 'nokogiri', '1.4.1'
+# gem 'nokogiri'
# gem 'sqlite3-ruby', :require => 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb
index 67a38ea1d5..7d63e99e05 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/application.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb
@@ -1,6 +1,6 @@
require File.expand_path('../boot', __FILE__)
-<% unless options[:skip_activerecord] -%>
+<% unless options[:skip_active_record] -%>
require 'rails/all'
<% else -%>
# Pick the frameworks you want:
@@ -22,7 +22,7 @@ module <%= app_const_base %>
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
- # config.autoload_paths += %W( #{config.root}/extras )
+ # config.autoload_paths += %W(#{config.root}/extras)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.
@@ -39,12 +39,12 @@ module <%= app_const_base %>
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
- # 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
+ # JavaScript files you want as :defaults (application.js is always included).
+<% if options[:skip_prototype] -%>
+ config.action_view.javascript_expansions[:defaults] = %w()
+<% else -%>
+ # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+<% end -%>
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = "utf-8"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
index f99ee937f3..fddf8b8144 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml
@@ -4,7 +4,7 @@
# http://rubyforge.org/projects/ruby-oci8/
#
# Specify your database using any valid connection syntax, such as a
-# tnsnames.ora service name, or a SQL connect url string of the form:
+# tnsnames.ora service name, or an SQL connect string of the form:
#
# //host:[port][/service name]
#
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 99758dfcf7..7616614aff 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -19,4 +19,8 @@
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
end
+
diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html
index c65593e8bc..75d5edd06d 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/index.html
+++ b/railties/lib/rails/generators/rails/app/templates/public/index.html
@@ -151,19 +151,6 @@
}
- #search {
- margin: 0;
- padding-top: 10px;
- padding-bottom: 10px;
- font-size: 11px;
- }
- #search input {
- font-size: 11px;
- margin: 2px;
- }
- #search-text {width: 170px}
-
-
#sidebar ul {
margin-left: 0;
padding-left: 0;
@@ -194,16 +181,6 @@
info.innerHTML = xhr.responseText;
info.style.display = 'block'
}
-
- function prepend() {
- search = document.getElementById('search-text');
- text = search.value;
- search.value = 'site:rubyonrails.org ' + text;
- }
-
- window.onload = function() {
- document.getElementById('search-text').value = '';
- }
</script>
</head>
<body>
diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
index 9fe6e1243b..06249a6ae3 100644
--- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
+++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/prototype.js
@@ -1,5 +1,5 @@
-/* Prototype JavaScript framework, version 1.6.1
- * (c) 2005-2009 Sam Stephenson
+/* Prototype JavaScript framework, version 1.7_rc2
+ * (c) 2005-2010 Sam Stephenson
*
* Prototype is freely distributable under the terms of an MIT-style license.
* For details, see the Prototype web site: http://www.prototypejs.org/
@@ -7,7 +7,8 @@
*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.6.1',
+
+ Version: '1.7_rc2',
Browser: (function(){
var ua = navigator.userAgent;
@@ -17,13 +18,15 @@ var Prototype = {
Opera: isOpera,
WebKit: ua.indexOf('AppleWebKit/') > -1,
Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
- MobileSafari: /Apple.*Mobile.*Safari/.test(ua)
+ MobileSafari: /Apple.*Mobile/.test(ua)
}
})(),
BrowserFeatures: {
XPath: !!document.evaluate,
+
SelectorsAPI: !!document.querySelector,
+
ElementExtensions: (function() {
var constructor = window.Element || window.HTMLElement;
return !!(constructor && constructor.prototype);
@@ -32,9 +35,9 @@ var Prototype = {
if (typeof window.HTMLDivElement !== 'undefined')
return true;
- var div = document.createElement('div');
- var form = document.createElement('form');
- var isSupported = false;
+ var div = document.createElement('div'),
+ form = document.createElement('form'),
+ isSupported = false;
if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
isSupported = true;
@@ -50,6 +53,7 @@ var Prototype = {
JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
emptyFunction: function() { },
+
K: function(x) { return x }
};
@@ -79,6 +83,14 @@ var Try = {
/* Based on Alex Arnell's inheritance implementation. */
var Class = (function() {
+
+ var IS_DONTENUM_BUGGY = (function(){
+ for (var p in { toString: 1 }) {
+ if (p === 'toString') return false;
+ }
+ return true;
+ })();
+
function subclass() {};
function create() {
var parent = null, properties = $A(arguments);
@@ -99,7 +111,7 @@ var Class = (function() {
parent.subclasses.push(klass);
}
- for (var i = 0; i < properties.length; i++)
+ for (var i = 0, length = properties.length; i < length; i++)
klass.addMethods(properties[i]);
if (!klass.prototype.initialize)
@@ -110,10 +122,10 @@ var Class = (function() {
}
function addMethods(source) {
- var ancestor = this.superclass && this.superclass.prototype;
- var properties = Object.keys(source);
+ var ancestor = this.superclass && this.superclass.prototype,
+ properties = Object.keys(source);
- if (!Object.keys({ toString: true }).length) {
+ if (IS_DONTENUM_BUGGY) {
if (source.toString != Object.prototype.toString)
properties.push("toString");
if (source.valueOf != Object.prototype.valueOf)
@@ -123,7 +135,7 @@ var Class = (function() {
for (var i = 0, length = properties.length; i < length; i++) {
var property = properties[i], value = source[property];
if (ancestor && Object.isFunction(value) &&
- value.argumentNames().first() == "$super") {
+ value.argumentNames()[0] == "$super") {
var method = value;
value = (function(m) {
return function() { return ancestor[m].apply(this, arguments); };
@@ -147,7 +159,35 @@ var Class = (function() {
})();
(function() {
- var _toString = Object.prototype.toString;
+ var _toString = Object.prototype.toString,
+ NULL_TYPE = 'Null',
+ UNDEFINED_TYPE = 'Undefined',
+ BOOLEAN_TYPE = 'Boolean',
+ NUMBER_TYPE = 'Number',
+ STRING_TYPE = 'String',
+ OBJECT_TYPE = 'Object',
+ BOOLEAN_CLASS = '[object Boolean]',
+ NUMBER_CLASS = '[object Number]',
+ STRING_CLASS = '[object String]',
+ ARRAY_CLASS = '[object Array]',
+ NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON &&
+ typeof JSON.stringify === 'function' &&
+ JSON.stringify(0) === '0' &&
+ typeof JSON.stringify(Prototype.K) === 'undefined';
+
+ function Type(o) {
+ switch(o) {
+ case null: return NULL_TYPE;
+ case (void 0): return UNDEFINED_TYPE;
+ }
+ var type = typeof o;
+ switch(type) {
+ case 'boolean': return BOOLEAN_TYPE;
+ case 'number': return NUMBER_TYPE;
+ case 'string': return STRING_TYPE;
+ }
+ return OBJECT_TYPE;
+ }
function extend(destination, source) {
for (var property in source)
@@ -166,27 +206,70 @@ var Class = (function() {
}
}
- function toJSON(object) {
- var type = typeof object;
- switch (type) {
- case 'undefined':
- case 'function':
- case 'unknown': return;
- case 'boolean': return object.toString();
+ function toJSON(value) {
+ return Str('', { '': value }, []);
+ }
+
+ function Str(key, holder, stack) {
+ var value = holder[key],
+ type = typeof value;
+
+ if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
}
- if (object === null) return 'null';
- if (object.toJSON) return object.toJSON();
- if (isElement(object)) return;
+ var _class = _toString.call(value);
- var results = [];
- for (var property in object) {
- var value = toJSON(object[property]);
- if (!isUndefined(value))
- results.push(property.toJSON() + ': ' + value);
+ switch (_class) {
+ case NUMBER_CLASS:
+ case BOOLEAN_CLASS:
+ case STRING_CLASS:
+ value = value.valueOf();
+ }
+
+ switch (value) {
+ case null: return 'null';
+ case true: return 'true';
+ case false: return 'false';
}
- return '{' + results.join(', ') + '}';
+ type = typeof value;
+ switch (type) {
+ case 'string':
+ return value.inspect(true);
+ case 'number':
+ return isFinite(value) ? String(value) : 'null';
+ case 'object':
+
+ for (var i = 0, length = stack.length; i < length; i++) {
+ if (stack[i] === value) { throw new TypeError(); }
+ }
+ stack.push(value);
+
+ var partial = [];
+ if (_class === ARRAY_CLASS) {
+ for (var i = 0, length = value.length; i < length; i++) {
+ var str = Str(i, value, stack);
+ partial.push(typeof str === 'undefined' ? 'null' : str);
+ }
+ partial = '[' + partial.join(',') + ']';
+ } else {
+ var keys = Object.keys(value);
+ for (var i = 0, length = keys.length; i < length; i++) {
+ var key = keys[i], str = Str(key, value, stack);
+ if (typeof str !== "undefined") {
+ partial.push(key.inspect(true)+ ':' + str);
+ }
+ }
+ partial = '{' + partial.join(',') + '}';
+ }
+ stack.pop();
+ return partial;
+ }
+ }
+
+ function stringify(object) {
+ return JSON.stringify(object);
}
function toQueryString(object) {
@@ -198,9 +281,13 @@ var Class = (function() {
}
function keys(object) {
+ if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
var results = [];
- for (var property in object)
- results.push(property);
+ for (var property in object) {
+ if (object.hasOwnProperty(property)) {
+ results.push(property);
+ }
+ }
return results;
}
@@ -220,9 +307,15 @@ var Class = (function() {
}
function isArray(object) {
- return _toString.call(object) == "[object Array]";
+ return _toString.call(object) === ARRAY_CLASS;
}
+ var hasNativeIsArray = (typeof Array.isArray == 'function')
+ && Array.isArray([]) && !Array.isArray({});
+
+ if (hasNativeIsArray) {
+ isArray = Array.isArray;
+ }
function isHash(object) {
return object instanceof Hash;
@@ -233,11 +326,11 @@ var Class = (function() {
}
function isString(object) {
- return _toString.call(object) == "[object String]";
+ return _toString.call(object) === STRING_CLASS;
}
function isNumber(object) {
- return _toString.call(object) == "[object Number]";
+ return _toString.call(object) === NUMBER_CLASS;
}
function isUndefined(object) {
@@ -247,10 +340,10 @@ var Class = (function() {
extend(Object, {
extend: extend,
inspect: inspect,
- toJSON: toJSON,
+ toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON,
toQueryString: toQueryString,
toHTML: toHTML,
- keys: keys,
+ keys: Object.keys || keys,
values: values,
clone: clone,
isElement: isElement,
@@ -311,7 +404,7 @@ Object.extend(Function.prototype, (function() {
function delay(timeout) {
var __method = this, args = slice.call(arguments, 1);
- timeout = timeout * 1000
+ timeout = timeout * 1000;
return window.setTimeout(function() {
return __method.apply(__method, args);
}, timeout);
@@ -352,14 +445,28 @@ Object.extend(Function.prototype, (function() {
})());
-Date.prototype.toJSON = function() {
- return '"' + this.getUTCFullYear() + '-' +
- (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
- this.getUTCDate().toPaddedString(2) + 'T' +
- this.getUTCHours().toPaddedString(2) + ':' +
- this.getUTCMinutes().toPaddedString(2) + ':' +
- this.getUTCSeconds().toPaddedString(2) + 'Z"';
-};
+
+(function(proto) {
+
+
+ function toISOString() {
+ return this.getUTCFullYear() + '-' +
+ (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+ this.getUTCDate().toPaddedString(2) + 'T' +
+ this.getUTCHours().toPaddedString(2) + ':' +
+ this.getUTCMinutes().toPaddedString(2) + ':' +
+ this.getUTCSeconds().toPaddedString(2) + 'Z';
+ }
+
+
+ function toJSON() {
+ return this.toISOString();
+ }
+
+ if (!proto.toISOString) proto.toISOString = toISOString;
+ if (!proto.toJSON) proto.toJSON = toJSON;
+
+})(Date.prototype);
RegExp.prototype.match = RegExp.prototype.test;
@@ -418,6 +525,9 @@ Object.extend(String, {
});
Object.extend(String.prototype, (function() {
+ var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
+ typeof JSON.parse === 'function' &&
+ JSON.parse('{"test": true}').test;
function prepareReplacement(replacement) {
if (Object.isFunction(replacement)) return replacement;
@@ -484,8 +594,8 @@ Object.extend(String.prototype, (function() {
}
function extractScripts() {
- var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
- var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+ var matchAll = new RegExp(Prototype.ScriptFragment, 'img'),
+ matchOne = new RegExp(Prototype.ScriptFragment, 'im');
return (this.match(matchAll) || []).map(function(scriptTag) {
return (scriptTag.match(matchOne) || ['', ''])[1];
});
@@ -510,8 +620,9 @@ Object.extend(String.prototype, (function() {
return match[1].split(separator || '&').inject({ }, function(hash, pair) {
if ((pair = pair.split('='))[0]) {
- var key = decodeURIComponent(pair.shift());
- var value = pair.length > 1 ? pair.join('=') : pair[0];
+ var key = decodeURIComponent(pair.shift()),
+ value = pair.length > 1 ? pair.join('=') : pair[0];
+
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
@@ -538,17 +649,9 @@ Object.extend(String.prototype, (function() {
}
function camelize() {
- var parts = this.split('-'), len = parts.length;
- if (len == 1) return parts[0];
-
- var camelized = this.charAt(0) == '-'
- ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
- : parts[0];
-
- for (var i = 1; i < len; i++)
- camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
-
- return camelized;
+ return this.replace(/-+(.)?/g, function(match, chr) {
+ return chr ? chr.toUpperCase() : '';
+ });
}
function capitalize() {
@@ -578,10 +681,6 @@ Object.extend(String.prototype, (function() {
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
}
- function toJSON() {
- return this.inspect(true);
- }
-
function unfilterJSON(filter) {
return this.replace(filter || Prototype.JSONFilter, '$1');
}
@@ -589,29 +688,42 @@ Object.extend(String.prototype, (function() {
function isJSON() {
var str = this;
if (str.blank()) return false;
- str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
- return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+ str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@');
+ str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']');
+ str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
+ return (/^[\],:{}\s]*$/).test(str);
}
function evalJSON(sanitize) {
- var json = this.unfilterJSON();
+ var json = this.unfilterJSON(),
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+ if (cx.test(json)) {
+ json = json.replace(cx, function (a) {
+ return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
try {
if (!sanitize || json.isJSON()) return eval('(' + json + ')');
} catch (e) { }
throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
}
+ function parseJSON() {
+ var json = this.unfilterJSON();
+ return JSON.parse(json);
+ }
+
function include(pattern) {
return this.indexOf(pattern) > -1;
}
function startsWith(pattern) {
- return this.indexOf(pattern) === 0;
+ return this.lastIndexOf(pattern, 0) === 0;
}
function endsWith(pattern) {
var d = this.length - pattern.length;
- return d >= 0 && this.lastIndexOf(pattern) === d;
+ return d >= 0 && this.indexOf(pattern, d) === d;
}
function empty() {
@@ -631,7 +743,7 @@ Object.extend(String.prototype, (function() {
sub: sub,
scan: scan,
truncate: truncate,
- strip: String.prototype.trim ? String.prototype.trim : strip,
+ strip: String.prototype.trim || strip,
stripTags: stripTags,
stripScripts: stripScripts,
extractScripts: extractScripts,
@@ -648,10 +760,9 @@ Object.extend(String.prototype, (function() {
underscore: underscore,
dasherize: dasherize,
inspect: inspect,
- toJSON: toJSON,
unfilterJSON: unfilterJSON,
isJSON: isJSON,
- evalJSON: evalJSON,
+ evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON,
include: include,
startsWith: startsWith,
endsWith: endsWith,
@@ -677,8 +788,9 @@ var Template = Class.create({
var before = match[1] || '';
if (before == '\\') return match[2];
- var ctx = object, expr = match[3];
- var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+ var ctx = object, expr = match[3],
+ pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+
match = pattern.exec(expr);
if (match == null) return before;
@@ -943,6 +1055,7 @@ var Enumerable = (function() {
find: detect
};
})();
+
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
@@ -951,6 +1064,7 @@ function $A(iterable) {
return results;
}
+
function $w(string) {
if (!Object.isString(string)) return [];
string = string.strip();
@@ -1007,7 +1121,7 @@ Array.from = $A;
}
function reverse(inline) {
- return (inline !== false ? this : this.toArray())._reverse();
+ return (inline === false ? this.toArray() : this)._reverse();
}
function uniq(sorted) {
@@ -1037,15 +1151,6 @@ Array.from = $A;
return '[' + this.map(Object.inspect).join(', ') + ']';
}
- function toJSON() {
- var results = [];
- this.each(function(object) {
- var value = Object.toJSON(object);
- if (!Object.isUndefined(value)) results.push(value);
- });
- return '[' + results.join(', ') + ']';
- }
-
function indexOf(item, i) {
i || (i = 0);
var length = this.length;
@@ -1094,8 +1199,7 @@ Array.from = $A;
clone: clone,
toArray: clone,
size: size,
- inspect: inspect,
- toJSON: toJSON
+ inspect: inspect
});
var CONCAT_ARGUMENTS_BUGGY = (function() {
@@ -1116,6 +1220,7 @@ var Hash = Class.create(Enumerable, (function() {
this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
}
+
function _each(iterator) {
for (var key in this._object) {
var value = this._object[key], pair = [key, value];
@@ -1144,6 +1249,8 @@ var Hash = Class.create(Enumerable, (function() {
return Object.clone(this._object);
}
+
+
function keys() {
return this.pluck('key');
}
@@ -1193,10 +1300,6 @@ var Hash = Class.create(Enumerable, (function() {
}).join(', ') + '}>';
}
- function toJSON() {
- return Object.toJSON(this.toObject());
- }
-
function clone() {
return new Hash(this);
}
@@ -1216,7 +1319,7 @@ var Hash = Class.create(Enumerable, (function() {
update: update,
toQueryString: toQueryString,
inspect: inspect,
- toJSON: toJSON,
+ toJSON: toObject,
clone: clone
};
})());
@@ -1241,10 +1344,6 @@ Object.extend(Number.prototype, (function() {
return '0'.times(length - string.length) + string;
}
- function toJSON() {
- return isFinite(this) ? this.toString() : 'null';
- }
-
function abs() {
return Math.abs(this);
}
@@ -1266,7 +1365,6 @@ Object.extend(Number.prototype, (function() {
succ: succ,
times: times,
toPaddedString: toPaddedString,
- toJSON: toJSON,
abs: abs,
round: round,
ceil: ceil,
@@ -1558,14 +1656,14 @@ Ajax.Response = Class.create({
var transport = this.transport = request.transport,
readyState = this.readyState = transport.readyState;
- if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+ if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
this.status = this.getStatus();
this.statusText = this.getStatusText();
this.responseText = String.interpret(transport.responseText);
this.headerJSON = this._getHeaderJSON();
}
- if(readyState == 4) {
+ if (readyState == 4) {
var xml = transport.responseXML;
this.responseXML = Object.isUndefined(xml) ? null : xml;
this.responseJSON = this._getResponseJSON();
@@ -1705,7 +1803,6 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
});
-
function $(element) {
if (arguments.length > 1) {
for (var i = 0, elements = [], length = arguments.length; i < length; i++)
@@ -1730,7 +1827,7 @@ if (Prototype.BrowserFeatures.XPath) {
/*--------------------------------------------------------------------------*/
-if (!window.Node) var Node = { };
+if (!Node) var Node = { };
if (!Node.ELEMENT_NODE) {
Object.extend(Node, {
@@ -1750,29 +1847,26 @@ if (!Node.ELEMENT_NODE) {
}
+
(function(global) {
- var SETATTRIBUTE_IGNORES_NAME = (function(){
- var elForm = document.createElement("form");
- var elInput = document.createElement("input");
- var root = document.documentElement;
- elInput.setAttribute("name", "test");
- elForm.appendChild(elInput);
- root.appendChild(elForm);
- var isBuggy = elForm.elements
- ? (typeof elForm.elements.test == "undefined")
- : null;
- root.removeChild(elForm);
- elForm = elInput = null;
- return isBuggy;
+ var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){
+ try {
+ var el = document.createElement('<input name="x">');
+ return el.tagName.toLowerCase() === 'input' && el.name === 'x';
+ }
+ catch(err) {
+ return false;
+ }
})();
var element = global.Element;
+
global.Element = function(tagName, attributes) {
attributes = attributes || { };
tagName = tagName.toLowerCase();
var cache = Element.cache;
- if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
+ if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) {
tagName = '<' + tagName + ' name="' + attributes.name + '">';
delete attributes.name;
return Element.writeAttribute(document.createElement(tagName), attributes);
@@ -1780,12 +1874,23 @@ if (!Node.ELEMENT_NODE) {
if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
};
+
Object.extend(global.Element, element || { });
if (element) global.Element.prototype = element.prototype;
+
})(this);
-Element.cache = { };
Element.idCounter = 1;
+Element.cache = { };
+
+function purgeElement(element) {
+ var uid = element._prototypeUID;
+ if (uid) {
+ Element.stopObserving(element);
+ element._prototypeUID = void 0;
+ delete Element.Storage[uid];
+ }
+}
Element.Methods = {
visible: function(element) {
@@ -1798,7 +1903,6 @@ Element.Methods = {
return element;
},
-
hide: function(element) {
element = $(element);
element.style.display = 'none';
@@ -1861,6 +1965,10 @@ Element.Methods = {
function update(element, content) {
element = $(element);
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+ while (i--) purgeElement(descendants[i]);
+
if (content && content.toElement)
content = content.toElement();
@@ -1967,19 +2075,26 @@ Element.Methods = {
element = $(element);
var result = '<' + element.tagName.toLowerCase();
$H({'id': 'id', 'className': 'class'}).each(function(pair) {
- var property = pair.first(), attribute = pair.last();
- var value = (element[property] || '').toString();
+ var property = pair.first(),
+ attribute = pair.last(),
+ value = (element[property] || '').toString();
if (value) result += ' ' + attribute + '=' + value.inspect(true);
});
return result + '>';
},
- recursivelyCollect: function(element, property) {
+ recursivelyCollect: function(element, property, maximumLength) {
element = $(element);
+ maximumLength = maximumLength || -1;
var elements = [];
- while (element = element[property])
+
+ while (element = element[property]) {
if (element.nodeType == 1)
elements.push(Element.extend(element));
+ if (elements.length == maximumLength)
+ break;
+ }
+
return elements;
},
@@ -1998,13 +2113,17 @@ Element.Methods = {
},
immediateDescendants: function(element) {
- if (!(element = $(element).firstChild)) return [];
- while (element && element.nodeType != 1) element = element.nextSibling;
- if (element) return [element].concat($(element).nextSiblings());
- return [];
+ var results = [], child = $(element).firstChild;
+ while (child) {
+ if (child.nodeType === 1) {
+ results.push(Element.extend(child));
+ }
+ child = child.nextSibling;
+ }
+ return results;
},
- previousSiblings: function(element) {
+ previousSiblings: function(element, maximumLength) {
return Element.recursivelyCollect(element, 'previousSibling');
},
@@ -2019,9 +2138,10 @@ Element.Methods = {
},
match: function(element, selector) {
+ element = $(element);
if (Object.isString(selector))
- selector = new Selector(selector);
- return selector.match($(element));
+ return Prototype.Selector.match(element, selector);
+ return selector.match(element);
},
up: function(element, expression, index) {
@@ -2029,7 +2149,7 @@ Element.Methods = {
if (arguments.length == 1) return $(element.parentNode);
var ancestors = Element.ancestors(element);
return Object.isNumber(expression) ? ancestors[expression] :
- Selector.findElement(ancestors, expression, index);
+ Prototype.Selector.find(ancestors, expression, index);
},
down: function(element, expression, index) {
@@ -2041,29 +2161,40 @@ Element.Methods = {
previous: function(element, expression, index) {
element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
- var previousSiblings = Element.previousSiblings(element);
- return Object.isNumber(expression) ? previousSiblings[expression] :
- Selector.findElement(previousSiblings, expression, index);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.previousSiblings(), expression, index);
+ } else {
+ return element.recursivelyCollect("previousSibling", index + 1)[index];
+ }
},
next: function(element, expression, index) {
element = $(element);
- if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
- var nextSiblings = Element.nextSiblings(element);
- return Object.isNumber(expression) ? nextSiblings[expression] :
- Selector.findElement(nextSiblings, expression, index);
+ if (Object.isNumber(expression)) index = expression, expression = false;
+ if (!Object.isNumber(index)) index = 0;
+
+ if (expression) {
+ return Prototype.Selector.find(element.nextSiblings(), expression, index);
+ } else {
+ var maximumLength = Object.isNumber(index) ? index + 1 : 1;
+ return element.recursivelyCollect("nextSibling", index + 1)[index];
+ }
},
select: function(element) {
- var args = Array.prototype.slice.call(arguments, 1);
- return Selector.findChildElements(element, args);
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element);
},
adjacent: function(element) {
- var args = Array.prototype.slice.call(arguments, 1);
- return Selector.findChildElements(element.parentNode, args).without(element);
+ element = $(element);
+ var expressions = Array.prototype.slice.call(arguments, 1).join(', ');
+ return Prototype.Selector.select(expressions, element.parentNode).without(element);
},
identify: function(element) {
@@ -2227,28 +2358,6 @@ Element.Methods = {
return element;
},
- getDimensions: function(element) {
- element = $(element);
- var display = Element.getStyle(element, 'display');
- if (display != 'none' && display != null) // Safari bug
- return {width: element.offsetWidth, height: element.offsetHeight};
-
- var els = element.style;
- var originalVisibility = els.visibility;
- var originalPosition = els.position;
- var originalDisplay = els.display;
- els.visibility = 'hidden';
- if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
- els.position = 'absolute';
- els.display = 'block';
- var originalWidth = element.clientWidth;
- var originalHeight = element.clientHeight;
- els.display = originalDisplay;
- els.position = originalPosition;
- els.visibility = originalVisibility;
- return {width: originalWidth, height: originalHeight};
- },
-
makePositioned: function(element) {
element = $(element);
var pos = Element.getStyle(element, 'position');
@@ -2295,11 +2404,13 @@ Element.Methods = {
cumulativeOffset: function(element) {
var valueT = 0, valueL = 0;
- do {
- valueT += element.offsetTop || 0;
- valueL += element.offsetLeft || 0;
- element = element.offsetParent;
- } while (element);
+ if (element.parentNode) {
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ }
return Element._returnOffset(valueL, valueT);
},
@@ -2322,11 +2433,11 @@ Element.Methods = {
element = $(element);
if (Element.getStyle(element, 'position') == 'absolute') return element;
- var offsets = Element.positionedOffset(element);
- var top = offsets[1];
- var left = offsets[0];
- var width = element.clientWidth;
- var height = element.clientHeight;
+ var offsets = Element.positionedOffset(element),
+ top = offsets[1],
+ left = offsets[0],
+ width = element.clientWidth,
+ height = element.clientHeight;
element._originalLeft = left - parseFloat(element.style.left || 0);
element._originalTop = top - parseFloat(element.style.top || 0);
@@ -2346,8 +2457,8 @@ Element.Methods = {
if (Element.getStyle(element, 'position') == 'relative') return element;
element.style.position = 'relative';
- var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
- var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0),
+ left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
element.style.top = top + 'px';
element.style.left = left + 'px';
@@ -2378,9 +2489,10 @@ Element.Methods = {
},
viewportOffset: function(forElement) {
- var valueT = 0, valueL = 0;
+ var valueT = 0,
+ valueL = 0,
+ element = forElement;
- var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
@@ -2412,11 +2524,10 @@ Element.Methods = {
}, arguments[2] || { });
source = $(source);
- var p = Element.viewportOffset(source);
+ var p = Element.viewportOffset(source), delta = [0, 0], parent = null;
element = $(element);
- var delta = [0, 0];
- var parent = null;
+
if (Element.getStyle(element, 'position') == 'absolute') {
parent = Element.getOffsetParent(element);
delta = Element.viewportOffset(parent);
@@ -2495,8 +2606,7 @@ else if (Prototype.Browser.IE) {
Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
function(proceed, element) {
element = $(element);
- try { element.offsetParent }
- catch(e) { return $(document.body) }
+ if (!element.parentNode) return $(document.body);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
element.setStyle({ position: 'relative' });
@@ -2510,8 +2620,7 @@ else if (Prototype.Browser.IE) {
Element.Methods[method] = Element.Methods[method].wrap(
function(proceed, element) {
element = $(element);
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
+ if (!element.parentNode) return Element._returnOffset(0, 0);
var position = element.getStyle('position');
if (position !== 'static') return proceed(element);
var offsetParent = element.getOffsetParent();
@@ -2525,14 +2634,6 @@ else if (Prototype.Browser.IE) {
);
});
- Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
- function(proceed, element) {
- try { element.offsetParent }
- catch(e) { return Element._returnOffset(0,0) }
- return proceed(element);
- }
- );
-
Element.Methods.getStyle = function(element, style) {
element = $(element);
style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
@@ -2576,10 +2677,9 @@ else if (Prototype.Browser.IE) {
Element._attributeTranslations = (function(){
- var classProp = 'className';
- var forProp = 'for';
-
- var el = document.createElement('div');
+ var classProp = 'className',
+ forProp = 'for',
+ el = document.createElement('div');
el.setAttribute(classProp, 'x');
@@ -2622,10 +2722,9 @@ else if (Prototype.Browser.IE) {
},
_getEv: (function(){
- var el = document.createElement('div');
+ var el = document.createElement('div'), f;
el.onclick = Prototype.emptyFunction;
var value = el.getAttribute('onclick');
- var f;
if (String(value).indexOf('{') > -1) {
f = function(element, attribute) {
@@ -2753,7 +2852,7 @@ else if (Prototype.Browser.WebKit) {
(value < 0.00001) ? 0 : value;
if (value == 1)
- if(element.tagName.toUpperCase() == 'IMG' && element.width) {
+ if (element.tagName.toUpperCase() == 'IMG' && element.width) {
element.width++; element.width--;
} else try {
var n = document.createTextNode(' ');
@@ -2793,8 +2892,8 @@ if ('outerHTML' in document.documentElement) {
var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
if (Element._insertionTranslations.tags[tagName]) {
- var nextSibling = element.next();
- var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+ var nextSibling = element.next(),
+ fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
parent.removeChild(element);
if (nextSibling)
fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
@@ -2816,11 +2915,17 @@ Element._returnOffset = function(l, t) {
};
Element._getContentFromAnonymousElement = function(tagName, html) {
- var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+ var div = new Element('div'),
+ t = Element._insertionTranslations.tags[tagName];
if (t) {
div.innerHTML = t[0] + html + t[1];
- t[2].times(function() { div = div.firstChild });
- } else div.innerHTML = html;
+ for (var i = t[2]; i--; ) {
+ div = div.firstChild;
+ }
+ }
+ else {
+ div.innerHTML = html;
+ }
return $A(div.childNodes);
};
@@ -2877,7 +2982,7 @@ Object.extend(Element, Element.Methods);
div = null;
-})(document.createElement('div'))
+})(document.createElement('div'));
Element.extend = (function() {
@@ -2885,8 +2990,8 @@ Element.extend = (function() {
if (typeof window.Element != 'undefined') {
var proto = window.Element.prototype;
if (proto) {
- var id = '_' + (Math.random()+'').slice(2);
- var el = document.createElement(tagName);
+ var id = '_' + (Math.random()+'').slice(2),
+ el = document.createElement(tagName);
proto[id] = 'x';
var isBuggy = (el[id] !== 'x');
delete proto[id];
@@ -2953,10 +3058,14 @@ Element.extend = (function() {
return extend;
})();
-Element.hasAttribute = function(element, attribute) {
- if (element.hasAttribute) return element.hasAttribute(attribute);
- return Element.Methods.Simulated.hasAttribute(element, attribute);
-};
+if (document.documentElement.hasAttribute) {
+ Element.hasAttribute = function(element, attribute) {
+ return element.hasAttribute(attribute);
+ };
+}
+else {
+ Element.hasAttribute = Element.Methods.Simulated.hasAttribute;
+}
Element.addMethods = function(methods) {
var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
@@ -3020,8 +3129,9 @@ Element.addMethods = function(methods) {
klass = 'HTML' + tagName.capitalize() + 'Element';
if (window[klass]) return window[klass];
- var element = document.createElement(tagName);
- var proto = element['__proto__'] || element.constructor.prototype;
+ var element = document.createElement(tagName),
+ proto = element['__proto__'] || element.constructor.prototype;
+
element = null;
return proto;
}
@@ -3104,8 +3214,8 @@ Element.addMethods({
uid = 0;
} else {
if (typeof element._prototypeUID === "undefined")
- element._prototypeUID = [Element.Storage.UID++];
- uid = element._prototypeUID[0];
+ element._prototypeUID = Element.Storage.UID++;
+ uid = element._prototypeUID;
}
if (!Element.Storage[uid])
@@ -3150,770 +3260,1698 @@ Element.addMethods({
}
}
return Element.extend(clone);
+ },
+
+ purge: function(element) {
+ if (!(element = $(element))) return;
+ purgeElement(element);
+
+ var descendants = element.getElementsByTagName('*'),
+ i = descendants.length;
+
+ while (i--) purgeElement(descendants[i]);
+
+ return null;
}
});
-/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
- * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
- * license. Please see http://www.yui-ext.com/ for more information. */
-
-var Selector = Class.create({
- initialize: function(expression) {
- this.expression = expression.strip();
-
- if (this.shouldUseSelectorsAPI()) {
- this.mode = 'selectorsAPI';
- } else if (this.shouldUseXPath()) {
- this.mode = 'xpath';
- this.compileXPathMatcher();
- } else {
- this.mode = "normal";
- this.compileMatcher();
- }
- },
+(function() {
- shouldUseXPath: (function() {
+ function toDecimal(pctString) {
+ var match = pctString.match(/^(\d+)%?$/i);
+ if (!match) return null;
+ return (Number(match[1]) / 100);
+ }
- var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
- var isBuggy = false;
- if (document.evaluate && window.XPathResult) {
- var el = document.createElement('div');
- el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
+ function getPixelValue(value, property) {
+ if (Object.isElement(value)) {
+ element = value;
+ value = element.getStyle(property);
+ }
+ if (value === null) {
+ return null;
+ }
- var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
- "//*[local-name()='li' or local-name()='LI']";
+ if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
+ return window.parseFloat(value);
+ }
- var result = document.evaluate(xpath, el, null,
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (/\d/.test(value) && element.runtimeStyle) {
+ var style = element.style.left, rStyle = element.runtimeStyle.left;
+ element.runtimeStyle.left = element.currentStyle.left;
+ element.style.left = value || 0;
+ value = element.style.pixelLeft;
+ element.style.left = style;
+ element.runtimeStyle.left = rStyle;
- isBuggy = (result.snapshotLength !== 2);
- el = null;
+ return value;
+ }
+
+ if (value.include('%')) {
+ var decimal = toDecimal(value);
+ var whole;
+ if (property.include('left') || property.include('right') ||
+ property.include('width')) {
+ whole = $(element.parentNode).measure('width');
+ } else if (property.include('top') || property.include('bottom') ||
+ property.include('height')) {
+ whole = $(element.parentNode).measure('height');
}
- return isBuggy;
- })();
- return function() {
- if (!Prototype.BrowserFeatures.XPath) return false;
+ return whole * decimal;
+ }
- var e = this.expression;
+ return 0;
+ }
- if (Prototype.Browser.WebKit &&
- (e.include("-of-type") || e.include(":empty")))
- return false;
+ function toCSSPixels(number) {
+ if (Object.isString(number) && number.endsWith('px')) {
+ return number;
+ }
+ return number + 'px';
+ }
- if ((/(\[[\w-]*?:|:checked)/).test(e))
+ function isDisplayed(element) {
+ var originalElement = element;
+ while (element && element.parentNode) {
+ var display = element.getStyle('display');
+ if (display === 'none') {
return false;
+ }
+ element = $(element.parentNode);
+ }
+ return true;
+ }
- if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
+ var hasLayout = Prototype.K;
+ if ('currentStyle' in document.documentElement) {
+ hasLayout = function(element) {
+ if (!element.currentStyle.hasLayout) {
+ element.style.zoom = 1;
+ }
+ return element;
+ };
+ }
- return true;
- }
+ function cssNameFor(key) {
+ if (key.include('border')) key = key + '-width';
+ return key.camelize();
+ }
- })(),
+ Element.Layout = Class.create(Hash, {
+ initialize: function($super, element, preCompute) {
+ $super();
+ this.element = $(element);
- shouldUseSelectorsAPI: function() {
- if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
+ Element.Layout.PROPERTIES.each( function(property) {
+ this._set(property, null);
+ }, this);
- if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
+ if (preCompute) {
+ this._preComputing = true;
+ this._begin();
+ Element.Layout.PROPERTIES.each( this._compute, this );
+ this._end();
+ this._preComputing = false;
+ }
+ },
- if (!Selector._div) Selector._div = new Element('div');
+ _set: function(property, value) {
+ return Hash.prototype.set.call(this, property, value);
+ },
- try {
- Selector._div.querySelector(this.expression);
- } catch(e) {
- return false;
- }
+ set: function(property, value) {
+ throw "Properties of Element.Layout are read-only.";
+ },
- return true;
- },
+ get: function($super, property) {
+ var value = $super(property);
+ return value === null ? this._compute(property) : value;
+ },
- compileMatcher: function() {
- var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
- c = Selector.criteria, le, p, m, len = ps.length, name;
+ _begin: function() {
+ if (this._prepared) return;
- if (Selector._cache[e]) {
- this.matcher = Selector._cache[e];
- return;
- }
+ var element = this.element;
+ if (isDisplayed(element)) {
+ this._prepared = true;
+ return;
+ }
- this.matcher = ["this.matcher = function(root) {",
- "var r = root, h = Selector.handlers, c = false, n;"];
+ var originalStyles = {
+ position: element.style.position || '',
+ width: element.style.width || '',
+ visibility: element.style.visibility || '',
+ display: element.style.display || ''
+ };
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- p = ps[i].re;
- name = ps[i].name;
- if (m = e.match(p)) {
- this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
- new Template(c[name]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
+ element.store('prototype_original_styles', originalStyles);
+
+ var position = element.getStyle('position'),
+ width = element.getStyle('width');
+
+ element.setStyle({
+ position: 'absolute',
+ visibility: 'hidden',
+ display: 'block'
+ });
+
+ var positionedWidth = element.getStyle('width');
+
+ var newWidth;
+ if (width && (positionedWidth === width)) {
+ newWidth = getPixelValue(width);
+ } else if (width && (position === 'absolute' || position === 'fixed')) {
+ newWidth = getPixelValue(width);
+ } else {
+ var parent = element.parentNode, pLayout = $(parent).getLayout();
+
+ newWidth = pLayout.get('width') -
+ this.get('margin-left') -
+ this.get('border-left') -
+ this.get('padding-left') -
+ this.get('padding-right') -
+ this.get('border-right') -
+ this.get('margin-right');
}
- }
- this.matcher.push("return h.unique(n);\n}");
- eval(this.matcher.join('\n'));
- Selector._cache[this.expression] = this.matcher;
- },
+ element.setStyle({ width: newWidth + 'px' });
- compileXPathMatcher: function() {
- var e = this.expression, ps = Selector.patterns,
- x = Selector.xpath, le, m, len = ps.length, name;
+ this._prepared = true;
+ },
- if (Selector._cache[e]) {
- this.xpath = Selector._cache[e]; return;
- }
+ _end: function() {
+ var element = this.element;
+ var originalStyles = element.retrieve('prototype_original_styles');
+ element.store('prototype_original_styles', null);
+ element.setStyle(originalStyles);
+ this._prepared = false;
+ },
- this.matcher = ['.//*'];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- name = ps[i].name;
- if (m = e.match(ps[i].re)) {
- this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
- new Template(x[name]).evaluate(m));
- e = e.replace(m[0], '');
- break;
- }
+ _compute: function(property) {
+ var COMPUTATIONS = Element.Layout.COMPUTATIONS;
+ if (!(property in COMPUTATIONS)) {
+ throw "Property not found.";
}
- }
+ return this._set(property, COMPUTATIONS[property].call(this, this.element));
+ },
- this.xpath = this.matcher.join('');
- Selector._cache[this.expression] = this.xpath;
- },
+ toObject: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var obj = {};
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ var value = this.get(key);
+ if (value != null) obj[key] = value;
+ }, this);
+ return obj;
+ },
- findElements: function(root) {
- root = root || document;
- var e = this.expression, results;
+ toHash: function() {
+ var obj = this.toObject.apply(this, arguments);
+ return new Hash(obj);
+ },
- switch (this.mode) {
- case 'selectorsAPI':
- if (root !== document) {
- var oldId = root.id, id = $(root).identify();
- id = id.replace(/([\.:])/g, "\\$1");
- e = "#" + id + " " + e;
- }
+ toCSS: function() {
+ var args = $A(arguments);
+ var keys = (args.length === 0) ? Element.Layout.PROPERTIES :
+ args.join(' ').split(' ');
+ var css = {};
- results = $A(root.querySelectorAll(e)).map(Element.extend);
- root.id = oldId;
+ keys.each( function(key) {
+ if (!Element.Layout.PROPERTIES.include(key)) return;
+ if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
- return results;
- case 'xpath':
- return document._getElementsByXPath(this.xpath, root);
- default:
- return this.matcher(root);
+ var value = this.get(key);
+ if (value != null) css[cssNameFor(key)] = value + 'px';
+ }, this);
+ return css;
+ },
+
+ inspect: function() {
+ return "#<Element.Layout>";
}
- },
+ });
- match: function(element) {
- this.tokens = [];
+ Object.extend(Element.Layout, {
+ PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
- var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
- var le, p, m, len = ps.length, name;
+ COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
- while (e && le !== e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- p = ps[i].re;
- name = ps[i].name;
- if (m = e.match(p)) {
- if (as[name]) {
- this.tokens.push([name, Object.clone(m)]);
- e = e.replace(m[0], '');
- } else {
- return this.findElements(document).include(element);
- }
- }
- }
- }
+ COMPUTATIONS: {
+ 'height': function(element) {
+ if (!this._preComputing) this._begin();
- var match = true, name, matches;
- for (var i = 0, token; token = this.tokens[i]; i++) {
- name = token[0], matches = token[1];
- if (!Selector.assertions[name](element, matches)) {
- match = false; break;
- }
- }
+ var bHeight = this.get('border-box-height');
+ if (bHeight <= 0) return 0;
- return match;
- },
+ var bTop = this.get('border-top'),
+ bBottom = this.get('border-bottom');
- toString: function() {
- return this.expression;
- },
+ var pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
- inspect: function() {
- return "#<Selector:" + this.expression.inspect() + ">";
- }
-});
+ if (!this._preComputing) this._end();
-if (Prototype.BrowserFeatures.SelectorsAPI &&
- document.compatMode === 'BackCompat') {
- Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
- var div = document.createElement('div'),
- span = document.createElement('span');
-
- div.id = "prototype_test_id";
- span.className = 'Test';
- div.appendChild(span);
- var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
- div = span = null;
- return isIgnored;
- })();
-}
+ return bHeight - bTop - bBottom - pTop - pBottom;
+ },
-Object.extend(Selector, {
- _cache: { },
-
- xpath: {
- descendant: "//*",
- child: "/*",
- adjacent: "/following-sibling::*[1]",
- laterSibling: '/following-sibling::*',
- tagName: function(m) {
- if (m[1] == '*') return '';
- return "[local-name()='" + m[1].toLowerCase() +
- "' or local-name()='" + m[1].toUpperCase() + "']";
- },
- className: "[contains(concat(' ', @class, ' '), ' #{1} ')]",
- id: "[@id='#{1}']",
- attrPresence: function(m) {
- m[1] = m[1].toLowerCase();
- return new Template("[@#{1}]").evaluate(m);
- },
- attr: function(m) {
- m[1] = m[1].toLowerCase();
- m[3] = m[5] || m[6];
- return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
- },
- pseudo: function(m) {
- var h = Selector.xpath.pseudos[m[1]];
- if (!h) return '';
- if (Object.isFunction(h)) return h(m);
- return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
- },
- operators: {
- '=': "[@#{1}='#{3}']",
- '!=': "[@#{1}!='#{3}']",
- '^=': "[starts-with(@#{1}, '#{3}')]",
- '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
- '*=': "[contains(@#{1}, '#{3}')]",
- '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
- '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
- },
- pseudos: {
- 'first-child': '[not(preceding-sibling::*)]',
- 'last-child': '[not(following-sibling::*)]',
- 'only-child': '[not(preceding-sibling::* or following-sibling::*)]',
- 'empty': "[count(*) = 0 and (count(text()) = 0)]",
- 'checked': "[@checked]",
- 'disabled': "[(@disabled) and (@type!='hidden')]",
- 'enabled': "[not(@disabled) and (@type!='hidden')]",
- 'not': function(m) {
- var e = m[6], p = Selector.patterns,
- x = Selector.xpath, le, v, len = p.length, name;
-
- var exclusion = [];
- while (e && le != e && (/\S/).test(e)) {
- le = e;
- for (var i = 0; i<len; i++) {
- name = p[i].name
- if (m = e.match(p[i].re)) {
- v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
- exclusion.push("(" + v.substring(1, v.length - 1) + ")");
- e = e.replace(m[0], '');
- break;
- }
- }
- }
- return "[not(" + exclusion.join(" and ") + ")]";
+ 'width': function(element) {
+ if (!this._preComputing) this._begin();
+
+ var bWidth = this.get('border-box-width');
+ if (bWidth <= 0) return 0;
+
+ var bLeft = this.get('border-left'),
+ bRight = this.get('border-right');
+
+ var pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ if (!this._preComputing) this._end();
+
+ return bWidth - bLeft - bRight - pLeft - pRight;
+ },
+
+ 'padding-box-height': function(element) {
+ var height = this.get('height'),
+ pTop = this.get('padding-top'),
+ pBottom = this.get('padding-bottom');
+
+ return height + pTop + pBottom;
+ },
+
+ 'padding-box-width': function(element) {
+ var width = this.get('width'),
+ pLeft = this.get('padding-left'),
+ pRight = this.get('padding-right');
+
+ return width + pLeft + pRight;
},
- 'nth-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+
+ 'border-box-height': function(element) {
+ return element.offsetHeight;
},
- 'nth-last-child': function(m) {
- return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+
+ 'border-box-width': function(element) {
+ return element.offsetWidth;
},
- 'nth-of-type': function(m) {
- return Selector.xpath.pseudos.nth("position() ", m);
+
+ 'margin-box-height': function(element) {
+ var bHeight = this.get('border-box-height'),
+ mTop = this.get('margin-top'),
+ mBottom = this.get('margin-bottom');
+
+ if (bHeight <= 0) return 0;
+
+ return bHeight + mTop + mBottom;
},
- 'nth-last-of-type': function(m) {
- return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+
+ 'margin-box-width': function(element) {
+ var bWidth = this.get('border-box-width'),
+ mLeft = this.get('margin-left'),
+ mRight = this.get('margin-right');
+
+ if (bWidth <= 0) return 0;
+
+ return bWidth + mLeft + mRight;
},
- 'first-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+
+ 'top': function(element) {
+ var offset = element.positionedOffset();
+ return offset.top;
},
- 'last-of-type': function(m) {
- m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+
+ 'bottom': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pHeight = parent.measure('height');
+
+ var mHeight = this.get('border-box-height');
+
+ return pHeight - mHeight - offset.top;
},
- 'only-of-type': function(m) {
- var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+
+ 'left': function(element) {
+ var offset = element.positionedOffset();
+ return offset.left;
},
- nth: function(fragment, m) {
- var mm, formula = m[6], predicate;
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- if (mm = formula.match(/^(\d+)$/)) // digit only
- return '[' + fragment + "= " + mm[1] + ']';
- if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (mm[1] == "-") mm[1] = -1;
- var a = mm[1] ? Number(mm[1]) : 1;
- var b = mm[2] ? Number(mm[2]) : 0;
- predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
- "((#{fragment} - #{b}) div #{a} >= 0)]";
- return new Template(predicate).evaluate({
- fragment: fragment, a: a, b: b });
- }
+
+ 'right': function(element) {
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pWidth = parent.measure('width');
+
+ var mWidth = this.get('border-box-width');
+
+ return pWidth - mWidth - offset.left;
+ },
+
+ 'padding-top': function(element) {
+ return getPixelValue(element, 'paddingTop');
+ },
+
+ 'padding-bottom': function(element) {
+ return getPixelValue(element, 'paddingBottom');
+ },
+
+ 'padding-left': function(element) {
+ return getPixelValue(element, 'paddingLeft');
+ },
+
+ 'padding-right': function(element) {
+ return getPixelValue(element, 'paddingRight');
+ },
+
+ 'border-top': function(element) {
+ return Object.isNumber(element.clientTop) ? element.clientTop :
+ getPixelValue(element, 'borderTopWidth');
+ },
+
+ 'border-bottom': function(element) {
+ return Object.isNumber(element.clientBottom) ? element.clientBottom :
+ getPixelValue(element, 'borderBottomWidth');
+ },
+
+ 'border-left': function(element) {
+ return Object.isNumber(element.clientLeft) ? element.clientLeft :
+ getPixelValue(element, 'borderLeftWidth');
+ },
+
+ 'border-right': function(element) {
+ return Object.isNumber(element.clientRight) ? element.clientRight :
+ getPixelValue(element, 'borderRightWidth');
+ },
+
+ 'margin-top': function(element) {
+ return getPixelValue(element, 'marginTop');
+ },
+
+ 'margin-bottom': function(element) {
+ return getPixelValue(element, 'marginBottom');
+ },
+
+ 'margin-left': function(element) {
+ return getPixelValue(element, 'marginLeft');
+ },
+
+ 'margin-right': function(element) {
+ return getPixelValue(element, 'marginRight');
}
}
- },
+ });
- criteria: {
- tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
- className: 'n = h.className(n, r, "#{1}", c); c = false;',
- id: 'n = h.id(n, r, "#{1}", c); c = false;',
- attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
- attr: function(m) {
- m[3] = (m[5] || m[6]);
- return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
- },
- pseudo: function(m) {
- if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
- return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
- },
- descendant: 'c = "descendant";',
- child: 'c = "child";',
- adjacent: 'c = "adjacent";',
- laterSibling: 'c = "laterSibling";'
- },
-
- patterns: [
- { name: 'laterSibling', re: /^\s*~\s*/ },
- { name: 'child', re: /^\s*>\s*/ },
- { name: 'adjacent', re: /^\s*\+\s*/ },
- { name: 'descendant', re: /^\s/ },
-
- { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
- { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ },
- { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ },
- { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
- { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
- { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
- ],
-
- assertions: {
- tagName: function(element, matches) {
- return matches[1].toUpperCase() == element.tagName.toUpperCase();
+ if ('getBoundingClientRect' in document.documentElement) {
+ Object.extend(Element.Layout.COMPUTATIONS, {
+ 'right': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.right - rect.right).round();
+ },
+
+ 'bottom': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.bottom - rect.bottom).round();
+ }
+ });
+ }
+
+ Element.Offset = Class.create({
+ initialize: function(left, top) {
+ this.left = left.round();
+ this.top = top.round();
+
+ this[0] = this.left;
+ this[1] = this.top;
},
- className: function(element, matches) {
- return Element.hasClassName(element, matches[1]);
+ relativeTo: function(offset) {
+ return new Element.Offset(
+ this.left - offset.left,
+ this.top - offset.top
+ );
},
- id: function(element, matches) {
- return element.id === matches[1];
+ inspect: function() {
+ return "#<Element.Offset left: #{left} top: #{top}>".interpolate(this);
},
- attrPresence: function(element, matches) {
- return Element.hasAttribute(element, matches[1]);
+ toString: function() {
+ return "[#{left}, #{top}]".interpolate(this);
},
- attr: function(element, matches) {
- var nodeValue = Element.readAttribute(element, matches[1]);
- return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+ toArray: function() {
+ return [this.left, this.top];
}
- },
+ });
- handlers: {
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- a.push(node);
- return a;
- },
+ function getLayout(element, preCompute) {
+ return new Element.Layout(element, preCompute);
+ }
- mark: function(nodes) {
- var _true = Prototype.emptyFunction;
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = _true;
- return nodes;
- },
+ function measure(element, property) {
+ return $(element).getLayout().get(property);
+ }
- unmark: (function(){
+ function getDimensions(element) {
+ var layout = $(element).getLayout();
+ return {
+ width: layout.get('width'),
+ height: layout.get('height')
+ };
+ }
- var PROPERTIES_ATTRIBUTES_MAP = (function(){
- var el = document.createElement('div'),
- isBuggy = false,
- propName = '_countedByPrototype',
- value = 'x'
- el[propName] = value;
- isBuggy = (el.getAttribute(propName) === value);
- el = null;
- return isBuggy;
- })();
-
- return PROPERTIES_ATTRIBUTES_MAP ?
- function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node.removeAttribute('_countedByPrototype');
- return nodes;
- } :
- function(nodes) {
- for (var i = 0, node; node = nodes[i]; i++)
- node._countedByPrototype = void 0;
- return nodes;
- }
- })(),
+ function getOffsetParent(element) {
+ if (isDetached(element)) return $(document.body);
- index: function(parentNode, reverse, ofType) {
- parentNode._countedByPrototype = Prototype.emptyFunction;
- if (reverse) {
- for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
- var node = nodes[i];
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
- }
- } else {
- for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
- if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+ var isInline = (Element.getStyle(element, 'display') === 'inline');
+ if (!isInline && element.offsetParent) return $(element.offsetParent);
+ if (element === document.body) return $(element);
+
+ while ((element = element.parentNode) && element !== document.body) {
+ if (Element.getStyle(element, 'position') !== 'static') {
+ return (element.nodeName === 'HTML') ? $(document.body) : $(element);
}
- },
+ }
- unique: function(nodes) {
- if (nodes.length == 0) return nodes;
- var results = [], n;
- for (var i = 0, l = nodes.length; i < l; i++)
- if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
- n._countedByPrototype = Prototype.emptyFunction;
- results.push(Element.extend(n));
- }
- return Selector.handlers.unmark(results);
- },
+ return $(document.body);
+ }
- descendant: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName('*'));
- return results;
- },
- child: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- for (var j = 0, child; child = node.childNodes[j]; j++)
- if (child.nodeType == 1 && child.tagName != '!') results.push(child);
- }
- return results;
- },
+ function cumulativeOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
+
+ function positionedOffset(element) {
+ var layout = element.getLayout();
- adjacent: function(nodes) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- var next = this.nextElementSibling(node);
- if (next) results.push(next);
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ if (isBody(element)) break;
+ var p = Element.getStyle(element, 'position');
+ if (p !== 'static') break;
}
- return results;
- },
+ } while (element);
- laterSibling: function(nodes) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- h.concat(results, Element.nextSiblings(node));
- return results;
- },
+ valueL -= layout.get('margin-top');
+ valueT -= layout.get('margin-left');
- nextElementSibling: function(node) {
- while (node = node.nextSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
+ return new Element.Offset(valueL, valueT);
+ }
- previousElementSibling: function(node) {
- while (node = node.previousSibling)
- if (node.nodeType == 1) return node;
- return null;
- },
+ function cumulativeScrollOffset(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.scrollTop || 0;
+ valueL += element.scrollLeft || 0;
+ element = element.parentNode;
+ } while (element);
+ return new Element.Offset(valueL, valueT);
+ }
- tagName: function(nodes, root, tagName, combinator) {
- var uTagName = tagName.toUpperCase();
- var results = [], h = Selector.handlers;
- if (nodes) {
- if (combinator) {
- if (combinator == "descendant") {
- for (var i = 0, node; node = nodes[i]; i++)
- h.concat(results, node.getElementsByTagName(tagName));
- return results;
- } else nodes = this[combinator](nodes);
- if (tagName == "*") return nodes;
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.tagName.toUpperCase() === uTagName) results.push(node);
- return results;
- } else return root.getElementsByTagName(tagName);
- },
+ function viewportOffset(forElement) {
+ var valueT = 0, valueL = 0, docBody = document.body;
- id: function(nodes, root, id, combinator) {
- var targetNode = $(id), h = Selector.handlers;
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ if (element.offsetParent == docBody &&
+ Element.getStyle(element, 'position') == 'absolute') break;
+ } while (element = element.offsetParent);
- if (root == document) {
- if (!targetNode) return [];
- if (!nodes) return [targetNode];
- } else {
- if (!root.sourceIndex || root.sourceIndex < 1) {
- var nodes = root.getElementsByTagName('*');
- for (var j = 0, node; node = nodes[j]; j++) {
- if (node.id === id) return [node];
- }
- }
+ element = forElement;
+ do {
+ if (element != docBody) {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
}
+ } while (element = element.parentNode);
+ return new Element.Offset(valueL, valueT);
+ }
- if (nodes) {
- if (combinator) {
- if (combinator == 'child') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (targetNode.parentNode == node) return [targetNode];
- } else if (combinator == 'descendant') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.descendantOf(targetNode, node)) return [targetNode];
- } else if (combinator == 'adjacent') {
- for (var i = 0, node; node = nodes[i]; i++)
- if (Selector.handlers.previousElementSibling(targetNode) == node)
- return [targetNode];
- } else nodes = h[combinator](nodes);
- }
- for (var i = 0, node; node = nodes[i]; i++)
- if (node == targetNode) return [targetNode];
- return [];
- }
- return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
- },
+ function absolutize(element) {
+ element = $(element);
- className: function(nodes, root, className, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- return Selector.handlers.byClassName(nodes, root, className);
- },
+ if (Element.getStyle(element, 'position') === 'absolute') {
+ return element;
+ }
- byClassName: function(nodes, root, className) {
- if (!nodes) nodes = Selector.handlers.descendant([root]);
- var needle = ' ' + className + ' ';
- for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
- nodeClassName = node.className;
- if (nodeClassName.length == 0) continue;
- if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
- results.push(node);
- }
- return results;
- },
+ var offsetParent = getOffsetParent(element);
+ var eOffset = element.viewportOffset(),
+ pOffset = offsetParent.viewportOffset();
- attrPresence: function(nodes, root, attr, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var results = [];
- for (var i = 0, node; node = nodes[i]; i++)
- if (Element.hasAttribute(node, attr)) results.push(node);
- return results;
- },
+ var offset = eOffset.relativeTo(pOffset);
+ var layout = element.getLayout();
- attr: function(nodes, root, attr, value, operator, combinator) {
- if (!nodes) nodes = root.getElementsByTagName("*");
- if (nodes && combinator) nodes = this[combinator](nodes);
- var handler = Selector.operators[operator], results = [];
- for (var i = 0, node; node = nodes[i]; i++) {
- var nodeValue = Element.readAttribute(node, attr);
- if (nodeValue === null) continue;
- if (handler(nodeValue, value)) results.push(node);
- }
- return results;
- },
+ element.store('prototype_absolutize_original_styles', {
+ left: element.getStyle('left'),
+ top: element.getStyle('top'),
+ width: element.getStyle('width'),
+ height: element.getStyle('height')
+ });
+
+ element.setStyle({
+ position: 'absolute',
+ top: offset.top + 'px',
+ left: offset.left + 'px',
+ width: layout.get('width') + 'px',
+ height: layout.get('height') + 'px'
+ });
+
+ return element;
+ }
- pseudo: function(nodes, name, value, root, combinator) {
- if (nodes && combinator) nodes = this[combinator](nodes);
- if (!nodes) nodes = root.getElementsByTagName("*");
- return Selector.pseudos[name](nodes, value, root);
+ function relativize(element) {
+ element = $(element);
+ if (Element.getStyle(element, 'position') === 'relative') {
+ return element;
}
- },
- pseudos: {
- 'first-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.previousElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'last-child': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (Selector.handlers.nextElementSibling(node)) continue;
- results.push(node);
- }
- return results;
- },
- 'only-child': function(nodes, value, root) {
- var h = Selector.handlers;
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
- results.push(node);
- return results;
- },
- 'nth-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root);
- },
- 'nth-last-child': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true);
- },
- 'nth-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, false, true);
- },
- 'nth-last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, formula, root, true, true);
- },
- 'first-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, false, true);
- },
- 'last-of-type': function(nodes, formula, root) {
- return Selector.pseudos.nth(nodes, "1", root, true, true);
- },
- 'only-of-type': function(nodes, formula, root) {
- var p = Selector.pseudos;
- return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
- },
+ var originalStyles =
+ element.retrieve('prototype_absolutize_original_styles');
- getIndices: function(a, b, total) {
- if (a == 0) return b > 0 ? [b] : [];
- return $R(1, total).inject([], function(memo, i) {
- if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
- return memo;
- });
- },
+ if (originalStyles) element.setStyle(originalStyles);
+ return element;
+ }
- nth: function(nodes, formula, root, reverse, ofType) {
- if (nodes.length == 0) return [];
- if (formula == 'even') formula = '2n+0';
- if (formula == 'odd') formula = '2n+1';
- var h = Selector.handlers, results = [], indexed = [], m;
- h.mark(nodes);
- for (var i = 0, node; node = nodes[i]; i++) {
- if (!node.parentNode._countedByPrototype) {
- h.index(node.parentNode, reverse, ofType);
- indexed.push(node.parentNode);
- }
- }
- if (formula.match(/^\d+$/)) { // just a number
- formula = Number(formula);
- for (var i = 0, node; node = nodes[i]; i++)
- if (node.nodeIndex == formula) results.push(node);
- } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
- if (m[1] == "-") m[1] = -1;
- var a = m[1] ? Number(m[1]) : 1;
- var b = m[2] ? Number(m[2]) : 0;
- var indices = Selector.pseudos.getIndices(a, b, nodes.length);
- for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
- for (var j = 0; j < l; j++)
- if (node.nodeIndex == indices[j]) results.push(node);
- }
- }
- h.unmark(nodes);
- h.unmark(indexed);
- return results;
- },
+ Element.addMethods({
+ getLayout: getLayout,
+ measure: measure,
+ getDimensions: getDimensions,
+ getOffsetParent: getOffsetParent,
+ cumulativeOffset: cumulativeOffset,
+ positionedOffset: positionedOffset,
+ cumulativeScrollOffset: cumulativeScrollOffset,
+ viewportOffset: viewportOffset,
+ absolutize: absolutize,
+ relativize: relativize
+ });
- 'empty': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++) {
- if (node.tagName == '!' || node.firstChild) continue;
- results.push(node);
- }
- return results;
- },
+ function isBody(element) {
+ return element.nodeName.toUpperCase() === 'BODY';
+ }
- 'not': function(nodes, selector, root) {
- var h = Selector.handlers, selectorType, m;
- var exclusions = new Selector(selector).findElements(root);
- h.mark(exclusions);
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node._countedByPrototype) results.push(node);
- h.unmark(exclusions);
- return results;
- },
+ function isDetached(element) {
+ return element !== document.body &&
+ !Element.descendantOf(element, document.body);
+ }
- 'enabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (!node.disabled && (!node.type || node.type !== 'hidden'))
- results.push(node);
- return results;
- },
+ if ('getBoundingClientRect' in document.documentElement) {
+ Element.addMethods({
+ viewportOffset: function(element) {
+ element = $(element);
+ if (isDetached(element)) return new Element.Offset(0, 0);
- 'disabled': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.disabled) results.push(node);
- return results;
- },
+ var rect = element.getBoundingClientRect(),
+ docEl = document.documentElement;
+ return new Element.Offset(rect.left - docEl.clientLeft,
+ rect.top - docEl.clientTop);
+ },
- 'checked': function(nodes, value, root) {
- for (var i = 0, results = [], node; node = nodes[i]; i++)
- if (node.checked) results.push(node);
- return results;
- }
- },
+ positionedOffset: function(element) {
+ element = $(element);
+ var parent = element.getOffsetParent();
+ if (isDetached(element)) return new Element.Offset(0, 0);
- operators: {
- '=': function(nv, v) { return nv == v; },
- '!=': function(nv, v) { return nv != v; },
- '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
- '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
- '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
- '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
- '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
- '-').include('-' + (v || "").toUpperCase() + '-'); }
- },
+ if (element.offsetParent &&
+ element.offsetParent.nodeName.toUpperCase() === 'HTML') {
+ return positionedOffset(element);
+ }
+
+ var eOffset = element.viewportOffset(),
+ pOffset = isBody(parent) ? viewportOffset(parent) :
+ parent.viewportOffset();
+ var retOffset = eOffset.relativeTo(pOffset);
- split: function(expression) {
- var expressions = [];
- expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
- expressions.push(m[1].strip());
+ var layout = element.getLayout();
+ var top = retOffset.top - layout.get('margin-top');
+ var left = retOffset.left - layout.get('margin-left');
+
+ return new Element.Offset(left, top);
+ }
});
- return expressions;
- },
+ }
+})();
+window.$$ = function() {
+ var expression = $A(arguments).join(', ');
+ return Prototype.Selector.select(expression, document);
+};
- matchElements: function(elements, expression) {
- var matches = $$(expression), h = Selector.handlers;
- h.mark(matches);
- for (var i = 0, results = [], element; element = elements[i]; i++)
- if (element._countedByPrototype) results.push(element);
- h.unmark(matches);
- return results;
- },
+Prototype.Selector = (function() {
+
+ function select() {
+ throw new Error('Method "Prototype.Selector.select" must be defined.');
+ }
+
+ function match() {
+ throw new Error('Method "Prototype.Selector.match" must be defined.');
+ }
- findElement: function(elements, expression, index) {
- if (Object.isNumber(expression)) {
- index = expression; expression = false;
+ function find(elements, expression, index) {
+ index = index || 0;
+ var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i;
+
+ for (i = 0; i < length; i++) {
+ if (match(elements[i], expression) && index == matchIndex++) {
+ return Element.extend(elements[i]);
+ }
}
- return Selector.matchElements(elements, expression || '*')[index || 0];
- },
+ }
- findChildElements: function(element, expressions) {
- expressions = Selector.split(expressions.join(','));
- var results = [], h = Selector.handlers;
- for (var i = 0, l = expressions.length, selector; i < l; i++) {
- selector = new Selector(expressions[i].strip());
- h.concat(results, selector.findElements(element));
+ function extendElements(elements) {
+ for (var i = 0, length = elements.length; i < length; i++) {
+ Element.extend(elements[i]);
}
- return (l > 1) ? h.unique(results) : results;
+ return elements;
}
+
+
+ var K = Prototype.K;
+
+ return {
+ select: select,
+ match: match,
+ find: find,
+ extendElements: (Element.extend === K) ? K : extendElements,
+ extendElement: Element.extend
+ };
+})();
+Prototype._original_property = window.Sizzle;
+/*!
+ * Sizzle CSS Selector Engine - v1.0
+ * Copyright 2009, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true;
+
+[0, 0].sort(function(){
+ baseHasDuplicate = false;
+ return 0;
});
-if (Prototype.Browser.IE) {
- Object.extend(Selector.handlers, {
- concat: function(a, b) {
- for (var i = 0, node; node = b[i]; i++)
- if (node.tagName !== "!") a.push(node);
- return a;
- }
- });
+var Sizzle = function(selector, context, results, seed) {
+ results = results || [];
+ var origContext = context = context || document;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+ soFar = selector;
+
+ while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context );
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] )
+ selector += parts.shift();
+
+ set = posProcess( selector, set );
+ }
+ }
+ } else {
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+ var ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+ }
+
+ if ( context ) {
+ var ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+ set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray(set);
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ var cur = parts.pop(), pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ throw "Syntax error, unrecognized expression: " + (cur || selector);
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+ } else if ( context && context.nodeType === 1 ) {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+ } else {
+ for ( var i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function(results){
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort(sortOrder);
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[i-1] ) {
+ results.splice(i--, 1);
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function(expr, set){
+ return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+ var set, match;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+ var type = Expr.order[i], match;
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ var left = match[1];
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace(/\\/g, "");
+ set = Expr.find[ type ]( match, context, isXML );
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = context.getElementsByTagName("*");
+ }
+
+ return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+ var old = expr, result = [], curLoop = set, match, anyFound,
+ isXMLFilter = set && set[0] && isXML(set[0]);
+
+ while ( expr && set.length ) {
+ for ( var type in Expr.filter ) {
+ if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+ var filter = Expr.filter[ type ], found, item;
+ anyFound = false;
+
+ if ( curLoop == result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ var pass = not ^ !!found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+ } else {
+ curLoop[i] = false;
+ }
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ if ( expr == old ) {
+ if ( anyFound == null ) {
+ throw "Syntax error, unrecognized expression: " + expr;
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+ },
+ leftMatch: {},
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+ attrHandle: {
+ href: function(elem){
+ return elem.getAttribute("href");
+ }
+ },
+ relative: {
+ "+": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !/\W/.test(part),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag && !isXML ) {
+ part = part.toUpperCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+ ">": function(checkSet, part, isXML){
+ var isPartStr = typeof part === "string";
+
+ if ( isPartStr && !/\W/.test(part) ) {
+ part = isXML ? part : part.toUpperCase();
+
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName === part ? parent : false;
+ }
+ }
+ } else {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+ "": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+ },
+ "~": function(checkSet, part, isXML){
+ var doneName = done++, checkFn = dirCheck;
+
+ if ( typeof part === "string" && !/\W/.test(part) ) {
+ var nodeCheck = part = isXML ? part : part.toUpperCase();
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+ }
+ },
+ find: {
+ ID: function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? [m] : [];
+ }
+ },
+ NAME: function(match, context, isXML){
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [], results = context.getElementsByName(match[1]);
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+ TAG: function(match, context){
+ return context.getElementsByTagName(match[1]);
+ }
+ },
+ preFilter: {
+ CLASS: function(match, curLoop, inplace, result, not, isXML){
+ match = " " + match[1].replace(/\\/g, "") + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+ if ( !inplace )
+ result.push( elem );
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+ ID: function(match){
+ return match[1].replace(/\\/g, "");
+ },
+ TAG: function(match, curLoop){
+ for ( var i = 0; curLoop[i] === false; i++ ){}
+ return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+ },
+ CHILD: function(match){
+ if ( match[1] == "nth" ) {
+ var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+ match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+
+ match[0] = done++;
+
+ return match;
+ },
+ ATTR: function(match, curLoop, inplace, result, not, isXML){
+ var name = match[1].replace(/\\/g, "");
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+ PSEUDO: function(match, curLoop, inplace, result, not){
+ if ( match[1] === "not" ) {
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+ return false;
+ }
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+ POS: function(match){
+ match.unshift( true );
+ return match;
+ }
+ },
+ filters: {
+ enabled: function(elem){
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+ disabled: function(elem){
+ return elem.disabled === true;
+ },
+ checked: function(elem){
+ return elem.checked === true;
+ },
+ selected: function(elem){
+ elem.parentNode.selectedIndex;
+ return elem.selected === true;
+ },
+ parent: function(elem){
+ return !!elem.firstChild;
+ },
+ empty: function(elem){
+ return !elem.firstChild;
+ },
+ has: function(elem, i, match){
+ return !!Sizzle( match[3], elem ).length;
+ },
+ header: function(elem){
+ return /h\d/i.test( elem.nodeName );
+ },
+ text: function(elem){
+ return "text" === elem.type;
+ },
+ radio: function(elem){
+ return "radio" === elem.type;
+ },
+ checkbox: function(elem){
+ return "checkbox" === elem.type;
+ },
+ file: function(elem){
+ return "file" === elem.type;
+ },
+ password: function(elem){
+ return "password" === elem.type;
+ },
+ submit: function(elem){
+ return "submit" === elem.type;
+ },
+ image: function(elem){
+ return "image" === elem.type;
+ },
+ reset: function(elem){
+ return "reset" === elem.type;
+ },
+ button: function(elem){
+ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+ },
+ input: function(elem){
+ return /input|select|textarea|button/i.test(elem.nodeName);
+ }
+ },
+ setFilters: {
+ first: function(elem, i){
+ return i === 0;
+ },
+ last: function(elem, i, match, array){
+ return i === array.length - 1;
+ },
+ even: function(elem, i){
+ return i % 2 === 0;
+ },
+ odd: function(elem, i){
+ return i % 2 === 1;
+ },
+ lt: function(elem, i, match){
+ return i < match[3] - 0;
+ },
+ gt: function(elem, i, match){
+ return i > match[3] - 0;
+ },
+ nth: function(elem, i, match){
+ return match[3] - 0 == i;
+ },
+ eq: function(elem, i, match){
+ return match[3] - 0 == i;
+ }
+ },
+ filter: {
+ PSEUDO: function(elem, match, i, array){
+ var name = match[1], filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var i = 0, l = not.length; i < l; i++ ) {
+ if ( not[i] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ },
+ CHILD: function(elem, match){
+ var type = match[1], node = elem;
+ switch (type) {
+ case 'only':
+ case 'first':
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ if ( type == 'first') return true;
+ node = elem;
+ case 'last':
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) return false;
+ }
+ return true;
+ case 'nth':
+ var first = match[2], last = match[3];
+
+ if ( first == 1 && last == 0 ) {
+ return true;
+ }
+
+ var doneName = match[0],
+ parent = elem.parentNode;
+
+ if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+ var count = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+ parent.sizcache = doneName;
+ }
+
+ var diff = elem.nodeIndex - last;
+ if ( first == 0 ) {
+ return diff == 0;
+ } else {
+ return ( diff % first == 0 && diff / first >= 0 );
+ }
+ }
+ },
+ ID: function(elem, match){
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+ TAG: function(elem, match){
+ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+ },
+ CLASS: function(elem, match){
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+ ATTR: function(elem, match){
+ var name = match[1],
+ result = Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value != check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+ POS: function(elem, match, i, array){
+ var name = match[2], filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+} catch(e){
+ makeArray = function(array, results) {
+ var ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var i = 0, l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+ } else {
+ for ( var i = 0; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( "sourceIndex" in document.documentElement ) {
+ sortOrder = function( a, b ) {
+ if ( !a.sourceIndex || !b.sourceIndex ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var ret = a.sourceIndex - b.sourceIndex;
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+} else if ( document.createRange ) {
+ sortOrder = function( a, b ) {
+ if ( !a.ownerDocument || !b.ownerDocument ) {
+ if ( a == b ) {
+ hasDuplicate = true;
+ }
+ return 0;
+ }
+
+ var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+ aRange.setStart(a, 0);
+ aRange.setEnd(a, 0);
+ bRange.setStart(b, 0);
+ bRange.setEnd(b, 0);
+ var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+ if ( ret === 0 ) {
+ hasDuplicate = true;
+ }
+ return ret;
+ };
+}
+
+(function(){
+ var form = document.createElement("div"),
+ id = "script" + (new Date).getTime();
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ var root = document.documentElement;
+ root.insertBefore( form, root.firstChild );
+
+ if ( !!document.getElementById( id ) ) {
+ Expr.find.ID = function(match, context, isXML){
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+ }
+ };
+
+ Expr.filter.ID = function(elem, match){
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+ root = form = null; // release memory in IE
+})();
+
+(function(){
+
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function(match, context){
+ var results = context.getElementsByTagName(match[1]);
+
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ div.innerHTML = "<a href='#'></a>";
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+ Expr.attrHandle.href = function(elem){
+ return elem.getAttribute("href", 2);
+ };
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+ var oldSizzle = Sizzle, div = document.createElement("div");
+ div.innerHTML = "<p class='TEST'></p>";
+
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function(query, context, extra, seed){
+ context = context || document;
+
+ if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(e){}
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+ var div = document.createElement("div");
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ if ( div.getElementsByClassName("e").length === 0 )
+ return;
+
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 )
+ return;
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function(match, context, isXML) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
}
-function $$() {
- return Selector.findChildElements(document, $A(arguments));
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ var sibDir = dir == "previousSibling" && !isXML;
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+ if ( elem ) {
+ if ( sibDir && elem.nodeType === 1 ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ elem = elem[dir];
+ var match = false;
+
+ while ( elem ) {
+ if ( elem.sizcache === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem.sizcache = doneName;
+ elem.sizset = i;
+ }
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
}
+var contains = document.compareDocumentPosition ? function(a, b){
+ return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+ return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+ !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
+};
+
+var posProcess = function(selector, context){
+ var tmpSet = [], later = "", match,
+ root = context.nodeType ? [context] : context;
+
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+
+window.Sizzle = Sizzle;
+
+})();
+
+;(function(engine) {
+ var extendElements = Prototype.Selector.extendElements;
+
+ function select(selector, scope) {
+ return extendElements(engine(selector, scope || document));
+ }
+
+ function match(element, selector) {
+ return engine.matches(selector, [element]).length == 1;
+ }
+
+ Prototype.Selector.engine = engine;
+ Prototype.Selector.select = select;
+ Prototype.Selector.match = match;
+})(Sizzle);
+
+window.Sizzle = Prototype._original_property;
+delete Prototype._original_property;
+
var Form = {
reset: function(form) {
form = $(form);
@@ -4334,8 +5372,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
function findElement(event, expression) {
var element = Event.element(event);
if (!expression) return element;
- var elements = [element].concat(element.ancestors());
- return Selector.findElement(elements, expression, 0);
+ while (element) {
+ if (Object.isElement(element) && Prototype.Selector.match(element, expression)) {
+ return Element.extend(element);
+ }
+ element = element.parentNode;
+ }
}
function pointer(event) {
@@ -4504,12 +5546,12 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
window.addEventListener('unload', Prototype.emptyFunction, false);
- var _getDOMEventName = Prototype.K;
+ var _getDOMEventName = Prototype.K,
+ translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
_getDOMEventName = function(eventName) {
- var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
- return eventName in translations ? translations[eventName] : eventName;
+ return (translations[eventName] || eventName);
};
}
@@ -4543,38 +5585,29 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
element = $(element);
var registry = Element.retrieve(element, 'prototype_event_registry');
+ if (!registry) return element;
- if (Object.isUndefined(registry)) return element;
-
- if (eventName && !handler) {
- var responders = registry.get(eventName);
-
- if (Object.isUndefined(responders)) return element;
-
- responders.each( function(r) {
- Element.stopObserving(element, eventName, r.handler);
- });
- return element;
- } else if (!eventName) {
+ if (!eventName) {
registry.each( function(pair) {
- var eventName = pair.key, responders = pair.value;
-
- responders.each( function(r) {
- Element.stopObserving(element, eventName, r.handler);
- });
+ var eventName = pair.key;
+ stopObserving(element, eventName);
});
return element;
}
var responders = registry.get(eventName);
+ if (!responders) return element;
- if (!responders) return;
+ if (!handler) {
+ responders.each(function(r) {
+ stopObserving(element, eventName, r.handler);
+ });
+ return element;
+ }
var responder = responders.find( function(r) { return r.handler === handler; });
if (!responder) return element;
- var actualEventName = _getDOMEventName(eventName);
-
if (eventName.include(':')) {
if (element.removeEventListener)
element.removeEventListener("dataavailable", responder, false);
@@ -4583,6 +5616,7 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
element.detachEvent("onfilterchange", responder);
}
} else {
+ var actualEventName = _getDOMEventName(eventName);
if (element.removeEventListener)
element.removeEventListener(actualEventName, responder, false);
else
@@ -4623,13 +5657,47 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
return Event.extend(event);
}
+ Event.Handler = Class.create({
+ initialize: function(element, eventName, selector, callback) {
+ this.element = $(element);
+ this.eventName = eventName;
+ this.selector = selector;
+ this.callback = callback;
+ this.handler = this.handleEvent.bind(this);
+ },
+
+ start: function() {
+ Event.observe(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ stop: function() {
+ Event.stopObserving(this.element, this.eventName, this.handler);
+ return this;
+ },
+
+ handleEvent: function(event) {
+ var element = event.findElement(this.selector);
+ if (element) this.callback.call(this.element, event, element);
+ }
+ });
+
+ function on(element, eventName, selector, callback) {
+ element = $(element);
+ if (Object.isFunction(selector) && Object.isUndefined(callback)) {
+ callback = selector, selector = null;
+ }
+
+ return new Event.Handler(element, eventName, selector, callback).start();
+ }
Object.extend(Event, Event.Methods);
Object.extend(Event, {
fire: fire,
observe: observe,
- stopObserving: stopObserving
+ stopObserving: stopObserving,
+ on: on
});
Element.addMethods({
@@ -4637,7 +5705,9 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
observe: observe,
- stopObserving: stopObserving
+ stopObserving: stopObserving,
+
+ on: on
});
Object.extend(document, {
@@ -4647,6 +5717,8 @@ Form.EventObserver = Class.create(Abstract.EventObserver, {
stopObserving: stopObserving.methodize(),
+ on: on.methodize(),
+
loaded: false
});
@@ -4872,3 +5944,58 @@ Element.ClassNames.prototype = {
Object.extend(Element.ClassNames.prototype, Enumerable);
/*--------------------------------------------------------------------------*/
+
+(function() {
+ window.Selector = Class.create({
+ initialize: function(expression) {
+ this.expression = expression.strip();
+ },
+
+ findElements: function(rootElement) {
+ return Prototype.Selector.select(this.expression, rootElement);
+ },
+
+ match: function(element) {
+ return Prototype.Selector.match(element, this.expression);
+ },
+
+ toString: function() {
+ return this.expression;
+ },
+
+ inspect: function() {
+ return "#<Selector: " + this.expression + ">";
+ }
+ });
+
+ Object.extend(Selector, {
+ matchElements: function(elements, expression) {
+ var match = Prototype.Selector.match,
+ results = [];
+
+ for (var i = 0, length = elements.length; i < length; i++) {
+ var element = elements[i];
+ if (match(element, expression)) {
+ results.push(Element.extend(element));
+ }
+ }
+ return results;
+ },
+
+ findElement: function(elements, expression, index) {
+ index = index || 0;
+ var matchIndex = 0, element;
+ for (var i = 0, length = elements.length; i < length; i++) {
+ element = elements[i];
+ if (Prototype.Selector.match(element, expression) && index === matchIndex++) {
+ return Element.extend(element);
+ }
+ }
+ },
+
+ findChildElements: function(element, expressions) {
+ var selector = expressions.toArray().join(', ');
+ return Prototype.Selector.select(selector, element || document);
+ }
+ });
+})();
diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
index 86564031f5..a8f7aeac7d 100644
--- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt
@@ -3,7 +3,7 @@ require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
class ActiveSupport::TestCase
-<% unless options[:skip_activerecord] -%>
+<% unless options[:skip_active_record] -%>
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb
index 0dfb5cd1c9..3376b422cb 100644
--- a/railties/lib/rails/generators/test_case.rb
+++ b/railties/lib/rails/generators/test_case.rb
@@ -51,7 +51,7 @@ module Rails
# Sets default arguments on generator invocation. This can be overwritten when
# invoking it.
#
- # arguments %w(app_name --skip-activerecord)
+ # arguments %w(app_name --skip-active-record)
#
def self.arguments(array)
self.default_arguments = array
@@ -68,7 +68,7 @@ module Rails
# Captures the given stream and returns it:
#
# stream = capture(:stdout){ puts "Cool" }
- # stream #=> "Cool\n"
+ # stream # => "Cool\n"
#
def capture(stream)
begin
@@ -214,8 +214,8 @@ module Rails
# destination File.expand_path("../tmp", File.dirname(__FILE__))
# teardown :cleanup_destination_root
#
- # test "database.yml is not created when skipping activerecord" do
- # run_generator %w(myapp --skip-activerecord)
+ # test "database.yml is not created when skipping Active Record" do
+ # run_generator %w(myapp --skip-active-record)
# assert_no_file "config/database.yml"
# end
# end
diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb
index e9c3ebe685..6cbd1f21c0 100644
--- a/railties/lib/rails/info.rb
+++ b/railties/lib/rails/info.rb
@@ -1,4 +1,3 @@
-require "active_support/core_ext/object/misc"
require "cgi"
require "active_support/core_ext/cgi"
@@ -89,7 +88,7 @@ module Rails
end
property 'Middleware' do
- Rails.configuration.middleware.active.map(&:inspect)
+ Rails.configuration.middleware.map(&:inspect)
end
# The application's location on the filesystem.
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 7a65188a9a..d303212f52 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -116,36 +116,20 @@ module Rails
@paths.concat paths
end
- def autoload_once!
- @autoload_once = true
- end
-
- def autoload_once?
- @autoload_once
- end
-
- def eager_load!
- @eager_load = true
- end
-
- def eager_load?
- @eager_load
- end
-
- def autoload!
- @autoload = true
- end
-
- def autoload?
- @autoload
- end
+ %w(autoload_once eager_load autoload load_path).each do |m|
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{m}!
+ @#{m} = true
+ end
- def load_path!
- @load_path = true
- end
+ def skip_#{m}!
+ @#{m} = false
+ end
- def load_path?
- @load_path
+ def #{m}?
+ @#{m}
+ end
+ RUBY
end
def paths
diff --git a/railties/lib/rails/rack/log_tailer.rb b/railties/lib/rails/rack/log_tailer.rb
index 3fa45156c3..011ac6cecc 100644
--- a/railties/lib/rails/rack/log_tailer.rb
+++ b/railties/lib/rails/rack/log_tailer.rb
@@ -6,7 +6,6 @@ module Rails
path = Pathname.new(log || "#{File.expand_path(Rails.root)}/log/#{Rails.env}.log").cleanpath
@cursor = ::File.size(path)
- @last_checked = Time.now.to_f
@file = ::File.open(path, 'r')
end
@@ -20,11 +19,9 @@ module Rails
def tail!
@file.seek @cursor
- mod = @file.mtime.to_f
- if mod > @last_checked
+ if !@file.eof?
contents = @file.read
- @last_checked = mod
- @cursor += contents.size
+ @cursor = @file.tell
$stdout.print contents
end
end
diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb
index dbdbfea509..8a6e2716dc 100644
--- a/railties/lib/rails/railtie.rb
+++ b/railties/lib/rails/railtie.rb
@@ -6,53 +6,53 @@ require 'active_support/deprecation'
module Rails
# Railtie is the core of the Rails Framework and provides several hooks to extend
# Rails and/or modify the initialization process.
- #
+ #
# Every major component of Rails (Action Mailer, Action Controller,
# Action View, Active Record and Active Resource) are all Railties, so each of
# them is responsible to set their own initialization. This makes, for example,
# Rails absent of any Active Record hook, allowing any other ORM framework to hook in.
- #
+ #
# Developing a Rails extension does _not_ require any implementation of
# Railtie, but if you need to interact with the Rails framework during
# or after boot, then Railtie is what you need to do that interaction.
- #
+ #
# For example, the following would need you to implement Railtie in your
# plugin:
- #
+ #
# * creating initializers
# * configuring a Rails framework or the Application, like setting a generator
# * adding Rails config.* keys to the environment
# * setting up a subscriber to the Rails +ActiveSupport::Notifications+
# * adding rake tasks into rails
- #
+ #
# == Creating your Railtie
#
# Implementing Railtie in your Rails extension is done by creating a class
# Railtie that has your extension name and making sure that this gets loaded
# during boot time of the Rails stack.
- #
+ #
# You can do this however you wish, but here is an example if you want to provide
# it for a gem that can be used with or without Rails:
- #
+ #
# * Create a file (say, lib/my_gem/railtie.rb) which contains class Railtie inheriting from
# Rails::Railtie and is namespaced to your gem:
#
- # # lib/my_gem/railtie.rb
- # module MyGem
- # class Railtie < Rails::Railtie
+ # # lib/my_gem/railtie.rb
+ # module MyGem
+ # class Railtie < Rails::Railtie
+ # end
# end
- # end
- #
+ #
# * Require your own gem as well as rails in this file:
- #
- # # lib/my_gem/railtie.rb
- # require 'my_gem'
- # require 'rails'
- #
- # module MyGem
- # class Railtie < Rails::Railtie
+ #
+ # # lib/my_gem/railtie.rb
+ # require 'my_gem'
+ # require 'rails'
+ #
+ # module MyGem
+ # class Railtie < Rails::Railtie
+ # end
# end
- # end
#
# == Initializers
#
@@ -65,12 +65,12 @@ module Rails
# end
# end
#
- # If specified, the block can also receive the application object, in case you
+ # If specified, the block can also receive the application object, in case you
# need to access some application specific configuration, like middleware:
#
# class MyRailtie < Rails::Railtie
# initializer "my_railtie.configure_rails_initialization" do |app|
- # app.middlewares.use MyRailtie::Middleware
+ # app.middleware.use MyRailtie::Middleware
# end
# end
#
@@ -156,6 +156,12 @@ module Rails
@rake_tasks
end
+ def console(&blk)
+ @load_console ||= []
+ @load_console << blk if blk
+ @load_console
+ end
+
def generators(&blk)
@generators ||= []
@generators << blk if blk
@@ -170,20 +176,16 @@ module Rails
def eager_load!
end
- def rake_tasks
- self.class.rake_tasks
- end
-
- def generators
- self.class.generators
+ def load_console
+ self.class.console.each(&:call)
end
def load_tasks
- rake_tasks.each { |blk| blk.call }
+ self.class.rake_tasks.each(&:call)
end
def load_generators
- generators.each { |blk| blk.call }
+ self.class.generators.each(&:call)
end
end
end
diff --git a/railties/lib/rails/script_rails_loader.rb b/railties/lib/rails/script_rails_loader.rb
index 8fbd3bf492..91672e5d81 100644
--- a/railties/lib/rails/script_rails_loader.rb
+++ b/railties/lib/rails/script_rails_loader.rb
@@ -7,6 +7,7 @@ module Rails
def self.exec_script_rails!
cwd = Dir.pwd
+ return unless in_rails_application? || in_rails_application_subdirectory?
exec RUBY, SCRIPT_RAILS, *ARGV if in_rails_application?
Dir.chdir("..") do
# Recurse in a chdir block: if the search fails we want to be sure
@@ -18,7 +19,7 @@ module Rails
end
def self.in_rails_application?
- File.exists?(SCRIPT_RAILS) || in_rails_application_subdirectory?
+ File.exists?(SCRIPT_RAILS)
end
def self.in_rails_application_subdirectory?(path = Pathname.new(Dir.pwd))
diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake
index 492f05e3cc..c1b1a41d48 100644
--- a/railties/lib/rails/tasks/documentation.rake
+++ b/railties/lib/rails/tasks/documentation.rake
@@ -58,43 +58,43 @@ namespace :doc do
rdoc.rdoc_files.include('README')
gem_path('actionmailer') do |actionmailer|
- %w(README CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_mailer/base.rb).each do |file|
rdoc.rdoc_files.include("#{actionmailer}/#{file}")
end
end
gem_path('actionpack') do |actionpack|
- %w(README CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/action_controller/**/*.rb lib/action_view/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{actionpack}/#{file}")
end
end
gem_path('activemodel') do |activemodel|
- %w(README CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG MIT-LICENSE lib/active_model/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activemodel}/#{file}")
end
end
gem_path('activerecord') do |activerecord|
- %w(README CHANGELOG lib/active_record/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_record/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activerecord}/#{file}")
end
end
gem_path('activeresource') do |activeresource|
- %w(README CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_resource.rb lib/active_resource/*).each do |file|
rdoc.rdoc_files.include("#{activeresource}/#{file}")
end
end
gem_path('activesupport') do |activesupport|
- %w(README CHANGELOG lib/active_support/**/*.rb).each do |file|
+ %w(README.rdoc CHANGELOG lib/active_support/**/*.rb).each do |file|
rdoc.rdoc_files.include("#{activesupport}/#{file}")
end
end
gem_path('railties') do |railties|
- %w(README CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
+ %w(README.rdoc CHANGELOG lib/{*.rb,commands/*.rb,generators/*.rb}).each do |file|
rdoc.rdoc_files.include("#{railties}/#{file}")
end
end
diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake
index e7bd0c38dc..443dacd739 100644
--- a/railties/lib/rails/tasks/framework.rake
+++ b/railties/lib/rails/tasks/framework.rake
@@ -54,7 +54,7 @@ namespace :rails do
namespace :update do
def invoke_from_app_generator(method)
- app_generator.invoke(method)
+ app_generator.send(method)
end
def app_generator
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index ecd513c2c8..38c14fcd6b 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -70,7 +70,7 @@ module Kernel
end
end
-desc 'Runs test:unit, test:functional, test:integration together (also available: test:benchmark, test:profile, test:plugins)'
+desc 'Runs test:units, test:functionals, test:integration together (also available: test:benchmark, test:profile, test:plugins)'
task :test do
errors = %w(test:units test:functionals test:integration).collect do |task|
begin
diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb
index 34d96fd0f3..c5d1d02bc1 100644
--- a/railties/lib/rails/version.rb
+++ b/railties/lib/rails/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index 247b926af9..d76b74190b 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -13,14 +13,14 @@ Gem::Specification.new do |s|
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'rails'
- s.files = Dir['CHANGELOG', 'README', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}']
+ s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}']
s.require_path = 'lib'
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false
s.add_dependency('rake', '>= 0.8.3')
- s.add_dependency('thor', '~> 0.13.7')
+ s.add_dependency('thor', '~> 0.14.0')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb
index 8ff69f0208..a72e6916dd 100644
--- a/railties/test/application/console_test.rb
+++ b/railties/test/application/console_test.rb
@@ -9,10 +9,8 @@ class ConsoleTest < Test::Unit::TestCase
end
def load_environment
- # Load steps taken from rails/commands/console.rb
require "#{rails_root}/config/environment"
- require 'rails/console/app'
- require 'rails/console/helpers'
+ Rails.application.load_console
end
def test_app_method_should_return_integration_session
@@ -75,4 +73,21 @@ class ConsoleTest < Test::Unit::TestCase
assert_equal 'Once upon a time in a world...',
helper.truncate('Once upon a time in a world far far away')
end
+
+ def test_active_record_does_not_panic_when_referencing_an_observed_constant
+ add_to_config "config.active_record.observers = :user_observer"
+
+ app_file "app/models/user.rb", <<-MODEL
+ class User < ActiveRecord::Base
+ end
+ MODEL
+
+ app_file "app/models/user_observer.rb", <<-MODEL
+ class UserObserver < ActiveRecord::Observer
+ end
+ MODEL
+
+ load_environment
+ assert_nothing_raised { User }
+ end
end
diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb
index cbf0decd07..d258625f42 100644
--- a/railties/test/application/generators_test.rb
+++ b/railties/test/application/generators_test.rb
@@ -103,5 +103,20 @@ module ApplicationTests
assert_equal({ :plugin => { :generator => "-g" } }, c.generators.aliases)
end
end
+
+ test "generators with string and hash for options should generate symbol keys" do
+ with_bare_config do |c|
+ c.generators do |g|
+ g.orm 'datamapper', :migration => false
+ end
+
+ expected = {
+ :rails => { :orm => :datamapper },
+ :datamapper => { :migration => false }
+ }
+
+ assert_equal expected, c.generators.options
+ end
+ end
end
end
diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb
index 4ff10091b1..d8e916f45f 100644
--- a/railties/test/application/initializers/frameworks_test.rb
+++ b/railties/test/application/initializers/frameworks_test.rb
@@ -98,7 +98,17 @@ module ApplicationTests
require "#{app_path}/config/environment"
- expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ expects = [ActiveRecord::QueryCache, ActiveRecord::SessionStore]
+ middleware = Rails.application.config.middleware.map { |m| m.klass }
+ assert_equal expects, middleware & expects
+ end
+
+ test "database middleware initializes when allow concurrency is true" do
+ add_to_config "config.threadsafe!"
+
+ require "#{app_path}/config/environment"
+
+ expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache]
middleware = Rails.application.config.middleware.map { |m| m.klass }
assert_equal expects, middleware & expects
end
diff --git a/railties/test/application/initializers/notifications_test.rb b/railties/test/application/initializers/notifications_test.rb
index fc8548af1f..7e035be764 100644
--- a/railties/test/application/initializers/notifications_test.rb
+++ b/railties/test/application/initializers/notifications_test.rb
@@ -1,17 +1,6 @@
require "isolation/abstract_unit"
module ApplicationTests
- class MockLogger
- def method_missing(*args)
- @logged ||= []
- @logged << args.last
- end
-
- def logged
- @logged.compact.map { |l| l.to_s.strip }
- end
- end
-
class NotificationsTest < Test::Unit::TestCase
include ActiveSupport::Testing::Isolation
@@ -34,15 +23,17 @@ module ApplicationTests
RUBY
require "#{app_path}/config/environment"
+ require "active_support/log_subscriber/test_helper"
- ActiveRecord::Base.logger = logger = MockLogger.new
+ logger = ActiveSupport::LogSubscriber::TestHelper::MockLogger.new
+ ActiveRecord::Base.logger = logger
# Mimic Active Record notifications
instrument "sql.active_record", :name => "SQL", :sql => "SHOW tables"
wait
- assert_equal 1, logger.logged.size
- assert_match /SHOW tables/, logger.logged.last
+ assert_equal 1, logger.logged(:debug).size
+ assert_match /SHOW tables/, logger.logged(:debug).last
end
end
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index e66e81ea2c..1f127cee78 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -28,18 +28,18 @@ module ApplicationTests
"ActionDispatch::RemoteIp",
"Rack::Sendfile",
"ActionDispatch::Callbacks",
- "ActiveRecord::ConnectionAdapters::ConnectionManagement",
"ActiveRecord::QueryCache",
"ActionDispatch::Cookies",
"ActionDispatch::Session::CookieStore",
"ActionDispatch::Flash",
"ActionDispatch::ParamsParser",
"Rack::MethodOverride",
- "ActionDispatch::Head"
+ "ActionDispatch::Head",
+ "ActionDispatch::BestStandardsSupport"
], middleware
end
- test "removing activerecord omits its middleware" do
+ test "removing Active Record omits its middleware" do
use_frameworks []
boot!
assert !middleware.include?("ActiveRecord::ConnectionAdapters::ConnectionManagement")
diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb
index f0268164d0..febc53bac9 100644
--- a/railties/test/application/routing_test.rb
+++ b/railties/test/application/routing_test.rb
@@ -11,19 +11,19 @@ module ApplicationTests
extend Rack::Test::Methods
end
- def app
+ def app(env = "production")
+ old_env = ENV["RAILS_ENV"]
+
@app ||= begin
+ ENV["RAILS_ENV"] = env
require "#{app_path}/config/environment"
Rails.application
end
+ ensure
+ ENV["RAILS_ENV"] = old_env
end
- test "rails/info/properties" do
- get "/rails/info/properties"
- assert_equal 200, last_response.status
- end
-
- test "simple controller" do
+ def simple_controller
controller :foo, <<-RUBY
class FooController < ApplicationController
def index
@@ -37,11 +37,42 @@ module ApplicationTests
match ':controller(/:action)'
end
RUBY
+ end
+
+ test "rails/info/properties in development" do
+ app("development")
+ get "/rails/info/properties"
+ assert_equal 200, last_response.status
+ end
+
+ test "rails/info/properties in production" do
+ app("production")
+ get "/rails/info/properties"
+ assert_equal 404, last_response.status
+ end
+
+ test "simple controller" do
+ simple_controller
get '/foo'
assert_equal 'foo', last_response.body
end
+ test "simple controller in production mode returns best standards" do
+ simple_controller
+
+ get '/foo'
+ assert_equal "IE=Edge,chrome=1", last_response.headers["X-UA-Compatible"]
+ end
+
+ test "simple controller in development mode leaves out Chrome" do
+ simple_controller
+ app("development")
+
+ get "/foo"
+ assert_equal "IE=Edge", last_response.headers["X-UA-Compatible"]
+ end
+
test "simple controller with helper" do
controller :foo, <<-RUBY
class FooController < ApplicationController
@@ -146,38 +177,42 @@ module ApplicationTests
assert_equal 'admin::foo', last_response.body
end
- test "reloads routes when configuration is changed" do
- controller :foo, <<-RUBY
- class FooController < ApplicationController
- def bar
- render :text => "bar"
+ {"development" => "baz", "production" => "bar"}.each do |mode, expected|
+ test "reloads routes when configuration is changed in #{mode}" do
+ controller :foo, <<-RUBY
+ class FooController < ApplicationController
+ def bar
+ render :text => "bar"
+ end
+
+ def baz
+ render :text => "baz"
+ end
end
+ RUBY
- def baz
- render :text => "baz"
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match 'foo', :to => 'foo#bar'
end
- end
- RUBY
+ RUBY
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do |map|
- match 'foo', :to => 'foo#bar'
- end
- RUBY
+ app(mode)
- get '/foo'
- assert_equal 'bar', last_response.body
+ get '/foo'
+ assert_equal 'bar', last_response.body
- app_file 'config/routes.rb', <<-RUBY
- AppTemplate::Application.routes.draw do |map|
- match 'foo', :to => 'foo#baz'
- end
- RUBY
+ app_file 'config/routes.rb', <<-RUBY
+ AppTemplate::Application.routes.draw do |map|
+ match 'foo', :to => 'foo#baz'
+ end
+ RUBY
- sleep 0.1
+ sleep 0.1
- get '/foo'
- assert_equal 'baz', last_response.body
+ get '/foo'
+ assert_equal expected, last_response.body
+ end
end
test 'resource routing with irrigular inflection' do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 6a3b5de9de..08cfb585f9 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -58,6 +58,12 @@ class AppGeneratorTest < Rails::Generators::TestCase
DEFAULT_APP_FILES.each{ |path| assert_file path }
end
+ def test_application_generate_pretend
+ run_generator ["testapp", "--pretend"]
+
+ DEFAULT_APP_FILES.each{ |path| assert_no_file path }
+ end
+
def test_application_controller_and_layout_files
run_generator
assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag :all/
@@ -65,7 +71,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
end
def test_options_before_application_name_raises_an_error
- content = capture(:stderr){ run_generator(["--skip-activerecord", destination_root]) }
+ content = capture(:stderr){ run_generator(["--skip-active-record", destination_root]) }
assert_equal "Options should be given after the application name. For details run: rails --help\n", content
end
@@ -100,6 +106,30 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "things-43/config/application.rb", /^module Things43$/
end
+ def test_application_name_is_detected_if_it_exists_and_app_folder_renamed
+ app_root = File.join(destination_root, "myapp")
+ app_moved_root = File.join(destination_root, "myapp_moved")
+
+ run_generator [app_root]
+
+ Rails.application.config.root = app_moved_root
+ Rails.application.class.stubs(:name).returns("Myapp")
+ Rails.application.stubs(:is_a?).returns(Rails::Application)
+
+ FileUtils.mv(app_root, app_moved_root)
+
+ # forces the shell to automatically overwrite all files
+ Thor::Base.shell.send(:attr_accessor, :always_force)
+ shell = Thor::Base.shell.new
+ shell.send(:always_force=, true)
+
+ generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true },
+ :destination_root => app_moved_root, :shell => shell
+ generator.send(:app_const)
+ silence(:stdout){ generator.send(:create_config_files) }
+ assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/
+ end
+
def test_application_names_are_not_singularized
run_generator [File.join(destination_root, "hats")]
assert_file "hats/config/environment.rb", /Hats::Application\.initialize!/
@@ -117,26 +147,31 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file "Gemfile", /^gem\s+["']mysql["']$/
end
- def test_config_database_is_not_added_if_skip_activerecord_is_given
- run_generator [destination_root, "--skip-activerecord"]
+ def test_config_database_is_not_added_if_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
assert_no_file "config/database.yml"
end
- def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given
- run_generator [destination_root, "--skip-activerecord"]
+ def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given
+ run_generator [destination_root, "--skip-active-record"]
assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/
end
def test_prototype_and_test_unit_are_added_by_default
run_generator
+ assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/
+ assert_file "public/javascripts/application.js"
assert_file "public/javascripts/prototype.js"
+ assert_file "public/javascripts/rails.js"
assert_file "test"
end
def test_prototype_and_test_unit_are_skipped_if_required
- run_generator [destination_root, "--skip-prototype", "--skip-testunit"]
+ run_generator [destination_root, "--skip-prototype", "--skip-test-unit"]
+ assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/
+ assert_file "public/javascripts/application.js"
assert_no_file "public/javascripts/prototype.js"
- assert_file "public/javascripts"
+ assert_no_file "public/javascripts/rails.js"
assert_no_file "test"
end
@@ -167,7 +202,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- assert_match /It works!/, silence(:stdout){ generator.invoke }
+ assert_match /It works!/, silence(:stdout){ generator.invoke_all }
end
def test_usage_read_from_file
@@ -191,14 +226,14 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_dev_option
generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke }
+ silence(:stdout){ generator.invoke_all }
rails_path = File.expand_path('../../..', Rails.root)
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/
end
def test_edge_option
generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install")
- silence(:stdout){ generator.invoke }
+ silence(:stdout){ generator.invoke_all }
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/
end
@@ -262,7 +297,7 @@ class CustomAppGeneratorTest < Rails::Generators::TestCase
template.instance_eval "def read; self; end" # Make the string respond to read
generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
- capture(:stdout) { generator.invoke }
+ capture(:stdout) { generator.invoke_all }
DEFAULT_APP_FILES.each{ |path| assert_no_file path }
end
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index ea469cb3c8..f12445ae35 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -216,4 +216,19 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
# Stylesheets (should not be removed)
assert_file "public/stylesheets/scaffold.css"
end
+
+ def test_scaffold_generator_on_revoke_does_not_mutilate_legacy_map_parameter
+ run_generator
+
+ # Add a |map| parameter to the routes block manually
+ route_path = File.expand_path("config/routes.rb", destination_root)
+ content = File.read(route_path).gsub(/\.routes\.draw do/) do |match|
+ "#{match} |map|"
+ end
+ File.open(route_path, "wb") { |file| file.write(content) }
+
+ run_generator ["product_line"], :behavior => :revoke
+
+ assert_file "config/routes.rb", /\.routes\.draw do\s*\|map\|\s*$/
+ end
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 74a09d4bde..f93800a5ae 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -108,7 +108,7 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match /^ fixjour$/, output
end
- def test_rails_generators_does_not_show_activerecord_hooks
+ def test_rails_generators_does_not_show_active_record_hooks
output = capture(:stdout){ Rails::Generators.help }
assert_match /ActiveRecord:/, output
assert_match /^ active_record:fixjour$/, output
diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb
index 008247cb1a..80fae8c543 100644
--- a/railties/test/paths_test.rb
+++ b/railties/test/paths_test.rb
@@ -118,13 +118,23 @@ class PathsTest < ActiveSupport::TestCase
assert_raise(RuntimeError) { @root << "/biz" }
end
- test "it is possible to add a path that should be loaded only once" do
+ test "it is possible to add a path that should be autoloaded only once" do
@root.app = "/app"
@root.app.autoload_once!
assert @root.app.autoload_once?
assert @root.autoload_once.include?(@root.app.paths.first)
end
+ test "it is possible to remove a path that should be autoloaded only once" do
+ @root.app = "/app"
+ @root.app.autoload_once!
+ assert @root.app.autoload_once?
+
+ @root.app.skip_autoload_once!
+ assert !@root.app.autoload_once?
+ assert !@root.autoload_once.include?(@root.app.paths.first)
+ end
+
test "it is possible to add a path without assignment and specify it should be loaded only once" do
@root.app "/app", :autoload_once => true
assert @root.app.autoload_once?
@@ -152,13 +162,23 @@ class PathsTest < ActiveSupport::TestCase
assert_equal 2, @root.autoload_once.size
end
- test "it is possible to mark a path as eager" do
+ test "it is possible to mark a path as eager loaded" do
@root.app = "/app"
@root.app.eager_load!
assert @root.app.eager_load?
assert @root.eager_load.include?(@root.app.paths.first)
end
+ test "it is possible to skip a path from eager loading" do
+ @root.app = "/app"
+ @root.app.eager_load!
+ assert @root.app.eager_load?
+
+ @root.app.skip_eager_load!
+ assert !@root.app.eager_load?
+ assert !@root.eager_load.include?(@root.app.paths.first)
+ end
+
test "it is possible to add a path without assignment and mark it as eager" do
@root.app "/app", :eager_load => true
assert @root.app.eager_load?
diff --git a/railties/test/railties/railtie_test.rb b/railties/test/railties/railtie_test.rb
index c74cc01dc1..db0fd87491 100644
--- a/railties/test/railties/railtie_test.rb
+++ b/railties/test/railties/railtie_test.rb
@@ -103,6 +103,22 @@ module RailtiesTest
assert $ran_block
end
+ test "console block is executed when MyApp.load_console is called" do
+ $ran_block = false
+
+ class MyTie < Rails::Railtie
+ console do
+ $ran_block = true
+ end
+ end
+
+ require "#{app_path}/config/environment"
+
+ assert !$ran_block
+ AppTemplate::Application.load_console
+ assert $ran_block
+ end
+
test "railtie can add initializers" do
$ran_block = false
diff --git a/version.rb b/version.rb
index 34d96fd0f3..c5d1d02bc1 100644
--- a/version.rb
+++ b/version.rb
@@ -3,7 +3,7 @@ module Rails
MAJOR = 3
MINOR = 0
TINY = 0
- BUILD = "beta4"
+ BUILD = "rc"
STRING = [MAJOR, MINOR, TINY, BUILD].join('.')
end