aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Rakefile62
-rwxr-xr-xactionmailer/Rakefile7
-rw-r--r--actionmailer/lib/action_mailer/base.rb2
-rw-r--r--actionmailer/test/abstract_unit.rb3
-rw-r--r--actionpack/CHANGELOG57
-rw-r--r--actionpack/Rakefile12
-rw-r--r--actionpack/lib/action_controller/assertions/response_assertions.rb107
-rwxr-xr-xactionpack/lib/action_controller/base.rb90
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb23
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb18
-rw-r--r--actionpack/lib/action_controller/filters.rb414
-rw-r--r--actionpack/lib/action_controller/layout.rb4
-rw-r--r--actionpack/lib/action_controller/mime_responds.rb6
-rw-r--r--actionpack/lib/action_controller/mime_type.rb82
-rw-r--r--actionpack/lib/action_controller/mime_types.rb3
-rw-r--r--actionpack/lib/action_controller/polymorphic_routes.rb37
-rw-r--r--actionpack/lib/action_controller/rack_process.rb35
-rwxr-xr-xactionpack/lib/action_controller/request.rb49
-rw-r--r--actionpack/lib/action_controller/rescue.rb2
-rw-r--r--actionpack/lib/action_controller/resources.rb6
-rw-r--r--actionpack/lib/action_controller/routing.rb4
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb3
-rw-r--r--actionpack/lib/action_controller/templates/rescues/diagnostics.erb4
-rw-r--r--actionpack/lib/action_controller/templates/rescues/template_error.erb4
-rw-r--r--actionpack/lib/action_controller/test_process.rb28
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_view.rb8
-rw-r--r--actionpack/lib/action_view/base.rb210
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb62
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb3
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb7
-rwxr-xr-xactionpack/lib/action_view/helpers/date_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb26
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb27
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb50
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb137
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb384
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb38
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb10
-rw-r--r--actionpack/lib/action_view/inline_template.rb20
-rw-r--r--actionpack/lib/action_view/partial_template.rb70
-rw-r--r--actionpack/lib/action_view/partials.rb45
-rw-r--r--actionpack/lib/action_view/paths.rb96
-rw-r--r--actionpack/lib/action_view/renderable.rb78
-rw-r--r--actionpack/lib/action_view/renderable_partial.rb36
-rw-r--r--actionpack/lib/action_view/template.rb128
-rw-r--r--actionpack/lib/action_view/template_error.rb4
-rw-r--r--actionpack/lib/action_view/template_file.rb88
-rw-r--r--actionpack/lib/action_view/template_handler.rb14
-rw-r--r--actionpack/lib/action_view/template_handlers/builder.rb20
-rw-r--r--actionpack/lib/action_view/template_handlers/compilable.rb112
-rw-r--r--actionpack/lib/action_view/template_handlers/erb.rb12
-rw-r--r--actionpack/lib/action_view/template_handlers/rjs.rb19
-rw-r--r--actionpack/lib/action_view/view_load_paths.rb103
-rw-r--r--actionpack/test/abstract_unit.rb3
-rw-r--r--actionpack/test/active_record_unit.rb58
-rw-r--r--actionpack/test/activerecord/render_partial_with_record_identification_test.rb27
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb43
-rw-r--r--actionpack/test/controller/addresses_render_test.rb3
-rw-r--r--actionpack/test/controller/caching_test.rb78
-rw-r--r--actionpack/test/controller/capture_test.rb2
-rwxr-xr-xactionpack/test/controller/cgi_test.rb87
-rw-r--r--actionpack/test/controller/components_test.rb2
-rw-r--r--actionpack/test/controller/content_type_test.rb38
-rw-r--r--actionpack/test/controller/custom_handler_test.rb45
-rw-r--r--actionpack/test/controller/deprecation/deprecated_base_methods_test.rb3
-rw-r--r--actionpack/test/controller/html-scanner/document_test.rb25
-rw-r--r--actionpack/test/controller/layout_test.rb5
-rw-r--r--actionpack/test/controller/mime_responds_test.rb58
-rw-r--r--actionpack/test/controller/new_render_test.rb142
-rw-r--r--actionpack/test/controller/polymorphic_routes_test.rb33
-rw-r--r--actionpack/test/controller/rack_test.rb54
-rwxr-xr-xactionpack/test/controller/redirect_test.rb24
-rw-r--r--actionpack/test/controller/render_test.rb46
-rw-r--r--actionpack/test/controller/request_test.rb16
-rw-r--r--actionpack/test/controller/resources_test.rb32
-rw-r--r--actionpack/test/controller/routing_test.rb20
-rw-r--r--actionpack/test/controller/send_file_test.rb4
-rw-r--r--actionpack/test/controller/test_test.rb18
-rw-r--r--actionpack/test/controller/view_paths_test.rb62
-rw-r--r--actionpack/test/fixtures/developers/_developer.erb1
-rw-r--r--actionpack/test/fixtures/fun/games/_game.erb1
-rw-r--r--actionpack/test/fixtures/fun/serious/games/_game.erb1
-rw-r--r--actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb2
-rw-r--r--actionpack/test/fixtures/projects/_project.erb1
-rw-r--r--actionpack/test/fixtures/public/javascripts/subdir/subdir.js1
-rw-r--r--actionpack/test/fixtures/public/stylesheets/subdir/subdir.css1
-rw-r--r--actionpack/test/fixtures/replies/_reply.erb1
-rw-r--r--actionpack/test/fixtures/test/_customer_with_var.erb1
-rw-r--r--actionpack/test/fixtures/test/_local_inspector.html.erb1
-rw-r--r--actionpack/test/fixtures/test/hello.builder2
-rw-r--r--actionpack/test/fixtures/test/hyphen-ated.erb1
-rw-r--r--actionpack/test/fixtures/test/non_erb_block_content_for.builder2
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb46
-rwxr-xr-xactionpack/test/template/date_helper_test.rb78
-rw-r--r--actionpack/test/template/form_options_helper_test.rb1874
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb40
-rw-r--r--actionpack/test/template/javascript_helper_test.rb16
-rw-r--r--actionpack/test/template/prototype_helper_test.rb143
-rw-r--r--actionpack/test/template/record_tag_helper_test.rb27
-rw-r--r--actionpack/test/template/render_test.rb131
-rw-r--r--actionpack/test/template/tag_helper_test.rb35
-rw-r--r--actionpack/test/template/template_file_test.rb95
-rw-r--r--actionpack/test/template/template_object_test.rb92
-rw-r--r--actionpack/test/template/text_helper_test.rb5
-rw-r--r--actionpack/test/template/url_helper_test.rb36
-rw-r--r--activemodel/Rakefile18
-rw-r--r--activemodel/lib/active_model.rb20
-rw-r--r--activemodel/lib/active_model/base.rb4
-rw-r--r--activemodel/lib/active_model/callbacks.rb2
-rw-r--r--activemodel/lib/active_model/core.rb7
-rw-r--r--activemodel/lib/active_model/observing.rb18
-rw-r--r--activemodel/lib/active_model/state_machine.rb66
-rw-r--r--activemodel/lib/active_model/state_machine/event.rb62
-rw-r--r--activemodel/lib/active_model/state_machine/machine.rb74
-rw-r--r--activemodel/lib/active_model/state_machine/state.rb50
-rw-r--r--activemodel/lib/active_model/state_machine/state_transition.rb40
-rw-r--r--activemodel/lib/active_model/validations.rb2
-rw-r--r--activemodel/spec/observing_spec.rb120
-rw-r--r--activemodel/spec/spec_helper.rb17
-rw-r--r--activemodel/test/observing_test.rb123
-rw-r--r--activemodel/test/state_machine/event_test.rb51
-rw-r--r--activemodel/test/state_machine/machine_test.rb43
-rw-r--r--activemodel/test/state_machine/state_test.rb74
-rw-r--r--activemodel/test/state_machine/state_transition_test.rb88
-rw-r--r--activemodel/test/state_machine_test.rb324
-rw-r--r--activemodel/test/test_helper.rb39
-rw-r--r--activerecord/CHANGELOG44
-rwxr-xr-xactiverecord/Rakefile8
-rw-r--r--activerecord/lib/active_record/association_preload.rb15
-rwxr-xr-xactiverecord/lib/active_record/associations.rb56
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb19
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb6
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb7
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb6
-rwxr-xr-xactiverecord/lib/active_record/associations/has_one_association.rb15
-rwxr-xr-xactiverecord/lib/active_record/base.rb43
-rwxr-xr-xactiverecord/lib/active_record/callbacks.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb21
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/abstract_adapter.rb13
-rwxr-xr-xactiverecord/lib/active_record/connection_adapters/mysql_adapter.rb74
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb12
-rw-r--r--activerecord/lib/active_record/dirty.rb4
-rwxr-xr-xactiverecord/lib/active_record/fixtures.rb15
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb1
-rw-r--r--activerecord/lib/active_record/migration.rb5
-rw-r--r--activerecord/lib/active_record/named_scope.rb4
-rw-r--r--activerecord/lib/active_record/observer.rb9
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/test_case.rb15
-rw-r--r--activerecord/lib/active_record/transactions.rb17
-rwxr-xr-xactiverecord/lib/active_record/validations.rb23
-rw-r--r--activerecord/test/cases/adapter_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb61
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb6
-rwxr-xr-xactiverecord/test/cases/associations/has_one_associations_test.rb7
-rwxr-xr-xactiverecord/test/cases/associations_test.rb108
-rwxr-xr-xactiverecord/test/cases/base_test.rb21
-rw-r--r--activerecord/test/cases/column_definition_test.rb36
-rw-r--r--activerecord/test/cases/dirty_test.rb46
-rw-r--r--activerecord/test/cases/finder_test.rb24
-rwxr-xr-xactiverecord/test/cases/fixtures_test.rb35
-rw-r--r--activerecord/test/cases/helper.rb10
-rwxr-xr-xactiverecord/test/cases/inheritance_test.rb7
-rwxr-xr-xactiverecord/test/cases/lifecycle_test.rb12
-rw-r--r--activerecord/test/cases/locking_test.rb20
-rw-r--r--activerecord/test/cases/method_scoping_test.rb10
-rw-r--r--activerecord/test/cases/migration_test.rb127
-rw-r--r--activerecord/test/cases/multiple_db_test.rb25
-rw-r--r--activerecord/test/cases/named_scope_test.rb57
-rw-r--r--activerecord/test/cases/reflection_test.rb6
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb46
-rwxr-xr-xactiverecord/test/cases/validations_test.rb23
-rw-r--r--activerecord/test/fixtures/companies.yml1
-rw-r--r--activerecord/test/models/author.rb3
-rw-r--r--activerecord/test/models/category.rb1
-rwxr-xr-xactiverecord/test/models/company.rb3
-rw-r--r--activerecord/test/models/person.rb2
-rw-r--r--activerecord/test/models/post.rb13
-rwxr-xr-xactiverecord/test/models/topic.rb4
-rw-r--r--activerecord/test/schema/schema.rb9
-rw-r--r--activeresource/Rakefile9
-rw-r--r--activesupport/CHANGELOG26
-rw-r--r--activesupport/Rakefile12
-rw-r--r--activesupport/lib/active_support.rb1
-rw-r--r--activesupport/lib/active_support/cache.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb47
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb25
-rw-r--r--activesupport/lib/active_support/core_ext/module/model_naming.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/instance_variables.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/test.rb1
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/inflector.rb27
-rw-r--r--activesupport/lib/active_support/json/encoders/date_time.rb2
-rw-r--r--activesupport/lib/active_support/memoizable.rb35
-rw-r--r--activesupport/lib/active_support/test_case.rb6
-rw-r--r--activesupport/lib/active_support/testing/core_ext/test.rb6
-rw-r--r--activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb (renamed from activesupport/lib/active_support/core_ext/test/unit/assertions.rb)7
-rw-r--r--activesupport/lib/active_support/testing/performance.rb92
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb47
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb6
-rw-r--r--activesupport/test/caching_test.rb6
-rw-r--r--activesupport/test/core_ext/array_ext_test.rb50
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb9
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb174
-rw-r--r--activesupport/test/inflector_test.rb29
-rw-r--r--activesupport/test/memoizable_test.rb49
-rw-r--r--activesupport/test/time_zone_test.rb7
-rwxr-xr-xcleanlogs.sh1
-rw-r--r--doc/template/horo.rb613
-rw-r--r--railties/CHANGELOG19
-rw-r--r--railties/Rakefile14
-rw-r--r--railties/bin/about3
-rw-r--r--railties/configs/routes.rb2
-rw-r--r--railties/environments/boot.rb8
-rw-r--r--railties/helpers/performance_test_helper.rb6
-rw-r--r--railties/lib/commands/about.rb1
-rw-r--r--railties/lib/commands/plugin.rb49
-rw-r--r--railties/lib/commands/server.rb14
-rw-r--r--railties/lib/commands/servers/thin.rb25
-rw-r--r--railties/lib/console_with_helpers.rb2
-rw-r--r--railties/lib/initializer.rb80
-rw-r--r--railties/lib/performance_test_help.rb6
-rw-r--r--railties/lib/rails/gem_dependency.rb6
-rw-r--r--railties/lib/rails/plugin.rb21
-rw-r--r--railties/lib/rails/plugin/locator.rb9
-rw-r--r--railties/lib/rails_generator/commands.rb27
-rw-r--r--railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb2
-rw-r--r--railties/lib/rails_generator/generators/components/performance_test/USAGE8
-rw-r--r--railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb16
-rw-r--r--railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb8
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb9
-rw-r--r--railties/lib/rails_generator/lookup.rb2
-rw-r--r--railties/lib/tasks/annotations.rake19
-rw-r--r--railties/lib/tasks/databases.rake34
-rw-r--r--railties/lib/tasks/misc.rake2
-rw-r--r--railties/lib/tasks/testing.rake14
-rw-r--r--railties/test/generators/rails_controller_generator_test.rb19
-rw-r--r--railties/test/generators/rails_scaffold_generator_test.rb42
-rw-r--r--railties/test/plugin_loader_test.rb2
247 files changed, 7500 insertions, 3276 deletions
diff --git a/.gitignore b/.gitignore
index 8c5dfb64e1..bba7d5dce8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
debug.log
+doc/rdoc
activeresource/doc
activerecord/doc
actionpack/doc
diff --git a/Rakefile b/Rakefile
index 11d205a725..352fd632d9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,4 +1,6 @@
require 'rake'
+require 'rake/rdoctask'
+require 'rake/contrib/sshpublisher'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
@@ -11,7 +13,7 @@ end
desc 'Run all tests by default'
task :default => :test
-%w(test rdoc package pgem release).each do |task_name|
+%w(test rdoc pgem package release).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
PROJECTS.each do |project|
@@ -19,3 +21,61 @@ task :default => :test
end
end
end
+
+
+desc "Generate documentation for the Rails framework"
+Rake::RDocTask.new do |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.template = ENV['template'] ? "#{ENV['template']}.rb" : './doc/template/horo'
+
+ rdoc.rdoc_files.include('railties/CHANGELOG')
+ rdoc.rdoc_files.include('railties/MIT-LICENSE')
+ rdoc.rdoc_files.include('railties/README')
+ rdoc.rdoc_files.include('railties/lib/{*.rb,commands/*.rb,rails/*.rb,rails_generator/*.rb}')
+
+ rdoc.rdoc_files.include('activerecord/README')
+ 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/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/CHANGELOG')
+ rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.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/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/CHANGELOG')
+ rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
+ rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
+end
+
+# Enhance rdoc task to copy referenced images also
+task :rdoc do
+ FileUtils.mkdir_p "doc/rdoc/files/examples/"
+ FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png"
+end
+
+desc "Publish API docs for Rails as a whole and for each component"
+task :pdoc => :rdoc do
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/api", "doc/rdoc").upload
+ PROJECTS.each do |project|
+ system %(cd #{project} && #{env} #{$0} pdoc)
+ end
+end
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index c09526cbef..612bd27774 100755
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -35,7 +35,7 @@ Rake::RDocTask.new { |rdoc|
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']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/action_mailer.rb')
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
@@ -76,12 +76,13 @@ end
desc "Publish the API documentation"
task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
end
desc "Publish the release files to RubyForge."
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index 1518e23dfe..5a71935009 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -426,7 +426,7 @@ module ActionMailer #:nodoc:
end
def template_root=(root)
- write_inheritable_attribute(:template_root, ActionView::ViewLoadPaths.new(Array(root)))
+ write_inheritable_attribute(:template_root, ActionView::PathSet.new(Array(root)))
end
end
diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb
index 9b7a4661b6..11058a770d 100644
--- a/actionmailer/test/abstract_unit.rb
+++ b/actionmailer/test/abstract_unit.rb
@@ -32,8 +32,7 @@ end
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)
- gem 'mocha', ">=0.5"
- require 'stubba'
+ gem 'mocha', ">=0.9.0"
yield
rescue Gem::LoadError
$stderr.puts "Skipping #{test_name} tests (Mocha >= 0.5 is required). `gem install mocha` and try again."
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 4a6e70a153..52d00a417c 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,62 @@
*Edge*
+* Get buffer for fragment cache from template's @output_buffer [Josh Peek]
+
+* Set config.action_view.warn_cache_misses = true to receive a warning if you perform an action that results in an expensive disk operation that could be cached [Josh Peek]
+
+* Refactor template preloading. New abstractions include Renderable mixins and a refactored Template class [Josh Peek]
+
+* Changed ActionView::TemplateHandler#render API method signature to render(template, local_assigns = {}) [Josh Peek]
+
+* Changed PrototypeHelper#submit_to_remote to PrototypeHelper#button_to_remote to stay consistent with link_to_remote (submit_to_remote still works as an alias) #8994 [clemens]
+
+* Add :recursive option to javascript_include_tag and stylesheet_link_tag to be used along with :all. #480 [Damian Janowski]
+
+* Allow users to disable the use of the Accept header [Michael Koziarski]
+
+ The accept header is poorly implemented by browsers and causes strange
+ errors when used on public sites where crawlers make requests too. You
+ can use formatted urls (e.g. /people/1.xml) to support API clients in a
+ much simpler way.
+
+ To disable the header you need to set:
+
+ config.action_controller.use_accept_header = false
+
+* Do not stat template files in production mode before rendering. You will no longer be able to modify templates in production mode without restarting the server [Josh Peek]
+
+* Deprecated TemplateHandler line offset [Josh Peek]
+
+* Allow caches_action to accept cache store options. #416. [José Valim]. Example:
+
+ caches_action :index, :redirected, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
+
+* Remove define_javascript_functions, javascript_include_tag and friends are far superior. [Michael Koziarski]
+
+* Deprecate :use_full_path render option. The supplying the option no longer has an effect [Josh Peek]
+
+* Add :as option to render a collection of partials with a custom local variable name. #509 [Simon Jefford, Pratik Naik]
+
+ render :partial => 'other_people', :collection => @people, :as => :person
+
+ This will let you access objects of @people as 'person' local variable inside 'other_people' partial template.
+
+* time_zone_select: support for regexp matching of priority zones. Resolves #195 [Ernie Miller]
+
+* Made ActionView::Base#render_file private [Josh Peek]
+
+* Refactor and simplify the implementation of assert_redirected_to. Arguments are now normalised relative to the controller being tested, not the root of the application. [Michael Koziarski]
+
+ This could cause some erroneous test failures if you were redirecting between controllers
+ in different namespaces and wrote your assertions relative to the root of the application.
+
+* Remove follow_redirect from controller functional tests.
+
+ If you want to follow redirects you can use integration tests. The functional test
+ version was only useful if you were using redirect_to :id=>...
+
+* Fix polymorphic_url with singleton resources. #461 [Tammer Saleh]
+
* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]
* Added block-call style to link_to [Sam Stephenson/DHH]. Example:
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index b37f756c1e..1880f21f67 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -49,12 +49,14 @@ Rake::RDocTask.new { |rdoc|
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']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
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('lib/**/*.rb')
+ rdoc.rdoc_files.include(Dir['lib/**/*.rb'] -
+ Dir['lib/*/vendor/**/*.rb'])
+ rdoc.rdoc_files.exclude('lib/actionpack.rb')
end
}
@@ -132,13 +134,13 @@ task :update_js => [ :update_scriptaculous ]
desc "Publish the API documentation"
task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ap", "doc").upload
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
end
desc "Publish the release files to RubyForge."
diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb
index 3deda0b45a..765225ae24 100644
--- a/actionpack/lib/action_controller/assertions/response_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -56,74 +56,24 @@ module ActionController
# # assert that the redirection was to the named route login_url
# assert_redirected_to login_url
#
+ # # assert that the redirection was to the url for @customer
+ # assert_redirected_to @customer
+ #
def assert_redirected_to(options = {}, message=nil)
clean_backtrace do
assert_response(:redirect, message)
return true if options == @response.redirected_to
- ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
-
- begin
- url = {}
- original = { :expected => options, :actual => @response.redirected_to.is_a?(Symbol) ? @response.redirected_to : @response.redirected_to.dup }
- original.each do |key, value|
- if value.is_a?(Symbol)
- value = @controller.respond_to?(value, true) ? @controller.send(value) : @controller.send("hash_for_#{value}_url")
- end
-
- unless value.is_a?(Hash)
- request = case value
- when NilClass then nil
- when /^\w+:\/\// then recognized_request_for(%r{^(\w+://.*?(/|$|\?))(.*)$} =~ value ? $3 : nil)
- else recognized_request_for(value)
- end
- value = request.path_parameters if request
- end
-
- if value.is_a?(Hash) # stringify 2 levels of hash keys
- if name = value.delete(:use_route)
- route = ActionController::Routing::Routes.named_routes[name]
- value.update(route.parameter_shell)
- end
-
- value.stringify_keys!
- value.values.select { |v| v.is_a?(Hash) }.collect { |v| v.stringify_keys! }
- if key == :expected && value['controller'] == @controller.controller_name && original[:actual].is_a?(Hash)
- original[:actual].stringify_keys!
- value.delete('controller') if original[:actual]['controller'].nil? || original[:actual]['controller'] == value['controller']
- end
- end
-
- if value.respond_to?(:[]) && value['controller']
- value['controller'] = value['controller'].to_s
- if key == :actual && value['controller'].first != '/' && !value['controller'].include?('/')
- new_controller_path = ActionController::Routing.controller_relative_to(value['controller'], @controller.class.controller_path)
- value['controller'] = new_controller_path if value['controller'] != new_controller_path && ActionController::Routing.possible_controllers.include?(new_controller_path) && @response.redirected_to.is_a?(Hash)
- end
- value['controller'] = value['controller'][1..-1] if value['controller'].first == '/' # strip leading hash
- end
- url[key] = value
- end
-
- @response_diff = url[:actual].diff(url[:expected]) if url[:actual]
- msg = build_message(message, "expected a redirect to <?>, found one to <?>, a difference of <?> ", url[:expected], url[:actual], @response_diff)
-
- assert_block(msg) do
- url[:expected].keys.all? do |k|
- if k == :controller then url[:expected][k] == ActionController::Routing.controller_relative_to(url[:actual][k], @controller.class.controller_path)
- else parameterize(url[:expected][k]) == parameterize(url[:actual][k])
- end
- end
- end
- rescue ActionController::RoutingError # routing failed us, so match the strings only.
- msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
- url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
- eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
- u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
- [u, (p.first == '/') ? p : '/' + p]
- end.flatten
+
+ # Support partial arguments for hash redirections
+ if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
+ return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
+ end
+
+ redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
+ options_after_normalisation = normalize_argument_to_redirection(options)
- assert_equal(eurl, url, msg) if eurl && url
- assert_equal(epath, path, msg) if epath && path
+ if redirected_to_after_normalisation != options_after_normalisation
+ flunk "Expected response to be a redirect to <#{options_after_normalisation}> but was a redirect to <#{redirected_to_after_normalisation}>"
end
end
end
@@ -137,36 +87,37 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
- rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
+ rendered = @response.rendered_template
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
- !@response.rendered_with_file?
+ @response.rendered_template.nil?
else
- expected == rendered
+ rendered.to_s.match(expected)
end
end
end
end
private
- # Recognizes the route for a given path.
- def recognized_request_for(path, request_method = nil)
- path = "/#{path}" unless path.first == '/'
-
- # Assume given controller
- request = ActionController::TestRequest.new({}, {}, nil)
- request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
- request.path = path
-
- ActionController::Routing::Routes.recognize(request)
- request
- end
# Proxy to to_param if the object will respond to it.
def parameterize(value)
value.respond_to?(:to_param) ? value.to_param : value
end
+
+ def normalize_argument_to_redirection(fragment)
+ after_routing = @controller.url_for(fragment)
+ if after_routing =~ %r{^\w+://.*}
+ after_routing
+ else
+ # FIXME - this should probably get removed.
+ if after_routing.first != '/'
+ after_routing = '/' + after_routing
+ end
+ @request.protocol + @request.host_with_port + after_routing
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index bf34edcd85..df94f78f18 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -340,6 +340,16 @@ module ActionController #:nodoc:
cattr_accessor :optimise_named_routes
self.optimise_named_routes = true
+ # Indicates whether the response format should be determined by examining the Accept HTTP header,
+ # or by using the simpler params + ajax rules.
+ #
+ # If this is set to +true+ (the default) then +respond_to+ and +Request#format+ will take the Accept
+ # header into account. If it is set to false then the request format will be determined solely
+ # by examining params[:format]. If params format is missing, the format will be either HTML or
+ # Javascript depending on whether the request is an AJAX request.
+ cattr_accessor :use_accept_header
+ self.use_accept_header = true
+
# Controls whether request forgergy protection is turned on or not. Turned off by default only in test mode.
class_inheritable_accessor :allow_forgery_protection
self.allow_forgery_protection = true
@@ -402,7 +412,7 @@ module ActionController #:nodoc:
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
unless read_inheritable_attribute(:hidden_actions)
- write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map(&:to_s))
+ write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
end
read_inheritable_attribute(:hidden_actions)
@@ -410,18 +420,18 @@ module ActionController #:nodoc:
# Hide each of the given methods from being callable as actions.
def hide_action(*names)
- write_inheritable_attribute(:hidden_actions, hidden_actions | names.map(&:to_s))
+ write_inheritable_attribute(:hidden_actions, hidden_actions | names.map { |name| name.to_s })
end
- ## View load paths determine the bases from which template references can be made. So a call to
- ## render("test/template") will be looked up in the view load paths array and the closest match will be
- ## returned.
+ # View load paths determine the bases from which template references can be made. So a call to
+ # render("test/template") will be looked up in the view load paths array and the closest match will be
+ # returned.
def view_paths
@view_paths || superclass.view_paths
end
def view_paths=(value)
- @view_paths = ActionView::ViewLoadPaths.new(Array(value)) if value
+ @view_paths = ActionView::Base.process_view_paths(value) if value
end
# Adds a view_path to the front of the view_paths array.
@@ -603,7 +613,8 @@ module ActionController #:nodoc:
#
# This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
# would have slashed-off the path components after the changed action.
- def url_for(options = {}) #:doc:
+ def url_for(options = {})
+ options ||= {}
case options
when String
options
@@ -641,7 +652,7 @@ module ActionController #:nodoc:
end
def view_paths=(value)
- @template.view_paths = ViewLoadPaths.new(value)
+ @template.view_paths = ActionView::Base.process_view_paths(value)
end
# Adds a view_path to the front of the view_paths array.
@@ -702,6 +713,9 @@ module ActionController #:nodoc:
# # builds the complete response.
# render :partial => "person", :collection => @winners
#
+ # # Renders a collection of partials but with a custom local variable name
+ # render :partial => "admin_person", :collection => @winners, :as => :person
+ #
# # Renders the same collection of partials, but also renders the
# # person_divider partial between each person partial.
# render :partial => "person", :collection => @winners, :spacer_template => "person_divider"
@@ -733,6 +747,9 @@ module ActionController #:nodoc:
# # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.erb)
# render :template => "weblog/show"
#
+ # # Renders the template with a local variable
+ # render :template => "weblog/show", :locals => {:customer => Customer.new}
+ #
# === Rendering a file
#
# File rendering works just like action rendering except that it takes a filesystem path. By default, the path
@@ -852,22 +869,21 @@ module ActionController #:nodoc:
else
if file = options[:file]
- render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
+ render_for_file(file, options[:status], nil, options[:locals] || {})
elsif template = options[:template]
- render_for_file(template, options[:status], true)
+ render_for_file(template, options[:status], true, options[:locals] || {})
elsif inline = options[:inline]
add_variables_to_assigns
- tmpl = ActionView::InlineTemplate.new(@template, options[:inline], options[:locals], options[:type])
- render_for_text(@template.render_template(tmpl), options[:status])
+ render_for_text(@template.render(options), options[:status])
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
- render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
+ render_with_a_layout(:file => template, :status => options[:status], :layout => true)
else
- render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
+ render_with_no_layout(:file => template, :status => options[:status])
end
elsif xml = options[:xml]
@@ -887,12 +903,12 @@ module ActionController #:nodoc:
if collection = options[:collection]
render_for_text(
@template.send!(:render_partial_collection, partial, collection,
- options[:spacer_template], options[:locals]), options[:status]
+ options[:spacer_template], options[:locals], options[:as]), options[:status]
)
else
render_for_text(
@template.send!(:render_partial, partial,
- ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status]
+ options[:object], options[:locals]), options[:status]
)
end
@@ -1037,29 +1053,31 @@ module ActionController #:nodoc:
status = 302
end
+ response.redirected_to= options
+ logger.info("Redirected to #{options}") if logger && logger.info?
+
case options
when %r{^\w+://.*}
- raise DoubleRenderError if performed?
- logger.info("Redirected to #{options}") if logger && logger.info?
- response.redirect(options, interpret_status(status))
- response.redirected_to = options
- @performed_redirect = true
-
+ redirect_to_full_url(options, status)
when String
- redirect_to(request.protocol + request.host_with_port + options, :status=>status)
-
+ redirect_to_full_url(request.protocol + request.host_with_port + options, status)
when :back
- request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"], :status=>status) : raise(RedirectBackError)
-
- when Hash
- redirect_to(url_for(options), :status=>status)
- response.redirected_to = options
-
+ if referer = request.headers["Referer"]
+ redirect_to(referer, :status=>status)
+ else
+ raise RedirectBackError
+ end
else
- redirect_to(url_for(options), :status=>status)
+ redirect_to_full_url(url_for(options), status)
end
end
+ def redirect_to_full_url(url, status)
+ raise DoubleRenderError if performed?
+ response.redirect(url, interpret_status(status))
+ @performed_redirect = true
+ end
+
# Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
# intermediate caches shouldn't cache the response.
#
@@ -1092,10 +1110,10 @@ module ActionController #:nodoc:
private
- def render_for_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
+ def render_for_file(template_path, status = nil, use_full_path = nil, locals = {}) #:nodoc:
add_variables_to_assigns
logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
- render_for_text(@template.render_file(template_path, use_full_path, locals), status)
+ render_for_text(@template.render(:file => template_path, :locals => locals), status)
end
def render_for_text(text = nil, status = nil, append_response = false) #:nodoc:
@@ -1183,7 +1201,7 @@ module ActionController #:nodoc:
end
def self.action_methods
- @action_methods ||= Set.new(public_instance_methods.map(&:to_s)) - hidden_actions
+ @action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
end
def add_variables_to_assigns
@@ -1230,8 +1248,8 @@ module ActionController #:nodoc:
end
def template_exempt_from_layout?(template_name = default_template_name)
- template_name = @template.send(:template_file_from_name, template_name) if @template
- @@exempt_from_layout.any? { |ext| template_name.to_s =~ ext }
+ template_name = @template.pick_template(template_name).to_s if @template
+ @@exempt_from_layout.any? { |ext| template_name =~ ext }
end
def default_template_name(action_name = self.action_name)
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 65a36f7f98..f3535f8330 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -27,13 +27,15 @@ module ActionController #:nodoc:
# You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy
# for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance.
#
- # And you can also use :if to pass a Proc that specifies when the action should be cached.
+ # And you can also use :if (or :unless) to pass a Proc that specifies when the action should be cached.
+ #
+ # Finally, if you are using memcached, you can also pass :expires_in.
#
# class ListsController < ApplicationController
# before_filter :authenticate, :except => :public
# caches_page :public
# caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request
- # caches_action :show, :cache_path => { :project => 1 }
+ # caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
# controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
@@ -56,8 +58,10 @@ module ActionController #:nodoc:
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
- cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path))
- around_filter(cache_filter, {:only => actions}.merge(options))
+ filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
+
+ cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
+ around_filter(cache_filter, filter_options)
end
end
@@ -80,8 +84,8 @@ module ActionController #:nodoc:
end
def before(controller)
- cache_path = ActionCachePath.new(controller, path_options_for(controller, @options))
- if cache = controller.read_fragment(cache_path.path)
+ cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
+ if cache = controller.read_fragment(cache_path.path, @options[:store_options])
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
options = { :text => cache }
@@ -96,7 +100,7 @@ module ActionController #:nodoc:
def after(controller)
return if controller.rendered_action_cache || !caching_allowed(controller)
action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
- controller.write_fragment(controller.action_cache_path.path, action_content)
+ controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options])
end
private
@@ -162,10 +166,7 @@ module ActionController #:nodoc:
# If there's no extension in the path, check request.format
if extension.nil?
- extension = request.format.to_sym.to_s
- if extension=='all'
- extension = nil
- end
+ extension = request.cache_format
end
extension
end
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index e4f5de44ab..b1f25fdf5c 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -60,17 +60,17 @@ module ActionController #:nodoc:
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
- def fragment_for(block, name = {}, options = nil) #:nodoc:
- unless perform_caching then block.call; return end
-
- buffer = yield
-
- if cache = read_fragment(name, options)
- buffer.concat(cache)
+ def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc:
+ if perform_caching
+ if cache = read_fragment(name, options)
+ buffer.concat(cache)
+ else
+ pos = buffer.length
+ block.call
+ write_fragment(name, buffer[pos..-1], options)
+ end
else
- pos = buffer.length
block.call
- write_fragment(name, buffer[pos..-1], options)
end
end
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
index 60d92d9b98..10dc0cc45b 100644
--- a/actionpack/lib/action_controller/filters.rb
+++ b/actionpack/lib/action_controller/filters.rb
@@ -7,6 +7,225 @@ module ActionController #:nodoc:
end
end
+ class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
+ def append_filter_to_chain(filters, filter_type, &block)
+ pos = find_filter_append_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def prepend_filter_to_chain(filters, filter_type, &block)
+ pos = find_filter_prepend_position(filters, filter_type)
+ update_filter_chain(filters, filter_type, pos, &block)
+ end
+
+ def create_filters(filters, filter_type, &block)
+ filters, conditions = extract_options(filters, &block)
+ filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
+ filters
+ end
+
+ def skip_filter_in_chain(*filters, &test)
+ filters, conditions = extract_options(filters)
+ filters.each do |filter|
+ if callback = find(filter) then delete(callback) end
+ end if conditions.empty?
+ update_filter_in_chain(filters, :skip => conditions, &test)
+ end
+
+ private
+ def update_filter_chain(filters, filter_type, pos, &block)
+ new_filters = create_filters(filters, filter_type, &block)
+ insert(pos, new_filters).flatten!
+ end
+
+ def find_filter_append_position(filters, filter_type)
+ # appending an after filter puts it at the end of the call chain
+ # before and around filters go before the first after filter in the chain
+ unless filter_type == :after
+ each_with_index do |f,i|
+ return i if f.after?
+ end
+ end
+ return -1
+ end
+
+ def find_filter_prepend_position(filters, filter_type)
+ # prepending a before or around filter puts it at the front of the call chain
+ # after filters go before the first after filter in the chain
+ if filter_type == :after
+ each_with_index do |f,i|
+ return i if f.after?
+ end
+ return -1
+ end
+ return 0
+ end
+
+ def find_or_create_filter(filter, filter_type, options = {})
+ update_filter_in_chain([filter], options)
+
+ if found_filter = find(filter) { |f| f.type == filter_type }
+ found_filter
+ else
+ filter_kind = case
+ when filter.respond_to?(:before) && filter_type == :before
+ :before
+ when filter.respond_to?(:after) && filter_type == :after
+ :after
+ else
+ :filter
+ end
+
+ case filter_type
+ when :before
+ BeforeFilter.new(filter_kind, filter, options)
+ when :after
+ AfterFilter.new(filter_kind, filter, options)
+ else
+ AroundFilter.new(filter_kind, filter, options)
+ end
+ end
+ end
+
+ def update_filter_in_chain(filters, options, &test)
+ filters.map! { |f| block_given? ? find(f, &test) : find(f) }
+ filters.compact!
+
+ map! do |filter|
+ if filters.include?(filter)
+ new_filter = filter.dup
+ new_filter.update_options!(options)
+ new_filter
+ else
+ filter
+ end
+ end
+ end
+ end
+
+ class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
+ def initialize(kind, method, options = {})
+ super
+ update_options! options
+ end
+
+ def before?
+ self.class == BeforeFilter
+ end
+
+ def after?
+ self.class == AfterFilter
+ end
+
+ def around?
+ self.class == AroundFilter
+ end
+
+ # Make sets of strings from :only/:except options
+ def update_options!(other)
+ if other
+ convert_only_and_except_options_to_sets_of_strings(other)
+ if other[:skip]
+ convert_only_and_except_options_to_sets_of_strings(other[:skip])
+ end
+ end
+
+ options.update(other)
+ end
+
+ private
+ def should_not_skip?(controller)
+ if options[:skip]
+ !included_in_action?(controller, options[:skip])
+ else
+ true
+ end
+ end
+
+ def included_in_action?(controller, options)
+ if options[:only]
+ options[:only].include?(controller.action_name)
+ elsif options[:except]
+ !options[:except].include?(controller.action_name)
+ else
+ true
+ end
+ end
+
+ def should_run_callback?(controller)
+ should_not_skip?(controller) && included_in_action?(controller, options) && super
+ end
+
+ def convert_only_and_except_options_to_sets_of_strings(opts)
+ [:only, :except].each do |key|
+ if values = opts[key]
+ opts[key] = Array(values).map(&:to_s).to_set
+ end
+ end
+ end
+ end
+
+ class AroundFilter < Filter #:nodoc:
+ def type
+ :around
+ end
+
+ def call(controller, &block)
+ if should_run_callback?(controller)
+ method = filter_responds_to_before_and_after? ? around_proc : self.method
+
+ # For around_filter do |controller, action|
+ if method.is_a?(Proc) && method.arity == 2
+ evaluate_method(method, controller, block)
+ else
+ evaluate_method(method, controller, &block)
+ end
+ else
+ block.call
+ end
+ end
+
+ private
+ def filter_responds_to_before_and_after?
+ method.respond_to?(:before) && method.respond_to?(:after)
+ end
+
+ def around_proc
+ Proc.new do |controller, action|
+ method.before(controller)
+
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ else
+ begin
+ action.call
+ ensure
+ method.after(controller)
+ end
+ end
+ end
+ end
+ end
+
+ class BeforeFilter < Filter #:nodoc:
+ def type
+ :before
+ end
+
+ def call(controller, &block)
+ super
+ if controller.send!(:performed?)
+ controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
+ end
+ end
+ end
+
+ class AfterFilter < Filter #:nodoc:
+ def type
+ :after
+ end
+ end
+
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
# authentication, caching, or auditing before the intended action is performed. Or to do localization or output
# compression after the action has been performed. Filters have access to the request, response, and all the instance
@@ -245,201 +464,6 @@ module ActionController #:nodoc:
# filter and controller action will not be run. If +before+ renders or redirects,
# the second half of +around+ and will still run but +after+ and the
# action will not. If +around+ fails to yield, +after+ will not be run.
-
- class FilterChain < ActiveSupport::Callbacks::CallbackChain #:nodoc:
- def append_filter_to_chain(filters, filter_type, &block)
- pos = find_filter_append_position(filters, filter_type)
- update_filter_chain(filters, filter_type, pos, &block)
- end
-
- def prepend_filter_to_chain(filters, filter_type, &block)
- pos = find_filter_prepend_position(filters, filter_type)
- update_filter_chain(filters, filter_type, pos, &block)
- end
-
- def create_filters(filters, filter_type, &block)
- filters, conditions = extract_options(filters, &block)
- filters.map! { |filter| find_or_create_filter(filter, filter_type, conditions) }
- filters
- end
-
- def skip_filter_in_chain(*filters, &test)
- filters, conditions = extract_options(filters)
- filters.each do |filter|
- if callback = find(filter) then delete(callback) end
- end if conditions.empty?
- update_filter_in_chain(filters, :skip => conditions, &test)
- end
-
- private
- def update_filter_chain(filters, filter_type, pos, &block)
- new_filters = create_filters(filters, filter_type, &block)
- insert(pos, new_filters).flatten!
- end
-
- def find_filter_append_position(filters, filter_type)
- # appending an after filter puts it at the end of the call chain
- # before and around filters go before the first after filter in the chain
- unless filter_type == :after
- each_with_index do |f,i|
- return i if f.after?
- end
- end
- return -1
- end
-
- def find_filter_prepend_position(filters, filter_type)
- # prepending a before or around filter puts it at the front of the call chain
- # after filters go before the first after filter in the chain
- if filter_type == :after
- each_with_index do |f,i|
- return i if f.after?
- end
- return -1
- end
- return 0
- end
-
- def find_or_create_filter(filter, filter_type, options = {})
- update_filter_in_chain([filter], options)
-
- if found_filter = find(filter) { |f| f.type == filter_type }
- found_filter
- else
- filter_kind = case
- when filter.respond_to?(:before) && filter_type == :before
- :before
- when filter.respond_to?(:after) && filter_type == :after
- :after
- else
- :filter
- end
-
- case filter_type
- when :before
- BeforeFilter.new(filter_kind, filter, options)
- when :after
- AfterFilter.new(filter_kind, filter, options)
- else
- AroundFilter.new(filter_kind, filter, options)
- end
- end
- end
-
- def update_filter_in_chain(filters, options, &test)
- filters.map! { |f| block_given? ? find(f, &test) : find(f) }
- filters.compact!
-
- map! do |filter|
- if filters.include?(filter)
- new_filter = filter.dup
- new_filter.options.merge!(options)
- new_filter
- else
- filter
- end
- end
- end
- end
-
- class Filter < ActiveSupport::Callbacks::Callback #:nodoc:
- def before?
- self.class == BeforeFilter
- end
-
- def after?
- self.class == AfterFilter
- end
-
- def around?
- self.class == AroundFilter
- end
-
- private
- def should_not_skip?(controller)
- if options[:skip]
- !included_in_action?(controller, options[:skip])
- else
- true
- end
- end
-
- def included_in_action?(controller, options)
- if options[:only]
- Array(options[:only]).map(&:to_s).include?(controller.action_name)
- elsif options[:except]
- !Array(options[:except]).map(&:to_s).include?(controller.action_name)
- else
- true
- end
- end
-
- def should_run_callback?(controller)
- should_not_skip?(controller) && included_in_action?(controller, options) && super
- end
- end
-
- class AroundFilter < Filter #:nodoc:
- def type
- :around
- end
-
- def call(controller, &block)
- if should_run_callback?(controller)
- method = filter_responds_to_before_and_after? ? around_proc : self.method
-
- # For around_filter do |controller, action|
- if method.is_a?(Proc) && method.arity == 2
- evaluate_method(method, controller, block)
- else
- evaluate_method(method, controller, &block)
- end
- else
- block.call
- end
- end
-
- private
- def filter_responds_to_before_and_after?
- method.respond_to?(:before) && method.respond_to?(:after)
- end
-
- def around_proc
- Proc.new do |controller, action|
- method.before(controller)
-
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
- else
- begin
- action.call
- ensure
- method.after(controller)
- end
- end
- end
- end
- end
-
- class BeforeFilter < Filter #:nodoc:
- def type
- :before
- end
-
- def call(controller, &block)
- super
- if controller.send!(:performed?)
- controller.send!(:halt_filter_chain, method, :rendered_or_redirected)
- end
- end
- end
-
- class AfterFilter < Filter #:nodoc:
- def type
- :after
- end
- end
-
module ClassMethods
# The passed <tt>filters</tt> will be appended to the filter_chain and
# will execute before the action on this controller is performed.
diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb
index 0721f71498..8b6febe254 100644
--- a/actionpack/lib/action_controller/layout.rb
+++ b/actionpack/lib/action_controller/layout.rb
@@ -254,7 +254,7 @@ module ActionController #:nodoc:
@template.instance_variable_set("@content_for_layout", content_for_layout)
response.layout = layout
status = template_with_options ? options[:status] : nil
- render_for_text(@template.render_file(layout, true), status)
+ render_for_text(@template.render(layout), status)
else
render_with_no_layout(options, extra_options, &block)
end
@@ -304,7 +304,7 @@ module ActionController #:nodoc:
end
def layout_directory?(layout_name)
- @template.view_paths.find_template_file_for_path("#{File.join('layouts', layout_name)}.#{@template.template_format}.erb") ? true : false
+ @template.file_exists?("#{File.join('layouts', layout_name)}.#{@template.template_format}")
end
end
end
diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb
index 1dbd8b9e6f..29294476f7 100644
--- a/actionpack/lib/action_controller/mime_responds.rb
+++ b/actionpack/lib/action_controller/mime_responds.rb
@@ -114,7 +114,11 @@ module ActionController #:nodoc:
@request = controller.request
@response = controller.response
- @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
+ if ActionController::Base.use_accept_header
+ @mime_type_priority = Array(Mime::Type.lookup_by_extension(@request.parameters[:format]) || @request.accepts)
+ else
+ @mime_type_priority = [@request.format]
+ end
@order = []
@responses = {}
diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb
index fa123f7808..a7215e6ea3 100644
--- a/actionpack/lib/action_controller/mime_type.rb
+++ b/actionpack/lib/action_controller/mime_type.rb
@@ -72,57 +72,61 @@ module Mime
end
def parse(accept_header)
- # keep track of creation order to keep the subsequent sort stable
- list = []
- accept_header.split(/,/).each_with_index do |header, index|
- params, q = header.split(/;\s*q=/)
- if params
- params.strip!
- list << AcceptItem.new(index, params, q) unless params.empty?
+ if accept_header !~ /,/
+ [Mime::Type.lookup(accept_header)]
+ else
+ # keep track of creation order to keep the subsequent sort stable
+ list = []
+ accept_header.split(/,/).each_with_index do |header, index|
+ params, q = header.split(/;\s*q=/)
+ if params
+ params.strip!
+ list << AcceptItem.new(index, params, q) unless params.empty?
+ end
end
- end
- list.sort!
+ list.sort!
- # Take care of the broken text/xml entry by renaming or deleting it
- text_xml = list.index("text/xml")
- app_xml = list.index(Mime::XML.to_s)
+ # Take care of the broken text/xml entry by renaming or deleting it
+ text_xml = list.index("text/xml")
+ app_xml = list.index(Mime::XML.to_s)
- if text_xml && app_xml
- # set the q value to the max of the two
- list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
+ if text_xml && app_xml
+ # set the q value to the max of the two
+ list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
- # make sure app_xml is ahead of text_xml in the list
- if app_xml > text_xml
- list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
- app_xml, text_xml = text_xml, app_xml
- end
+ # make sure app_xml is ahead of text_xml in the list
+ if app_xml > text_xml
+ list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
+ app_xml, text_xml = text_xml, app_xml
+ end
- # delete text_xml from the list
- list.delete_at(text_xml)
+ # delete text_xml from the list
+ list.delete_at(text_xml)
- elsif text_xml
- list[text_xml].name = Mime::XML.to_s
- end
+ elsif text_xml
+ list[text_xml].name = Mime::XML.to_s
+ end
- # Look for more specific XML-based types and sort them ahead of app/xml
+ # Look for more specific XML-based types and sort them ahead of app/xml
- if app_xml
- idx = app_xml
- app_xml_type = list[app_xml]
+ if app_xml
+ idx = app_xml
+ app_xml_type = list[app_xml]
- while(idx < list.length)
- type = list[idx]
- break if type.q < app_xml_type.q
- if type.name =~ /\+xml$/
- list[app_xml], list[idx] = list[idx], list[app_xml]
- app_xml = idx
+ while(idx < list.length)
+ type = list[idx]
+ break if type.q < app_xml_type.q
+ if type.name =~ /\+xml$/
+ list[app_xml], list[idx] = list[idx], list[app_xml]
+ app_xml = idx
+ end
+ idx += 1
end
- idx += 1
end
- end
- list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
- list
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ list
+ end
end
end
diff --git a/actionpack/lib/action_controller/mime_types.rb b/actionpack/lib/action_controller/mime_types.rb
index 01a266d3fe..2d7fba1173 100644
--- a/actionpack/lib/action_controller/mime_types.rb
+++ b/actionpack/lib/action_controller/mime_types.rb
@@ -17,4 +17,5 @@ Mime::Type.register "multipart/form-data", :multipart_form
Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form
# http://www.ietf.org/rfc/rfc4627.txt
-Mime::Type.register "application/json", :json, %w( text/x-json ) \ No newline at end of file
+# http://www.json.org/JSONRequest.html
+Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) \ No newline at end of file
diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb
index 509fa6a08e..7c30bf0778 100644
--- a/actionpack/lib/action_controller/polymorphic_routes.rb
+++ b/actionpack/lib/action_controller/polymorphic_routes.rb
@@ -48,6 +48,9 @@ module ActionController
#
# # 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"
#
# ==== Options
#
@@ -83,8 +86,6 @@ module ActionController
else [ record_or_hash_or_array ]
end
- args << format if format
-
inflection =
case
when options[:action].to_s == "new"
@@ -96,6 +97,9 @@ module ActionController
else
:singular
end
+
+ args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
+ args << format if format
named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options)
send!(named_route, *args)
@@ -136,11 +140,19 @@ module ActionController
else
record = records.pop
route = records.inject("") do |string, parent|
- string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
+ if parent.is_a?(Symbol) || parent.is_a?(String)
+ string << "#{parent}_"
+ else
+ string << "#{RecordIdentifier.send!("singular_class_name", parent)}_"
+ end
end
end
- route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
+ if record.is_a?(Symbol) || record.is_a?(String)
+ route << "#{record}_"
+ else
+ route << "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
+ end
action_prefix(options) + namespace + route + routing_type(options).to_s
end
@@ -163,16 +175,17 @@ module ActionController
end
end
+ # Remove the first symbols from the array and return the url prefix
+ # implied by those symbols.
def extract_namespace(record_or_hash_or_array)
- returning "" do |namespace|
- if record_or_hash_or_array.is_a?(Array)
- record_or_hash_or_array.delete_if do |record_or_namespace|
- if record_or_namespace.is_a?(String) || record_or_namespace.is_a?(Symbol)
- namespace << "#{record_or_namespace}_"
- end
- end
- end
+ return "" unless record_or_hash_or_array.is_a?(Array)
+
+ namespace_keys = []
+ while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol)
+ namespace_keys << record_or_hash_or_array.shift
end
+
+ namespace_keys.map {|k| "#{k}_"}.join
end
end
end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 9b4aa9b7cf..01bc1ebb26 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -24,6 +24,19 @@ module ActionController #:nodoc:
super()
end
+ %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
+ PATH_TRANSLATED QUERY_STRING REMOTE_HOST
+ REMOTE_IDENT REMOTE_USER SCRIPT_NAME
+ SERVER_NAME SERVER_PROTOCOL
+
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
+ define_method(env.sub(/^HTTP_/n, '').downcase) do
+ @env[env]
+ end
+ end
+
# The request body is an IO input stream. If the RAW_POST_DATA environment
# variable is already set, wrap it in a StringIO.
def body
@@ -35,7 +48,7 @@ module ActionController #:nodoc:
end
def key?(key)
- @env.key? key
+ @env.key?(key)
end
def query_parameters
@@ -85,6 +98,18 @@ module ActionController #:nodoc:
@env['REMOTE_ADDR']
end
+ def request_method
+ @env['REQUEST_METHOD'].downcase.to_sym
+ end
+
+ def server_port
+ @env['SERVER_PORT'].to_i
+ end
+
+ def server_software
+ @env['SERVER_SOFTWARE'].split("/").first
+ end
+
def session
unless defined?(@session)
if @session_options == false
@@ -178,9 +203,9 @@ end_msg
normalize_headers(@headers)
if [204, 304].include?(@status.to_i)
@headers.delete "Content-Type"
- [status.to_i, @headers.to_hash, []]
+ [status, @headers.to_hash, []]
else
- [status.to_i, @headers.to_hash, self]
+ [status, @headers.to_hash, self]
end
end
alias to_a out
@@ -225,8 +250,8 @@ end_msg
headers['Content-Language'] = options.delete('language') if options['language']
headers['Expires'] = options.delete('expires') if options['expires']
- @status = options.delete('Status') if options['Status']
- @status ||= 200
+ @status = options['Status'] || "200 OK"
+
# Convert 'cookie' header to 'Set-Cookie' headers.
# Because Set-Cookie header can appear more the once in the response body,
# we store it in a line break seperated string that will be translated to
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 9b02f2c8a1..2d9f6c3e6f 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -82,21 +82,34 @@ module ActionController
# Returns the accepted MIME type for the request
def accepts
@accepts ||=
- if @env['HTTP_ACCEPT'].to_s.strip.empty?
- [ content_type, Mime::ALL ].compact # make sure content_type being nil is not included
- else
- Mime::Type.parse(@env['HTTP_ACCEPT'])
+ begin
+ header = @env['HTTP_ACCEPT'].to_s.strip
+
+ if header.empty?
+ [content_type, Mime::ALL].compact
+ else
+ Mime::Type.parse(header)
+ end
end
end
- # Returns the Mime type for the format used in the request. If there is no format available, the first of the
- # accept types will be used. Examples:
+ # Returns the Mime type for the format used in the request.
#
# GET /posts/5.xml | request.format => Mime::XML
# GET /posts/5.xhtml | request.format => Mime::HTML
- # GET /posts/5 | request.format => request.accepts.first (usually Mime::HTML for browsers)
+ # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
- @format ||= parameters[:format] ? Mime::Type.lookup_by_extension(parameters[:format]) : accepts.first
+ @format ||= begin
+ if parameters[:format]
+ Mime::Type.lookup_by_extension(parameters[:format])
+ elsif ActionController::Base.use_accept_header
+ accepts.first
+ elsif xhr?
+ Mime::Type.lookup_by_extension("js")
+ else
+ Mime::Type.lookup_by_extension("html")
+ end
+ end
end
@@ -116,6 +129,26 @@ module ActionController
@format = Mime::Type.lookup_by_extension(parameters[:format])
end
+ # Returns a symbolized version of the <tt>:format</tt> parameter of the request.
+ # If no format is given it returns <tt>:js</tt>for AJAX requests and <tt>:html</tt>
+ # otherwise.
+ def template_format
+ parameter_format = parameters[:format]
+
+ if parameter_format
+ parameter_format.to_sym
+ elsif xhr?
+ :js
+ else
+ :html
+ end
+ end
+
+ def cache_format
+ parameter_format = parameters[:format]
+ parameter_format && parameter_format.to_sym
+ end
+
# Returns true if the request's "X-Requested-With" header contains
# "XMLHttpRequest". (The Prototype Javascript library sends this header with
# every Ajax request.)
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 40ef4ea044..163ed87fbb 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -178,7 +178,7 @@ module ActionController #:nodoc:
@template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
@template.send!(:assign_variables_from_controller)
- @template.instance_variable_set("@contents", @template.render_file(template_path_for_local_rescue(exception), false))
+ @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception), :use_full_path => false))
response.content_type = Mime::HTML
render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index 9fb1f9fa39..af2fcaf3ad 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -72,7 +72,7 @@ module ActionController
end
def conditions
- @conditions = @options[:conditions] || {}
+ @conditions ||= @options[:conditions] || {}
end
def path
@@ -80,9 +80,9 @@ module ActionController
end
def new_path
- new_action = self.options[:path_names][:new] if self.options[:path_names]
+ new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action ||= Base.resources_path_names[:new]
- @new_path ||= "#{path}/#{new_action}"
+ @new_path ||= "#{path}/#{new_action}"
end
def member_path
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index 8846dcc504..dfbaa53b7c 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -88,6 +88,10 @@ module ActionController
#
# map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
#
+ # Note: The default routes, as provided by the Rails generator, make all actions in every
+ # controller accessible via GET requests. You should consider removing them or commenting
+ # them out if you're using named routes and resources.
+ #
# == Named routes
#
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index 4740113ed0..b8323847fd 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -67,10 +67,9 @@ module ActionController
options = options.dup
if options[:namespace]
- options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
+ options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
options.delete(:path_prefix)
options.delete(:name_prefix)
- options.delete(:namespace)
end
requirements = (options.delete(:requirements) || {}).dup
diff --git a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
index 032f945ed2..385c6c1b09 100644
--- a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
@@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>
-<%= render_file(@rescues_path + "/_trace.erb", false) %>
+<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
-<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
+<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
diff --git a/actionpack/lib/action_controller/templates/rescues/template_error.erb b/actionpack/lib/action_controller/templates/rescues/template_error.erb
index eda64db3e9..4aecc68d18 100644
--- a/actionpack/lib/action_controller/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_controller/templates/rescues/template_error.erb
@@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
-<%= render_file(@rescues_path + "/_trace.erb", false) %>
+<%= render(:file => @rescues_path + "/_trace.erb", :use_full_path => false) %>
<% @exception = @real_exception %>
-<%= render_file(@rescues_path + "/_request_and_response.erb", false) %>
+<%= render(:file => @rescues_path + "/_request_and_response.erb", :use_full_path => false) %>
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 0cf143210d..a6e0c98936 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -205,21 +205,10 @@ module ActionController #:nodoc:
p.match(redirect_url) != nil
end
- # Returns the template path of the file which was used to
- # render this response (or nil)
- def rendered_file(with_controller=false)
- unless template.first_render.nil?
- unless with_controller
- template.first_render
- else
- template.first_render.split('/').last || template.first_render
- end
- end
- end
-
- # Was this template rendered by a file?
- def rendered_with_file?
- !rendered_file.nil?
+ # Returns the template of the file which was used to
+ # render this response (or nil)
+ def rendered_template
+ template._first_render
end
# A shortcut to the flash. Returns an empyt hash if no session flash exists.
@@ -404,15 +393,6 @@ module ActionController #:nodoc:
end
alias xhr :xml_http_request
- def follow_redirect
- redirected_controller = @response.redirected_to[:controller]
- if redirected_controller && redirected_controller != @controller.controller_name
- raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
- end
-
- get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
- end
-
def assigns(key = nil)
if key.nil?
@response.template.assigns
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 607fd186b9..b8d73c350d 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -17,7 +17,7 @@ module HTML #:nodoc:
@root = Node.new(nil)
node_stack = [ @root ]
while token = tokenizer.next
- node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token)
+ node = Node.parse(node_stack.last, tokenizer.line, tokenizer.position, token, strict)
node_stack.last.children << node unless node.tag? && node.closing == :close
if node.tag?
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 067a871f79..dd555b3792 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -21,12 +21,14 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
+
require 'action_view/template_handlers'
-require 'action_view/template_file'
-require 'action_view/view_load_paths'
+require 'action_view/renderable'
+require 'action_view/renderable_partial'
+
require 'action_view/template'
-require 'action_view/partial_template'
require 'action_view/inline_template'
+require 'action_view/paths'
require 'action_view/base'
require 'action_view/partials'
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index 4f3cc46a14..04e8d3a358 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -3,6 +3,12 @@ module ActionView #:nodoc:
end
class MissingTemplate < ActionViewError #:nodoc:
+ def initialize(paths, path, template_format = nil)
+ full_template_path = path.include?('.') ? path : "#{path}.erb"
+ display_paths = paths.join(':')
+ template_type = (path =~ /layouts/i) ? 'layout' : 'template'
+ super("Missing #{template_type} #{full_template_path} in view path #{display_paths}")
+ end
end
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
@@ -153,20 +159,19 @@ module ActionView #:nodoc:
class Base
include ERB::Util
- attr_accessor :base_path, :assigns, :template_extension, :first_render
+ attr_accessor :base_path, :assigns, :template_extension
attr_accessor :controller
+ attr_accessor :_first_render, :_last_render
attr_writer :template_format
- attr_accessor :current_render_extension
attr_accessor :output_buffer
- # Specify trim mode for the ERB compiler. Defaults to '-'.
- # See ERb documentation for suitable values.
- @@erb_trim_mode = '-'
- cattr_accessor :erb_trim_mode
+ class << self
+ delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB'
+ end
- # Specify whether file modification times should be checked to see if a template needs recompilation
+ # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed.
@@cache_template_loading = false
cattr_accessor :cache_template_loading
@@ -180,6 +185,10 @@ module ActionView #:nodoc:
@@debug_rjs = false
cattr_accessor :debug_rjs
+ # A warning will be displayed whenever an action results in a cache miss on your view paths.
+ @@warn_cache_misses = false
+ cattr_accessor :warn_cache_misses
+
attr_internal :request
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -190,19 +199,10 @@ module ActionView #:nodoc:
end
include CompiledTemplates
- # Maps inline templates to their method names
- cattr_accessor :method_names
- @@method_names = {}
- # Map method names to the names passed in local assigns so far
- @@template_args = {}
-
# Cache public asset paths
cattr_reader :computed_public_paths
@@computed_public_paths = {}
- class ObjectWrapper < Struct.new(:value) #:nodoc:
- end
-
def self.helper_modules #:nodoc:
helpers = []
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|
@@ -216,6 +216,10 @@ module ActionView #:nodoc:
return helpers
end
+ def self.process_view_paths(value)
+ ActionView::PathSet.new(Array(value))
+ end
+
def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc:
@assigns = assigns_for_first_render
@assigns_added = nil
@@ -226,39 +230,20 @@ module ActionView #:nodoc:
attr_reader :view_paths
def view_paths=(paths)
- @view_paths = ViewLoadPaths.new(Array(paths))
- end
-
- # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
- # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
- # is made available as local variables.
- def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
- if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
- raise ActionViewError, <<-END_ERROR
-Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
-
- render "user_mailer/signup"
- render :file => "user_mailer/signup"
-
-If you are rendering a subtemplate, you must now use controller-like partial syntax:
-
- render :partial => 'signup' # no mailer_name necessary
- END_ERROR
- end
-
- Template.new(self, template_path, use_full_path, local_assigns).render_template
+ @view_paths = self.class.process_view_paths(paths)
end
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
# The hash in <tt>local_assigns</tt> is made available as local variables.
def render(options = {}, local_assigns = {}, &block) #:nodoc:
+ local_assigns ||= {}
+
if options.is_a?(String)
- render_file(options, true, local_assigns)
+ render_file(options, nil, local_assigns)
elsif options == :update
update_page(&block)
elsif options.is_a?(Hash)
- use_full_path = options[:use_full_path]
- options = options.reverse_merge(:locals => {}, :use_full_path => true)
+ options = options.reverse_merge(:locals => {})
if partial_layout = options.delete(:layout)
if block_given?
@@ -271,59 +256,112 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
end
end
elsif options[:file]
- render_file(options[:file], use_full_path || false, options[:locals])
+ render_file(options[:file], nil, options[:locals])
elsif options[:partial] && options[:collection]
- render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals])
+ render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals], options[:as])
elsif options[:partial]
- render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals])
+ render_partial(options[:partial], options[:object], options[:locals])
elsif options[:inline]
- template = InlineTemplate.new(self, options[:inline], options[:locals], options[:type])
- render_template(template)
+ render_inline(options[:inline], options[:locals], options[:type])
end
end
end
- def render_template(template) #:nodoc:
- template.render_template
- end
-
# Returns true is the file may be rendered implicitly.
def file_public?(template_path)#:nodoc:
template_path.split('/').last[0,1] != '_'
end
- # Returns a symbolized version of the <tt>:format</tt> parameter of the request,
- # or <tt>:html</tt> by default.
- #
- # EXCEPTION: If the <tt>:format</tt> parameter is not set, the Accept header will be examined for
- # whether it contains the JavaScript mime type as its first priority. If that's the case,
- # it will be used. This ensures that Ajax applications can use the same URL to support both
- # JavaScript and non-JavaScript users.
+ # The format to be used when choosing between multiple templates with
+ # the same name but differing formats. See +Request#template_format+
+ # for more details.
def template_format
return @template_format if @template_format
if controller && controller.respond_to?(:request)
- parameter_format = controller.request.parameters[:format]
- accept_format = controller.request.accepts.first
-
- case
- when parameter_format.blank? && accept_format != :js
- @template_format = :html
- when parameter_format.blank? && accept_format == :js
- @template_format = :js
- else
- @template_format = parameter_format.to_sym
- end
+ @template_format = controller.request.template_format
else
@template_format = :html
end
end
def file_exists?(template_path)
- view_paths.template_exists?(template_file_from_name(template_path))
+ pick_template(template_path) ? true : false
+ rescue MissingTemplate
+ false
+ end
+
+ # Gets the extension for an existing template with the given template_path.
+ # Returns the format with the extension if that template exists.
+ #
+ # pick_template('users/show')
+ # # => 'users/show.html.erb'
+ #
+ # pick_template('users/legacy')
+ # # => 'users/legacy.rhtml'
+ #
+ def pick_template(template_path)
+ path = template_path.sub(/^\//, '')
+ if m = path.match(/(.*)\.(\w+)$/)
+ template_file_name, template_file_extension = m[1], m[2]
+ else
+ template_file_name = path
+ end
+
+ # OPTIMIZE: Checks to lookup template in view path
+ if template = self.view_paths["#{template_file_name}.#{template_format}"]
+ template
+ elsif template = self.view_paths[template_file_name]
+ template
+ elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"]
+ template
+ elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"]
+ @template_format = :html
+ template
+ else
+ template = Template.new(template_path, view_paths)
+
+ if self.class.warn_cache_misses && logger = ActionController::Base.logger
+ logger.debug "[PERFORMANCE] Rendering a template that was " +
+ "not found in view path. Templates outside the view path are " +
+ "not cached and result in expensive disk operations. Move this " +
+ "file into #{view_paths.join(':')} or add the folder to your " +
+ "view path list"
+ end
+
+ template
+ end
end
private
+ # Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt>
+ # is made available as local variables.
+ def render_file(template_path, use_full_path = nil, local_assigns = {}) #:nodoc:
+ unless use_full_path == nil
+ ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
+ end
+
+ if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
+ raise ActionViewError, <<-END_ERROR
+ Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
+
+ render "user_mailer/signup"
+ render :file => "user_mailer/signup"
+
+ If you are rendering a subtemplate, you must now use controller-like partial syntax:
+
+ render :partial => 'signup' # no mailer_name necessary
+ END_ERROR
+ end
+
+ template = pick_template(template_path)
+ template.render_template(self, local_assigns)
+ end
+
+ def render_inline(text, local_assigns = {}, type = nil)
+ InlineTemplate.new(text, type).render(self, local_assigns)
+ end
+
def wrap_content_for_layout(content)
original_content_for_layout, @content_for_layout = @content_for_layout, content
yield
@@ -344,42 +382,10 @@ If you are rendering a subtemplate, you must now use controller-like partial syn
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end
- def execute(template)
- send(template.method, template.locals) do |*names|
+ def execute(template, local_assigns = {})
+ send(template.method(local_assigns), local_assigns) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
-
- def template_file_from_name(template_name)
- template_name = TemplateFile.from_path(template_name)
- pick_template_extension(template_name) unless template_name.extension
- end
-
- # Gets the extension for an existing template with the given template_path.
- # Returns the format with the extension if that template exists.
- #
- # pick_template_extension('users/show')
- # # => 'html.erb'
- #
- # pick_template_extension('users/legacy')
- # # => "rhtml"
- #
- def pick_template_extension(file)
- if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file)
- f
- elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html))
- @template_format = :html
- f
- else
- nil
- end
- end
-
- # Determine the template extension from the <tt>@first_render</tt> filename
- def file_from_first_render(file)
- if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
- file.dup_with_extension(extension)
- end
- end
end
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index e5a95a961c..bf13945844 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -209,6 +209,10 @@ module ActionView
# Note that the default javascript files will be included first. So Prototype and Scriptaculous are available to
# all subsequently included files.
#
+ # If you want Rails to search in all the subdirectories under javascripts, you should explicitly set <tt>:recursive</tt>:
+ #
+ # javascript_include_tag :all, :recursive => true
+ #
# == Caching multiple javascripts into one
#
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
@@ -235,18 +239,23 @@ module ActionView
#
# javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
# <script type="text/javascript" src="/javascripts/shop.js"></script>
+ #
+ # The <tt>:recursive</tt> option is also available for caching:
+ #
+ # javascript_include_tag :all, :cache => true, :recursive => true
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
+ recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
- write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources))
+ write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
javascript_src_tag(joined_javascript_name, options)
else
- expand_javascript_sources(sources).collect { |source| javascript_src_tag(source, options) }.join("\n")
+ expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
end
end
@@ -332,13 +341,17 @@ module ActionView
# <link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />
# <link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />
#
- # You can also include all styles in the stylesheet directory using <tt>:all</tt> as the source:
+ # You can also include all styles in the stylesheets directory using <tt>:all</tt> as the source:
#
# stylesheet_link_tag :all # =>
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
#
+ # If you want Rails to search in all the subdirectories under stylesheets, you should explicitly set <tt>:recursive</tt>:
+ #
+ # stylesheet_link_tag :all, :recursive => true
+ #
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
@@ -362,18 +375,23 @@ module ActionView
#
# stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # The <tt>:recursive</tt> option is also available for caching:
+ #
+ # stylesheet_link_tag :all, :cache => true, :recursive => true
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
cache = options.delete("cache")
+ recursive = options.delete("recursive")
if ActionController::Base.perform_caching && cache
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
- write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources))
+ write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
stylesheet_tag(joined_stylesheet_name, options)
else
- expand_stylesheet_sources(sources).collect { |source| stylesheet_tag(source, options) }.join("\n")
+ expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
end
end
@@ -556,18 +574,19 @@ module ActionView
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
end
- def compute_javascript_paths(sources)
- expand_javascript_sources(sources).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
+ def compute_javascript_paths(*args)
+ expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
end
- def compute_stylesheet_paths(sources)
- expand_stylesheet_sources(sources).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
+ def compute_stylesheet_paths(*args)
+ expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
end
- def expand_javascript_sources(sources)
+ def expand_javascript_sources(sources, recursive = false)
if sources.include?(:all)
- all_javascript_files = Dir[File.join(JAVASCRIPTS_DIR, '*.js')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
- @@all_javascript_sources ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
+ all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
+ @@all_javascript_sources ||= {}
+ @@all_javascript_sources[recursive] ||= ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
else
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
@@ -577,9 +596,10 @@ module ActionView
end
end
- def expand_stylesheet_sources(sources)
+ def expand_stylesheet_sources(sources, recursive)
if sources.first == :all
- @@all_stylesheet_sources ||= Dir[File.join(STYLESHEETS_DIR, '*.css')].collect { |file| File.basename(file).gsub(/\.\w+$/, '') }.sort
+ @@all_stylesheet_sources ||= {}
+ @@all_stylesheet_sources[recursive] ||= collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
else
sources.collect do |source|
determine_source(source, @@stylesheet_expansions)
@@ -601,10 +621,16 @@ module ActionView
end
def write_asset_file_contents(joined_asset_path, asset_paths)
- unless file_exist?(joined_asset_path)
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
- File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
- end
+ FileUtils.mkdir_p(File.dirname(joined_asset_path))
+ File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
+ end
+
+ def collect_asset_files(*path)
+ dir = path.first
+
+ Dir[File.join(*path.compact)].collect do |file|
+ file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
+ end.sort
end
end
end
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 930c397785..64d1ad2715 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,8 +32,7 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- handler = Template.handler_class_for_extension(current_render_extension.to_sym)
- handler.new(@controller).cache_fragment(block, name, options)
+ @controller.fragment_for(output_buffer, name, options, &block)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 9cd9d3d06a..720e2da8cc 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -31,10 +31,12 @@ module ActionView
# </body></html>
#
def capture(*args, &block)
- if output_buffer
+ # Return captured buffer in erb.
+ if block_called_from_erb?(block)
with_output_buffer { block.call(*args) }
else
- block.call(*args)
+ # Return block result otherwise, but protect buffer also.
+ with_output_buffer { return block.call(*args) }
end
end
@@ -117,6 +119,7 @@ module ActionView
ivar = "@content_for_#{name}"
content = capture(&block) if block_given?
instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}")
+ nil
end
private
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 61fff42d5a..1f213127d3 100755
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -158,13 +158,16 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def date_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
# Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
# time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
# You can include the seconds with <tt>:include_seconds</tt>.
- #
+ #
+ # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
+ # <tt>:ignore_date</tt> is set to +true+.
+ #
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
@@ -193,7 +196,7 @@ module ActionView
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month
# choices are valid.
def time_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_time_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options)
end
# Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
@@ -219,7 +222,7 @@ module ActionView
#
# The selects are prepared for multi-parameter assignment to an Active Record object.
def datetime_select(object_name, method, options = {}, html_options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options, html_options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
# Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
@@ -558,23 +561,32 @@ module ActionView
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
def select_year(date, options = {}, html_options = {})
- val = date ? (date.kind_of?(Fixnum) ? date : date.year) : ''
+ if !date || date == 0
+ value = ''
+ middle_year = Date.today.year
+ elsif date.kind_of?(Fixnum)
+ value = middle_year = date
+ else
+ value = middle_year = date.year
+ end
+
if options[:use_hidden]
- hidden_html(options[:field_name] || 'year', val, options)
+ hidden_html(options[:field_name] || 'year', value, options)
else
- year_options = []
- y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
+ year_options = ''
+ start_year = options[:start_year] || middle_year - 5
+ end_year = options[:end_year] || middle_year + 5
+ step_val = start_year < end_year ? 1 : -1
- start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
- step_val = start_year < end_year ? 1 : -1
start_year.step(end_year, step_val) do |year|
- year_options << ((val == year) ?
- content_tag(:option, year, :value => year, :selected => "selected") :
- content_tag(:option, year, :value => year)
- )
+ if value == year
+ year_options << content_tag(:option, year, :value => year, :selected => "selected")
+ else
+ year_options << content_tag(:option, year, :value => year)
+ end
year_options << "\n"
end
- select_html(options[:field_name] || 'year', year_options.join, options, html_options)
+ select_html(options[:field_name] || 'year', year_options, options, html_options)
end
end
@@ -659,7 +671,7 @@ module ActionView
order.reverse.each do |param|
# Send hidden fields for discarded elements once output has started
# This ensures AR can reconstruct valid dates using ParseDate
- next if discard[param] && date_or_time_select.empty?
+ next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options))
date_or_time_select.insert(0,
@@ -708,15 +720,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, options.merge(:object => @object))
+ @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
end
def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, options.merge(:object => @object))
+ @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
end
def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, options.merge(:object => @object))
+ @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 63a932320e..bafc635ad2 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -333,7 +333,7 @@ module ActionView
# # => <label for="post_title" class="title_label">A short title</label>
#
def label(object_name, method, text = nil, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
end
# Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -355,7 +355,7 @@ module ActionView
# # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
#
def text_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
end
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
@@ -377,7 +377,7 @@ module ActionView
# # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
#
def password_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
end
# Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -395,7 +395,7 @@ module ActionView
# hidden_field(:user, :token)
# # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
def hidden_field(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
end
# Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -414,7 +414,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, nil, options.delete(:object)).to_input_field_tag("file", options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
end
# Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
@@ -442,7 +442,7 @@ module ActionView
# # #{@entry.body}
# # </textarea>
def text_area(object_name, method, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
end
# Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
@@ -468,7 +468,7 @@ module ActionView
# # <input name="eula[accepted]" type="hidden" value="no" />
#
def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
end
# Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
@@ -488,7 +488,7 @@ module ActionView
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
# # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
def radio_button(object_name, method, tag_value, options = {})
- InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
end
end
@@ -501,9 +501,9 @@ module ActionView
DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
- def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
+ def initialize(object_name, method_name, template_object, object = nil)
@object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
- @template_object, @local_binding = template_object, local_binding
+ @template_object= template_object
@object = object
if @object_name.sub!(/\[\]$/,"")
if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
@@ -601,7 +601,11 @@ module ActionView
end
def object
- @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
+ @object || @template_object.instance_variable_get("@#{@object_name}")
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we
+ # need to fallback to nil.
+ nil
end
def value(object)
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index b3f8e63c1b..87d49397c6 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -96,7 +96,7 @@ module ActionView
# By default, <tt>post.person_id</tt> is the selected option. Specify <tt>:selected => value</tt> to use a different selection
# or <tt>:selected => nil</tt> to leave all options unselected.
def select(object, method, choices, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_select_tag(choices, options, html_options)
end
# Returns <tt><select></tt> and <tt><option></tt> tags for the collection of existing return values of
@@ -130,12 +130,12 @@ module ActionView
# <option value="3">M. Clark</option>
# </select>
def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
end
# Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
end
# Return select and option tags for the given object and method, using
@@ -150,7 +150,8 @@ module ActionView
# You can also supply an array of TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use TimeZone.us_zones as a convenience for
- # obtaining a list of the US time zones.)
+ # obtaining a list of the US time zones, or a Regexp to select the zones
+ # of your choice)
#
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default TimeZone if the object's time zone is +nil+.
@@ -164,9 +165,11 @@ module ActionView
#
# time_zone_select( "user", 'time_zone', [ TimeZone['Alaska'], TimeZone['Hawaii'] ])
#
+ # time_zone_select( "user", 'time_zone', /Australia/)
+ #
# time_zone_select( "user", "time_zone", TZInfo::Timezone.all.sort, :model => TZInfo::Timezone)
def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
- InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ InstanceTag.new(object, method, self, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
end
# Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
@@ -292,7 +295,8 @@ module ActionView
# selected option tag. You can also supply an array of TimeZone objects
# as +priority_zones+, so that they will be listed above the rest of the
# (long) list. (You can use TimeZone.us_zones as a convenience for
- # obtaining a list of the US time zones.)
+ # obtaining a list of the US time zones, or a Regexp to select the zones
+ # of your choice)
#
# The +selected+ parameter must be either +nil+, or a string that names
# a TimeZone.
@@ -311,6 +315,9 @@ module ActionView
convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } }
if priority_zones
+ if priority_zones.is_a?(Regexp)
+ priority_zones = model.all.find_all {|z| z =~ priority_zones}
+ end
zone_options += options_for_select(convert_zones[priority_zones], selected)
zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n"
@@ -438,19 +445,19 @@ module ActionView
class FormBuilder
def select(method, choices, options = {}, html_options = {})
- @template.select(@object_name, method, choices, options.merge(:object => @object), html_options)
+ @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options))
end
def collection_select(method, collection, value_method, text_method, options = {}, html_options = {})
- @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options)
+ @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options))
end
def country_select(method, priority_countries = nil, options = {}, html_options = {})
- @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options)
+ @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options))
end
def time_zone_select(method, priority_zones = nil, options = {}, html_options = {})
- @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options)
+ @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options))
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 3a97f1390f..bdfb2eebd7 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -6,7 +6,7 @@ module ActionView
# Provides a number of methods for creating form tags that doesn't rely on an Active Record object assigned to the template like
# FormHelper does. Instead, you provide the names and values manually.
#
- # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
+ # NOTE: The HTML options <tt>disabled</tt>, <tt>readonly</tt>, and <tt>multiple</tt> can all be treated as booleans. So specifying
# <tt>:disabled => true</tt> will give <tt>disabled="disabled"</tt>.
module FormTagHelper
# Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like
@@ -20,15 +20,15 @@ module ActionView
# * A list of parameters to feed to the URL the form will be posted to.
#
# ==== Examples
- # form_tag('/posts')
+ # form_tag('/posts')
# # => <form action="/posts" method="post">
#
- # form_tag('/posts/1', :method => :put)
+ # form_tag('/posts/1', :method => :put)
# # => <form action="/posts/1" method="put">
#
- # form_tag('/upload', :multipart => true)
+ # form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
- #
+ #
# <% form_tag '/posts' do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
@@ -88,7 +88,7 @@ module ActionView
# * <tt>:size</tt> - The number of visible characters that will fit in the input.
# * <tt>:maxlength</tt> - The maximum number of characters that the browser will allow the user to enter.
# * Any other key creates standard HTML attributes for the tag.
- #
+ #
# ==== Examples
# text_field_tag 'name'
# # => <input id="name" name="name" type="text" />
@@ -146,13 +146,13 @@ module ActionView
# # => <input id="token" name="token" type="hidden" value="VUBJKB23UIVI1UU1VOBVI@" />
#
# hidden_field_tag 'collected_input', '', :onchange => "alert('Input collected!')"
- # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
+ # # => <input id="collected_input" name="collected_input" onchange="alert('Input collected!')"
# # type="hidden" value="" />
def hidden_field_tag(name, value = nil, options = {})
text_field_tag(name, value, options.stringify_keys.update("type" => "hidden"))
end
- # Creates a file upload field. If you are using file uploads then you will also need
+ # Creates a file upload field. If you are using file uploads then you will also need
# to set the multipart option for the form tag:
#
# <%= form_tag { :action => "post" }, { :multipart => true } %>
@@ -160,7 +160,7 @@ module ActionView
# <%= submit_tag %>
# <%= end_form_tag %>
#
- # The specified URL will then be passed a File object containing the selected file, or if the field
+ # The specified URL will then be passed a File object containing the selected file, or if the field
# was left blank, a StringIO object.
#
# ==== Options
@@ -181,7 +181,7 @@ module ActionView
# # => <input id="resume" name="resume" type="file" value="~/resume.doc" />
#
# file_field_tag 'user_pic', :accept => 'image/png,image/gif,image/jpeg'
- # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
+ # # => <input accept="image/png,image/gif,image/jpeg" id="user_pic" name="user_pic" type="file" />
#
# file_field_tag 'file', :accept => 'text/html', :class => 'upload', :value => 'index.html'
# # => <input accept="text/html" class="upload" id="file" name="file" type="file" value="index.html" />
@@ -286,7 +286,7 @@ module ActionView
tag :input, html_options
end
- # Creates a radio button; use groups of radio buttons named the same to allow users to
+ # Creates a radio button; use groups of radio buttons named the same to allow users to
# select from a group of options.
#
# ==== Options
@@ -313,14 +313,14 @@ module ActionView
tag :input, html_options
end
- # Creates a submit button with the text <tt>value</tt> as the caption.
+ # Creates a submit button with the text <tt>value</tt> as the caption.
#
# ==== Options
# * <tt>:confirm => 'question?'</tt> - This will add a JavaScript confirm
# prompt with the question specified. If the user accepts, the form is
# processed normally, otherwise no action is taken.
# * <tt>:disabled</tt> - If true, the user will not be able to use this input.
- # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
+ # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a disabled version
# of the submit button when the form is submitted.
# * Any other key creates standard HTML options for the tag.
#
@@ -335,7 +335,7 @@ module ActionView
# # => <input disabled="disabled" name="commit" type="submit" value="Save edits" />
#
# submit_tag "Complete sale", :disable_with => "Please wait..."
- # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
+ # # => <input name="commit" onclick="this.disabled=true;this.value='Please wait...';this.form.submit();"
# # type="submit" value="Complete sale" />
#
# submit_tag nil, :class => "form_submit"
@@ -346,27 +346,29 @@ module ActionView
# # name="commit" type="submit" value="Edit" />
def submit_tag(value = "Save changes", options = {})
options.stringify_keys!
-
+
if disable_with = options.delete("disable_with")
+ disable_with = "this.value='#{disable_with}'"
+ disable_with << ";#{options.delete('onclick')}" if options['onclick']
+
options["onclick"] = [
"this.setAttribute('originalValue', this.value)",
"this.disabled=true",
- "this.value='#{disable_with}'",
- "#{options["onclick"]}",
+ disable_with,
"result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit())",
"if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false }",
"return result;",
].join(";")
end
-
+
if confirm = options.delete("confirm")
options["onclick"] ||= ''
options["onclick"] += "return #{confirm_javascript_function(confirm)};"
end
-
+
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
end
-
+
# Displays an image which when clicked will submit the form.
#
# <tt>source</tt> is passed to AssetTagHelper#image_path
@@ -412,7 +414,7 @@ module ActionView
concat(content)
concat("</fieldset>")
end
-
+
private
def html_options_for_form(url_for_options, options, *parameters_for_url)
returning options.stringify_keys do |html_options|
@@ -420,7 +422,7 @@ module ActionView
html_options["action"] = url_for(url_for_options, *parameters_for_url)
end
end
-
+
def extra_tags_for_form(html_options)
case method = html_options.delete("method").to_s
when /^get$/i # must be case-insentive, but can't use downcase as might be nil
@@ -434,12 +436,12 @@ module ActionView
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
end
end
-
+
def form_tag_html(html_options)
extra_tags = extra_tags_for_form(html_options)
tag(:form, html_options, true) + extra_tags
end
-
+
def form_tag_in_block(html_options, &block)
content = capture(&block)
concat(form_tag_html(html_options))
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 7404a251e4..22bd5cb440 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -4,10 +4,10 @@ require 'action_view/helpers/prototype_helper'
module ActionView
module Helpers
# Provides functionality for working with JavaScript in your views.
- #
+ #
# == Ajax, controls and visual effects
- #
- # * For information on using Ajax, see
+ #
+ # * For information on using Ajax, see
# ActionView::Helpers::PrototypeHelper.
# * For information on using controls and visual effects, see
# ActionView::Helpers::ScriptaculousHelper.
@@ -20,22 +20,22 @@ module ActionView
# and ActionView::Helpers::ScriptaculousHelper), you must do one of the
# following:
#
- # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
- # section of your page (recommended): This function will return
+ # * Use <tt><%= javascript_include_tag :defaults %></tt> in the HEAD
+ # section of your page (recommended): This function will return
# references to the JavaScript files created by the +rails+ command in
# your <tt>public/javascripts</tt> directory. Using it is recommended as
- # the browser can then cache the libraries instead of fetching all the
+ # the browser can then cache the libraries instead of fetching all the
# functions anew on every request.
- # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
+ # * Use <tt><%= javascript_include_tag 'prototype' %></tt>: As above, but
# will only include the Prototype core library, which means you are able
- # to use all basic AJAX functionality. For the Scriptaculous-based
- # JavaScript helpers, like visual effects, autocompletion, drag and drop
+ # to use all basic AJAX functionality. For the Scriptaculous-based
+ # JavaScript helpers, like visual effects, autocompletion, drag and drop
# and so on, you should use the method described above.
# * Use <tt><%= define_javascript_functions %></tt>: this will copy all the
# JavaScript support functions within a single script block. Not
# recommended.
#
- # For documentation on +javascript_include_tag+ see
+ # For documentation on +javascript_include_tag+ see
# ActionView::Helpers::AssetTagHelper.
module JavaScriptHelper
unless const_defined? :JAVASCRIPT_PATH
@@ -43,13 +43,13 @@ module ActionView
end
include PrototypeHelper
-
- # Returns a link that will trigger a JavaScript +function+ using the
+
+ # Returns a link that will trigger a JavaScript +function+ using the
# onclick handler and return false after the fact.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # (instead of making an Ajax request first).
#
# Examples:
# link_to_function "Greeting", "alert('Hello world!')"
@@ -70,36 +70,31 @@ module ActionView
# <a href="#" id="more_link" onclick="try {
# $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
# $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
- # }
- # catch (e) {
- # alert('RJS error:\n\n' + e.toString());
+ # }
+ # catch (e) {
+ # alert('RJS error:\n\n' + e.toString());
# alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
# \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
- # throw e
+ # throw e
# };
# return false;">Show me more</a>
#
def link_to_function(name, *args, &block)
- html_options = args.extract_options!
- function = args[0] || ''
-
- html_options.symbolize_keys!
- function = update_page(&block) if block_given?
- content_tag(
- "a", name,
- html_options.merge({
- :href => html_options[:href] || "#",
- :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function}; return false;"
- })
- )
+ html_options = args.extract_options!.symbolize_keys
+
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
+ href = html_options[:href] || '#'
+
+ content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
end
-
- # Returns a button that'll trigger a JavaScript +function+ using the
+
+ # Returns a button that'll trigger a JavaScript +function+ using the
# onclick handler.
#
# The +function+ argument can be omitted in favor of an +update_page+
# block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
+ # (instead of making an Ajax request first).
#
# Examples:
# button_to_function "Greeting", "alert('Hello world!')"
@@ -111,45 +106,30 @@ module ActionView
# page[:details].visual_effect :toggle_slide
# end
def button_to_function(name, *args, &block)
- html_options = args.extract_options!
- function = args[0] || ''
-
- html_options.symbolize_keys!
- function = update_page(&block) if block_given?
- tag(:input, html_options.merge({
- :type => "button", :value => name,
- :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
- }))
- end
+ html_options = args.extract_options!.symbolize_keys
- # Includes the Action Pack JavaScript libraries inside a single <script>
- # tag. The function first includes prototype.js and then its core extensions,
- # (determined by filenames starting with "prototype").
- # Afterwards, any additional scripts will be included in undefined order.
- #
- # Note: The recommended approach is to copy the contents of
- # lib/action_view/helpers/javascripts/ into your application's
- # public/javascripts/ directory, and use +javascript_include_tag+ to
- # create remote <script> links.
- def define_javascript_functions
- javascript = "<script type=\"#{Mime::JS}\">"
-
- # load prototype.js and its extensions first
- prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse
- prototype_libs.each do |filename|
- javascript << "\n" << IO.read(filename)
- end
-
- # load other libraries
- (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename|
- javascript << "\n" << IO.read(filename)
- end
- javascript << '</script>'
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
+
+ tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
end
+ JS_ESCAPE_MAP = {
+ '\\' => '\\\\',
+ '</' => '<\/',
+ "\r\n" => '\n',
+ "\n" => '\n',
+ "\r" => '\n',
+ '"' => '\\"',
+ "'" => "\\'" }
+
# Escape carrier returns and single and double quotes for JavaScript segments.
def escape_javascript(javascript)
- (javascript || '').gsub('\\','\0\0').gsub('</','<\/').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
+ if javascript
+ javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
+ else
+ ''
+ end
end
# Returns a JavaScript tag with the +content+ inside. Example:
@@ -163,7 +143,7 @@ module ActionView
# </script>
#
# +html_options+ may be a hash of attributes for the <script> tag. Example:
- # javascript_tag "alert('All is good')", :defer => 'defer'
+ # javascript_tag "alert('All is good')", :defer => 'defer'
# # => <script defer="defer" type="text/javascript">alert('All is good')</script>
#
# Instead of passing the content as an argument, you can also use a block
@@ -180,30 +160,35 @@ module ActionView
content_or_options_with_block
end
- tag = content_tag("script", javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
+ tag = content_tag(:script, javascript_cdata_section(content), html_options.merge(:type => Mime::JS))
- block_given? ? concat(tag) : tag
+ if block_called_from_erb?(block)
+ concat(tag)
+ else
+ tag
+ end
end
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n"
end
-
+
protected
def options_for_javascript(options)
- '{' + options.map {|k, v| "#{k}:#{v}"}.sort.join(', ') + '}'
+ if options.empty?
+ '{}'
+ else
+ "{#{options.keys.map { |k| "#{k}:#{options[k]}" }.sort.join(', ')}}"
+ end
end
-
+
def array_or_string_for_javascript(option)
- js_option = if option.kind_of?(Array)
+ if option.kind_of?(Array)
"['#{option.join('\',\'')}']"
elsif !option.nil?
"'#{option}'"
end
- js_option
end
end
-
- JavascriptHelper = JavaScriptHelper unless const_defined? :JavascriptHelper
end
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index a7c3b9ddc3..edb43844a4 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -3,25 +3,25 @@ require 'set'
module ActionView
module Helpers
# Prototype[http://www.prototypejs.org/] is a JavaScript library that provides
- # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
+ # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation,
# Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]
- # functionality, and more traditional object-oriented facilities for JavaScript.
+ # functionality, and more traditional object-oriented facilities for JavaScript.
# This module provides a set of helpers to make it more convenient to call
- # functions from Prototype using Rails, including functionality to call remote
- # Rails methods (that is, making a background request to a Rails action) using Ajax.
- # This means that you can call actions in your controllers without
- # reloading the page, but still update certain parts of it using
+ # functions from Prototype using Rails, including functionality to call remote
+ # Rails methods (that is, making a background request to a Rails action) using Ajax.
+ # This means that you can call actions in your controllers without
+ # reloading the page, but still update certain parts of it using
# injections into the DOM. A common use case is having a form that adds
# a new element to a list without reloading the page or updating a shopping
# cart total when a new item is added.
#
# == Usage
- # To be able to use these helpers, you must first include the Prototype
- # JavaScript framework in your pages.
+ # To be able to use these helpers, you must first include the Prototype
+ # JavaScript framework in your pages.
#
# javascript_include_tag 'prototype'
#
- # (See the documentation for
+ # (See the documentation for
# ActionView::Helpers::JavaScriptHelper for more information on including
# this and other JavaScript files in your Rails templates.)
#
@@ -29,7 +29,7 @@ module ActionView
#
# link_to_remote "Add to cart",
# :url => { :action => "add", :id => product.id },
- # :update => { :success => "cart", :failure => "error" }
+ # :update => { :success => "cart", :failure => "error" }
#
# ...through a form...
#
@@ -50,8 +50,8 @@ module ActionView
# :update => :hits,
# :with => 'query'
# %>
- #
- # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
+ #
+ # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than
# are listed here); check out the documentation for each method to find out more about its usage and options.
#
# === Common Options
@@ -63,7 +63,7 @@ module ActionView
# When building your action handlers (that is, the Rails actions that receive your background requests), it's
# important to remember a few things. First, whatever your action would normall return to the browser, it will
# return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause
- # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
+ # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up.
# You can turn the layout off on particular actions by doing the following:
#
# class SiteController < ActionController::Base
@@ -74,8 +74,8 @@ module ActionView
#
# render :layout => false
#
- # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
- # method that Ajax uses to make background requests) method.
+ # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the
+ # method that Ajax uses to make background requests) method.
# def name
# # Is this an XmlHttpRequest request?
# if (request.xhr?)
@@ -93,7 +93,7 @@ module ActionView
#
# Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request.
#
- # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
+ # If you are just returning a little data or don't want to build a template for your output, you may opt to simply
# render text output, like this:
#
# render :text => 'Return this from my method!'
@@ -103,7 +103,7 @@ module ActionView
#
# == Updating multiple elements
# See JavaScriptGenerator for information on updating multiple elements
- # on the page in an Ajax response.
+ # on the page in an Ajax response.
module PrototypeHelper
unless const_defined? :CALLBACKS
CALLBACKS = Set.new([ :uninitialized, :loading, :loaded,
@@ -114,64 +114,64 @@ module ActionView
:form, :with, :update, :script ]).merge(CALLBACKS)
end
- # Returns a link to a remote action defined by <tt>options[:url]</tt>
- # (using the url_for format) that's called in the background using
+ # Returns a link to a remote action defined by <tt>options[:url]</tt>
+ # (using the url_for format) that's called in the background using
# XMLHttpRequest. The result of that request can then be inserted into a
- # DOM object whose id can be specified with <tt>options[:update]</tt>.
+ # DOM object whose id can be specified with <tt>options[:update]</tt>.
# Usually, the result would be a partial prepared by the controller with
- # render :partial.
+ # render :partial.
#
# Examples:
- # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true});
# # return false;">Delete this post</a>
- # link_to_remote "Delete this post", :update => "posts",
+ # link_to_remote "Delete this post", :update => "posts",
# :url => { :action => "destroy", :id => post.id }
#
- # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
+ # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true});
# # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a>
- # link_to_remote(image_tag("refresh"), :update => "emails",
+ # link_to_remote(image_tag("refresh"), :update => "emails",
# :url => { :action => "list_emails" })
- #
+ #
# You can override the generated HTML options by specifying a hash in
# <tt>options[:html]</tt>.
- #
+ #
# link_to_remote "Delete this post", :update => "posts",
- # :url => post_url(@post), :method => :delete,
- # :html => { :class => "destructive" }
+ # :url => post_url(@post), :method => :delete,
+ # :html => { :class => "destructive" }
#
# You can also specify a hash for <tt>options[:update]</tt> to allow for
- # easy redirection of output to an other DOM element if a server-side
+ # easy redirection of output to an other DOM element if a server-side
# error occurs:
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
+ # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5',
# # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a>
# link_to_remote "Delete this post",
# :url => { :action => "destroy", :id => post.id },
# :update => { :success => "posts", :failure => "error" }
#
- # Optionally, you can use the <tt>options[:position]</tt> parameter to
- # influence how the target DOM element is updated. It must be one of
+ # Optionally, you can use the <tt>options[:position]</tt> parameter to
+ # influence how the target DOM element is updated. It must be one of
# <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>.
#
# The method used is by default POST. You can also specify GET or you
# can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt>
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
+ # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'});
# # return false;">Destroy</a>
# link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete
#
- # By default, these remote requests are processed asynchronous during
- # which various JavaScript callbacks can be triggered (for progress
- # indicators and the likes). All callbacks get access to the
- # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
+ # By default, these remote requests are processed asynchronous during
+ # which various JavaScript callbacks can be triggered (for progress
+ # indicators and the likes). All callbacks get access to the
+ # <tt>request</tt> object, which holds the underlying XMLHttpRequest.
#
# To access the server response, use <tt>request.responseText</tt>, to
# find out the HTTP status, use <tt>request.status</tt>.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
+ # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true,
# # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a>
# word = 'hello'
# link_to_remote word,
@@ -180,43 +180,43 @@ module ActionView
#
# The callbacks that may be specified are (in order):
#
- # <tt>:loading</tt>:: Called when the remote document is being
+ # <tt>:loading</tt>:: Called when the remote document is being
# loaded with data by the browser.
# <tt>:loaded</tt>:: Called when the browser has finished loading
# the remote document.
- # <tt>:interactive</tt>:: Called when the user can interact with the
- # remote document, even though it has not
+ # <tt>:interactive</tt>:: Called when the user can interact with the
+ # remote document, even though it has not
# finished loading.
# <tt>:success</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is in the 2XX range.
# <tt>:failure</tt>:: Called when the XMLHttpRequest is completed,
# and the HTTP status code is not in the 2XX
# range.
- # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
- # (fires after success/failure if they are
+ # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete
+ # (fires after success/failure if they are
# present).
- #
- # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
+ #
+ # You can further refine <tt>:success</tt> and <tt>:failure</tt> by
# adding additional callbacks for specific status codes.
#
# Example:
- # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
- # # on404:function(request){alert('Not found...? Wrong URL...?')},
+ # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true,
+ # # on404:function(request){alert('Not found...? Wrong URL...?')},
# # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a>
# link_to_remote word,
# :url => { :action => "action" },
# 404 => "alert('Not found...? Wrong URL...?')",
# :failure => "alert('HTTP Error ' + request.status + '!')"
#
- # A status code callback overrides the success/failure handlers if
+ # A status code callback overrides the success/failure handlers if
# present.
#
# If you for some reason or another need synchronous processing (that'll
- # block the browser while the request is happening), you can specify
+ # block the browser while the request is happening), you can specify
# <tt>options[:type] = :synchronous</tt>.
#
# You can customize further browser side call logic by passing in
- # JavaScript code snippets via some optional parameters. In their order
+ # JavaScript code snippets via some optional parameters. In their order
# of use these are:
#
# <tt>:confirm</tt>:: Adds confirmation dialog.
@@ -228,7 +228,7 @@ module ActionView
# <tt>:after</tt>:: Called immediately after request was
# initiated and before <tt>:loading</tt>.
# <tt>:submit</tt>:: Specifies the DOM element ID that's used
- # as the parent of the form elements. By
+ # as the parent of the form elements. By
# default this is the current form, but
# it could just as well be the ID of a
# table row or any other DOM element.
@@ -238,10 +238,10 @@ module ActionView
# URL query string.
#
# Example:
- #
+ #
# :with => "'name=' + $('name').value"
#
- # You can generate a link that uses AJAX in the general case, while
+ # You can generate a link that uses AJAX in the general case, while
# degrading gracefully to plain link behavior in the absence of
# JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL.
# Note the extra curly braces around the <tt>options</tt> hash separate
@@ -251,7 +251,7 @@ module ActionView
# link_to_remote "Delete this post",
# { :update => "posts", :url => { :action => "destroy", :id => post.id } },
# :href => url_for(:action => "destroy", :id => post.id)
- def link_to_remote(name, options = {}, html_options = nil)
+ def link_to_remote(name, options = {}, html_options = nil)
link_to_function(name, remote_function(options), html_options || options.delete(:html))
end
@@ -262,15 +262,15 @@ module ActionView
# and defining callbacks is the same as link_to_remote.
# Examples:
# # Call get_averages and put its results in 'avg' every 10 seconds
- # # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
+ # # Generates:
+ # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages',
# # {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg')
#
# # Call invoice every 10 seconds with the id of the customer
# # If it succeeds, update the invoice DIV; if it fails, update the error DIV
# # Generates:
- # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
+ # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'},
# # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10)
# periodically_call_remote(:url => { :action => 'invoice', :id => customer.id },
# :update => { :success => "invoice", :failure => "error" }
@@ -286,11 +286,11 @@ module ActionView
javascript_tag(code)
end
- # Returns a form tag that will submit using XMLHttpRequest in the
- # background instead of the regular reloading POST arrangement. Even
+ # Returns a form tag that will submit using XMLHttpRequest in the
+ # background instead of the regular reloading POST arrangement. Even
# though it's using JavaScript to serialize the form elements, the form
# submission will work just like a regular submission as viewed by the
- # receiving side (all elements available in <tt>params</tt>). The options for
+ # receiving side (all elements available in <tt>params</tt>). The options for
# specifying the target with <tt>:url</tt> and defining callbacks is the same as
# +link_to_remote+.
#
@@ -299,21 +299,21 @@ module ActionView
#
# Example:
# # Generates:
- # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
+ # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('',
# # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
- # form_remote_tag :html => { :action =>
+ # form_remote_tag :html => { :action =>
# url_for(:controller => "some", :action => "place") }
#
# The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd)
# argument in the FormTagHelper.form_tag method.
#
- # By default the fall-through action is the same as the one specified in
+ # By default the fall-through action is the same as the one specified in
# the <tt>:url</tt> (and the default method is <tt>:post</tt>).
#
# form_remote_tag also takes a block, like form_tag:
# # Generates:
- # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
+ # # <form action="/" method="post" onsubmit="new Ajax.Request('/',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)});
# # return false;"> <div><input name="commit" type="submit" value="Save" /></div>
# # </form>
# <% form_remote_tag :url => '/posts' do -%>
@@ -323,19 +323,19 @@ module ActionView
options[:form] = true
options[:html] ||= {}
- options[:html][:onsubmit] =
- (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
+ options[:html][:onsubmit] =
+ (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") +
"#{remote_function(options)}; return false;"
form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block)
end
- # Creates a form that will submit using XMLHttpRequest in the background
- # instead of the regular reloading POST arrangement and a scope around a
+ # Creates a form that will submit using XMLHttpRequest in the background
+ # instead of the regular reloading POST arrangement and a scope around a
# specific resource that is used as a base for questioning about
- # values for the fields.
+ # values for the fields.
#
- # === Resource
+ # === Resource
#
# Example:
# <% remote_form_for(@post) do |f| %>
@@ -348,7 +348,7 @@ module ActionView
# ...
# <% end %>
#
- # === Nested Resource
+ # === Nested Resource
#
# Example:
# <% remote_form_for([@post, @comment]) do |f| %>
@@ -387,29 +387,31 @@ module ActionView
concat('</form>')
end
alias_method :form_remote_for, :remote_form_for
-
+
# Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+
# that will submit form using XMLHttpRequest in the background instead of a regular POST request that
- # reloads the page.
+ # reloads the page.
#
# # Create a button that submits to the create action
- # #
- # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
- # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # #
+ # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create',
+ # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Create" />
- # <%= submit_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
+ # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %>
#
# # Submit to the remote action update and update the DIV succeed or fail based
# # on the success or failure of the request
# #
- # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
- # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
+ # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'},
+ # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)});
# # return false;" type="button" value="Update" />
- # <%= submit_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
+ # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' },
# :update => { :success => "succeed", :failure => "fail" }
#
# <tt>options</tt> argument is the same as in form_remote_tag.
- def submit_to_remote(name, value, options = {})
+ #
+ # Note: This method used to be called submit_to_remote, but that's now just an alias for button_to_remote
+ def button_to_remote(name, value, options = {})
options[:with] ||= 'Form.serialize(this.form)'
options[:html] ||= {}
@@ -420,7 +422,8 @@ module ActionView
tag("input", options[:html], false)
end
-
+ alias_method :submit_to_remote, :button_to_remote
+
# Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function
# that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple
# update return document using +update_element_function+ calls.
@@ -430,11 +433,11 @@ module ActionView
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
- #
+ #
# Example:
- # # Generates: <select id="options" onchange="new Ajax.Updater('options',
+ # # Generates: <select id="options" onchange="new Ajax.Updater('options',
# # '/testing/update_options', {asynchronous:true, evalScripts:true})">
- # <select id="options" onchange="<%= remote_function(:update => "options",
+ # <select id="options" onchange="<%= remote_function(:update => "options",
# :url => { :action => :update_options }) %>">
# <option value="0">Hello</option>
# <option value="1">World</option>
@@ -452,7 +455,7 @@ module ActionView
update << "'#{options[:update]}'"
end
- function = update.empty? ?
+ function = update.empty? ?
"new Ajax.Request(" :
"new Ajax.Updater(#{update}, "
@@ -473,9 +476,9 @@ module ActionView
# callback when its contents have changed. The default callback is an
# Ajax call. By default the value of the observed field is sent as a
# parameter with the Ajax call.
- #
+ #
# Example:
- # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
+ # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest',
# # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})})
# <%= observe_field :suggest, :url => { :action => :find_suggestion },
# :frequency => 0.25,
@@ -497,14 +500,14 @@ module ActionView
# new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')})
# The element parameter is the DOM element being observed, and the value is its value at the
# time the observer is triggered.
- #
+ #
# Additional options are:
# <tt>:frequency</tt>:: The frequency (in seconds) at which changes to
# this field will be detected. Not setting this
# option at all or to a value equal to or less than
# zero will use event based observation instead of
# time based observation.
- # <tt>:update</tt>:: Specifies the DOM ID of the element whose
+ # <tt>:update</tt>:: Specifies the DOM ID of the element whose
# innerHTML should be updated with the
# XMLHttpRequest response text.
# <tt>:with</tt>:: A JavaScript expression specifying the parameters
@@ -515,7 +518,7 @@ module ActionView
# variable +value+.
#
# Examples
- #
+ #
# :with => "'my_custom_key=' + value"
# :with => "'person[name]=' + prompt('New name')"
# :with => "Form.Element.serialize('other-field')"
@@ -541,7 +544,7 @@ module ActionView
# observe_field 'book_title',
# :url => 'http://example.com/books/edit/1',
# :with => 'title'
- #
+ #
# # Sends params: {:book_title => 'Title of the book'} when the focus leaves
# # the input field.
# observe_field 'book_title',
@@ -555,7 +558,7 @@ module ActionView
build_observer('Form.Element.EventObserver', field_id, options)
end
end
-
+
# Observes the form with the DOM ID specified by +form_id+ and calls a
# callback when its contents have changed. The default callback is an
# Ajax call. By default all fields of the observed field are sent as
@@ -571,16 +574,17 @@ module ActionView
build_observer('Form.EventObserver', form_id, options)
end
end
-
- # All the methods were moved to GeneratorMethods so that
+
+ # All the methods were moved to GeneratorMethods so that
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
@context, @lines = context, []
+ @context.output_buffer = @lines if @context
include_helpers_from_context
@context.instance_exec(self, &block)
end
-
+
private
def include_helpers_from_context
@context.extended_by.each do |mod|
@@ -588,17 +592,17 @@ module ActionView
end
extend GeneratorMethods
end
-
- # JavaScriptGenerator generates blocks of JavaScript code that allow you
- # to change the content and presentation of multiple DOM elements. Use
+
+ # JavaScriptGenerator generates blocks of JavaScript code that allow you
+ # to change the content and presentation of multiple DOM elements. Use
# this in your Ajax response bodies, either in a <script> tag or as plain
# JavaScript sent with a Content-type of "text/javascript".
#
- # Create new instances with PrototypeHelper#update_page or with
- # ActionController::Base#render, then call +insert_html+, +replace_html+,
- # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
- # methods on the yielded generator in any order you like to modify the
- # content and appearance of the current page.
+ # Create new instances with PrototypeHelper#update_page or with
+ # ActionController::Base#render, then call +insert_html+, +replace_html+,
+ # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in
+ # methods on the yielded generator in any order you like to modify the
+ # content and appearance of the current page.
#
# Example:
#
@@ -611,12 +615,12 @@ module ActionView
# page.visual_effect :highlight, 'list'
# page.hide 'status-indicator', 'cancel-link'
# end
- #
+ #
#
# Helper methods can be used in conjunction with JavaScriptGenerator.
- # When a helper method is called inside an update block on the +page+
+ # When a helper method is called inside an update block on the +page+
# object, that method will also have access to a +page+ object.
- #
+ #
# Example:
#
# module ApplicationHelper
@@ -652,7 +656,7 @@ module ActionView
# end
# end
#
- # You can also use PrototypeHelper#update_page_tag instead of
+ # You can also use PrototypeHelper#update_page_tag instead of
# PrototypeHelper#update_page to wrap the generated JavaScript in a
# <script> tag.
module GeneratorMethods
@@ -665,7 +669,7 @@ module ActionView
end
end
end
-
+
# Returns a element reference by finding it through +id+ in the DOM. This element can then be
# used for further method calls. Examples:
#
@@ -686,31 +690,31 @@ module ActionView
JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id))
end
end
-
- # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
+
+ # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
# expression as an argument to another JavaScriptGenerator method.
def literal(code)
ActiveSupport::JSON::Variable.new(code.to_s)
end
-
+
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
# used for further method calls. Examples:
#
# page.select('p') # => $$('p');
# page.select('p.welcome b').first # => $$('p.welcome b').first();
# page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
- #
+ #
# You can also use prototype enumerations with the collection. Observe:
- #
+ #
# # Generates: $$('#items li').each(function(value) { value.hide(); });
# page.select('#items li').each do |value|
# value.hide
- # end
+ # end
#
- # Though you can call the block param anything you want, they are always rendered in the
+ # Though you can call the block param anything you want, they are always rendered in the
# javascript as 'value, index.' Other enumerations, like collect() return the last statement:
#
- # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
+ # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); });
# page.select('#items li').collect('hidden') do |item|
# item.hide
# end
@@ -718,13 +722,13 @@ module ActionView
def select(pattern)
JavaScriptElementCollectionProxy.new(self, pattern)
end
-
+
# Inserts HTML at the specified +position+ relative to the DOM element
# identified by the given +id+.
- #
+ #
# +position+ may be one of:
- #
- # <tt>:top</tt>:: HTML is inserted inside the element, before the
+ #
+ # <tt>:top</tt>:: HTML is inserted inside the element, before the
# element's existing content.
# <tt>:bottom</tt>:: HTML is inserted inside the element, after the
# element's existing content.
@@ -747,7 +751,7 @@ module ActionView
insertion = position.to_s.camelize
call "new Insertion.#{insertion}", id, render(*options_for_render)
end
-
+
# Replaces the inner HTML of the DOM element with the given +id+.
#
# +options_for_render+ may be either a string of HTML to insert, or a hash
@@ -761,7 +765,7 @@ module ActionView
def replace_html(id, *options_for_render)
call 'Element.update', id, render(*options_for_render)
end
-
+
# Replaces the "outer HTML" (i.e., the entire element, not just its
# contents) of the DOM element with the given +id+.
#
@@ -783,7 +787,7 @@ module ActionView
# </div>
#
# # Insert a new person
- # #
+ # #
# # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, "");
# page.insert_html :bottom, :partial => 'person', :object => @person
#
@@ -795,7 +799,7 @@ module ActionView
def replace(id, *options_for_render)
call 'Element.replace', id, render(*options_for_render)
end
-
+
# Removes the DOM elements with the given +ids+ from the page.
#
# Example:
@@ -807,9 +811,9 @@ module ActionView
def remove(*ids)
loop_on_multiple_args 'Element.remove', ids
end
-
+
# Shows hidden DOM elements with the given +ids+.
- #
+ #
# Example:
#
# # Show a few people
@@ -819,7 +823,7 @@ module ActionView
def show(*ids)
loop_on_multiple_args 'Element.show', ids
end
-
+
# Hides the visible DOM elements with the given +ids+.
#
# Example:
@@ -829,9 +833,9 @@ module ActionView
# page.hide 'person_29', 'person_9', 'person_0'
#
def hide(*ids)
- loop_on_multiple_args 'Element.hide', ids
+ loop_on_multiple_args 'Element.hide', ids
end
-
+
# Toggles the visibility of the DOM elements with the given +ids+.
# Example:
#
@@ -841,9 +845,9 @@ module ActionView
# page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements
#
def toggle(*ids)
- loop_on_multiple_args 'Element.toggle', ids
+ loop_on_multiple_args 'Element.toggle', ids
end
-
+
# Displays an alert dialog with the given +message+.
#
# Example:
@@ -853,21 +857,21 @@ module ActionView
def alert(message)
call 'alert', message
end
-
+
# Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+.
#
# Examples:
#
# # Generates: window.location.href = "/mycontroller";
# page.redirect_to(:action => 'index')
- #
+ #
# # Generates: window.location.href = "/account/signup";
# page.redirect_to(:controller => 'account', :action => 'signup')
def redirect_to(location)
url = location.is_a?(String) ? location : @context.url_for(location)
record "window.location.href = #{url.inspect}"
end
-
+
# Reloads the browser's current +location+ using JavaScript
#
# Examples:
@@ -881,17 +885,17 @@ module ActionView
# Calls the JavaScript +function+, optionally with the given +arguments+.
#
# If a block is given, the block will be passed to a new JavaScriptGenerator;
- # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
+ # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt>
# and passed as the called function's final argument.
- #
+ #
# Examples:
#
# # Generates: Element.replace(my_element, "My content to replace with.")
# page.call 'Element.replace', 'my_element', "My content to replace with."
- #
+ #
# # Generates: alert('My message!')
# page.call 'alert', 'My message!'
- #
+ #
# # Generates:
# # my_method(function() {
# # $("one").show();
@@ -904,7 +908,7 @@ module ActionView
def call(function, *arguments, &block)
record "#{function}(#{arguments_for_call(arguments, block)})"
end
-
+
# Assigns the JavaScript +variable+ the given +value+.
#
# Examples:
@@ -915,13 +919,13 @@ module ActionView
# # Generates: record_count = 33;
# page.assign 'record_count', 33
#
- # # Generates: tabulated_total = 47
+ # # Generates: tabulated_total = 47
# page.assign 'tabulated_total', @total_from_cart
#
def assign(variable, value)
record "#{variable} = #{javascript_object_for(value)}"
end
-
+
# Writes raw JavaScript to the page.
#
# Example:
@@ -930,10 +934,10 @@ module ActionView
def <<(javascript)
@lines << javascript
end
-
+
# Executes the content of the block after a delay of +seconds+. Example:
#
- # # Generates:
+ # # Generates:
# # setTimeout(function() {
# # ;
# # new Effect.Fade("notice",{});
@@ -946,13 +950,13 @@ module ActionView
yield
record "}, #{(seconds * 1000).to_i})"
end
-
- # Starts a script.aculo.us visual effect. See
+
+ # Starts a script.aculo.us visual effect. See
# ActionView::Helpers::ScriptaculousHelper for more information.
def visual_effect(name, id = nil, options = {})
record @context.send(:visual_effect, name, id, options)
end
-
+
# Creates a script.aculo.us sortable element. Useful
# to recreate sortable elements after items get added
# or deleted.
@@ -960,66 +964,66 @@ module ActionView
def sortable(id, options = {})
record @context.send(:sortable_element_js, id, options)
end
-
+
# Creates a script.aculo.us draggable element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def draggable(id, options = {})
record @context.send(:draggable_element_js, id, options)
end
-
+
# Creates a script.aculo.us drop receiving element.
# See ActionView::Helpers::ScriptaculousHelper for more information.
def drop_receiving(id, options = {})
record @context.send(:drop_receiving_element_js, id, options)
end
-
+
private
def loop_on_multiple_args(method, ids)
- record(ids.size>1 ?
- "#{javascript_object_for(ids)}.each(#{method})" :
+ record(ids.size>1 ?
+ "#{javascript_object_for(ids)}.each(#{method})" :
"#{method}(#{ids.first.to_json})")
end
-
+
def page
self
end
-
+
def record(line)
returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do
self << line
end
end
-
+
def render(*options_for_render)
old_format = @context && @context.template_format
@context.template_format = :html if @context
- Hash === options_for_render.first ?
- @context.render(*options_for_render) :
+ Hash === options_for_render.first ?
+ @context.render(*options_for_render) :
options_for_render.first.to_s
ensure
@context.template_format = old_format if @context
end
-
+
def javascript_object_for(object)
object.respond_to?(:to_json) ? object.to_json : object.inspect
end
-
+
def arguments_for_call(arguments, block = nil)
arguments << block_to_function(block) if block
arguments.map { |argument| javascript_object_for(argument) }.join ', '
end
-
+
def block_to_function(block)
generator = self.class.new(@context, &block)
literal("function() { #{generator.to_s} }")
- end
+ end
def method_missing(method, *arguments)
JavaScriptProxy.new(self, method.to_s.camelize)
end
end
end
-
+
# Yields a JavaScriptGenerator and returns the generated JavaScript code.
# Use this to update multiple elements on a page in an Ajax response.
# See JavaScriptGenerator for more information.
@@ -1032,13 +1036,13 @@ module ActionView
def update_page(&block)
JavaScriptGenerator.new(@template, &block).to_s
end
-
+
# Works like update_page but wraps the generated JavaScript in a <script>
# tag. Use this to include generated JavaScript in an ERb template.
# See JavaScriptGenerator for more information.
#
# +html_options+ may be a hash of <script> attributes to be passed
- # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
+ # to ActionView::Helpers::JavaScriptHelper#javascript_tag.
def update_page_tag(html_options = {}, &block)
javascript_tag update_page(&block), html_options
end
@@ -1046,7 +1050,7 @@ module ActionView
protected
def options_for_ajax(options)
js_options = build_callbacks(options)
-
+
js_options['asynchronous'] = options[:type] != :synchronous
js_options['method'] = method_option_to_s(options[:method]) if options[:method]
js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position]
@@ -1059,7 +1063,7 @@ module ActionView
elsif options[:with]
js_options['parameters'] = options[:with]
end
-
+
if protect_against_forgery? && !options[:form]
if js_options['parameters']
js_options['parameters'] << " + '&"
@@ -1068,14 +1072,14 @@ module ActionView
end
js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')"
end
-
+
options_for_javascript(js_options)
end
- def method_option_to_s(method)
+ def method_option_to_s(method)
(method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'"
end
-
+
def build_observer(klass, name, options = {})
if options[:with] && (options[:with] !~ /[\{=(.]/)
options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)"
@@ -1092,7 +1096,7 @@ module ActionView
javascript << ")"
javascript_tag(javascript)
end
-
+
def build_callbacks(options)
callbacks = {}
options.each do |callback, code|
@@ -1105,7 +1109,7 @@ module ActionView
end
end
- # Converts chained method calls on DOM proxy elements into JavaScript chains
+ # Converts chained method calls on DOM proxy elements into JavaScript chains
class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc:
def initialize(generator, root = nil)
@@ -1121,7 +1125,7 @@ module ActionView
call("#{method.to_s.camelize(:lower)}", *arguments, &block)
end
end
-
+
def call(function, *arguments, &block)
append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})")
self
@@ -1130,23 +1134,23 @@ module ActionView
def assign(variable, value)
append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}")
end
-
+
def function_chain
@function_chain ||= @generator.instance_variable_get(:@lines)
end
-
+
def append_to_function_chain!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call};"
end
end
-
+
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
def initialize(generator, id)
@id = id
super(generator, "$(#{id.to_json})")
end
-
+
# Allows access of element attributes through +attribute+. Examples:
#
# page['foo']['style'] # => $('foo').style;
@@ -1157,11 +1161,11 @@ module ActionView
append_to_function_chain!(attribute)
self
end
-
+
def []=(variable, value)
assign(variable, value)
end
-
+
def replace_html(*options_for_render)
call 'update', @generator.send(:render, *options_for_render)
end
@@ -1169,11 +1173,11 @@ module ActionView
def replace(*options_for_render)
call 'replace', @generator.send(:render, *options_for_render)
end
-
+
def reload(options_for_replace = {})
replace(options_for_replace.merge({ :partial => @id.to_s }))
end
-
+
end
class JavaScriptVariableProxy < JavaScriptProxy #:nodoc:
@@ -1192,7 +1196,7 @@ module ActionView
def to_json(options = nil)
@variable
end
-
+
private
def append_to_function_chain!(call)
@generator << @variable if @empty
@@ -1210,7 +1214,7 @@ module ActionView
def initialize(generator, pattern)
super(generator, @pattern = pattern)
end
-
+
def each_slice(variable, number, &block)
if block
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
@@ -1219,18 +1223,18 @@ module ActionView
append_enumerable_function!("eachSlice(#{number.to_json});")
end
end
-
+
def grep(variable, pattern, &block)
enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block
end
-
+
def in_groups_of(variable, number, fill_with = nil)
arguments = [number]
arguments << fill_with unless fill_with.nil?
add_variable_assignment!(variable)
append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});")
- end
-
+ end
+
def inject(variable, memo, &block)
enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block
end
@@ -1292,13 +1296,13 @@ module ActionView
function_chain.push("return #{function_chain.pop.chomp(';')};")
end
end
-
+
def append_enumerable_function!(call)
function_chain[-1].chomp!(';')
function_chain[-1] += ".#{call}"
end
end
-
+
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
def initialize(generator, pattern)
super(generator, "$$(#{pattern.to_json})")
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index e1abec1847..5a296da247 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -12,14 +12,14 @@ module ActionView
BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
- # Returns an empty HTML tag of type +name+ which by default is XHTML
- # compliant. Set +open+ to true to create an open tag compatible
- # with HTML 4.0 and below. Add HTML attributes by passing an attributes
+ # Returns an empty HTML tag of type +name+ which by default is XHTML
+ # compliant. Set +open+ to true to create an open tag compatible
+ # with HTML 4.0 and below. Add HTML attributes by passing an attributes
# hash to +options+. Set +escape+ to false to disable attribute value
# escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -30,7 +30,7 @@ module ActionView
# tag("br", nil, true)
# # => <br>
#
- # tag("input", { :type => 'text', :disabled => true })
+ # tag("input", { :type => 'text', :disabled => true })
# # => <input type="text" disabled="disabled" />
#
# tag("img", { :src => "open & shut.png" })
@@ -43,13 +43,13 @@ module ActionView
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
- # HTML attributes by passing an attributes hash to +options+.
+ # HTML attributes by passing an attributes hash to +options+.
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +options+ as the second parameter.
# Set escape to false to disable attribute value escaping.
#
# ==== Options
- # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
+ # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and
# <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use
# symbols or strings for the attribute names.
#
@@ -68,7 +68,13 @@ module ActionView
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
if block_given?
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
- concat(content_tag_string(name, capture(&block), options, escape))
+ content_tag = content_tag_string(name, capture(&block), options, escape)
+
+ if block_called_from_erb?(block)
+ concat(content_tag)
+ else
+ content_tag
+ end
else
content_tag_string(name, content_or_options_with_block, options, escape)
end
@@ -102,6 +108,22 @@ module ActionView
end
private
+ BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
+
+ if RUBY_VERSION < '1.9.0'
+ # Check whether we're called from an erb template.
+ # We'd return a string in any other case, but erb <%= ... %>
+ # can't take an <% end %> later on, so we have to use <% ... %>
+ # and implicitly concat.
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block)
+ end
+ else
+ def block_called_from_erb?(block)
+ block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
+ end
+ end
+
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
"<#{name}#{tag_options}>#{content}</#{name}>"
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index a1a91f6b3d..3e3452b615 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -27,14 +27,10 @@ module ActionView
# %>
def concat(string, unused_binding = nil)
if unused_binding
- ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.")
+ ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller)
end
- if output_buffer && string
- output_buffer << string
- else
- string
- end
+ output_buffer << string
end
if RUBY_VERSION < '1.9'
@@ -472,7 +468,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
- (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:][^\s$]))+)?)* # path
+ (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))+)?)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index baecd304cd..e5178938fd 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -63,17 +63,15 @@ module ActionView
# # calls @workshop.to_s
# # => /workshops/5
def url_for(options = {})
+ options ||= {}
case options
when Hash
- show_path = options[:host].nil? ? true : false
- options = { :only_path => show_path }.update(options.symbolize_keys)
+ options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
url = @controller.send(:url_for, options)
when String
escape = true
url = options
- when NilClass
- url = @controller.send(:url_for, nil)
else
escape = false
url = polymorphic_path(options)
@@ -468,7 +466,7 @@ module ActionView
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
if encode == "javascript"
- "document.write('#{content_tag("a", name || email_address, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
+ "document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
@@ -535,7 +533,7 @@ module ActionView
when method
"#{method_javascript_function(method, url, href)}return false;"
when popup
- popup_javascript_function(popup) + 'return false;'
+ "#{popup_javascript_function(popup)}return false;"
else
html_options["onclick"]
end
diff --git a/actionpack/lib/action_view/inline_template.rb b/actionpack/lib/action_view/inline_template.rb
index fd0ad48302..5e00cef13f 100644
--- a/actionpack/lib/action_view/inline_template.rb
+++ b/actionpack/lib/action_view/inline_template.rb
@@ -1,17 +1,19 @@
module ActionView #:nodoc:
- class InlineTemplate < Template #:nodoc:
- def initialize(view, source, locals = {}, type = nil)
- @view = view
+ class InlineTemplate #:nodoc:
+ include Renderable
+ attr_reader :source, :extension, :method_segment
+
+ def initialize(source, type = nil)
@source = source
@extension = type
- @locals = locals || {}
-
- @handler = self.class.handler_class_for_extension(@extension).new(@view)
+ @method_segment = "inline_#{@source.hash.abs}"
end
- def method_key
- @source
- end
+ private
+ # Always recompile inline templates
+ def recompile?(local_assigns)
+ true
+ end
end
end
diff --git a/actionpack/lib/action_view/partial_template.rb b/actionpack/lib/action_view/partial_template.rb
deleted file mode 100644
index 0cf996ca04..0000000000
--- a/actionpack/lib/action_view/partial_template.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-module ActionView #:nodoc:
- class PartialTemplate < Template #:nodoc:
- attr_reader :variable_name, :object
-
- def initialize(view, partial_path, object = nil, locals = {})
- @view_controller = view.controller if view.respond_to?(:controller)
- set_path_and_variable_name!(partial_path)
- super(view, @path, true, locals)
- add_object_to_local_assigns!(object)
-
- # This is needed here in order to compile template with knowledge of 'counter'
- initialize_counter!
-
- # Prepare early. This is a performance optimization for partial collections
- prepare!
- end
-
- def render
- ActionController::Base.benchmark("Rendered #{@path.path_without_format_and_extension}", Logger::DEBUG, false) do
- @handler.render(self)
- end
- end
-
- def render_member(object)
- @locals[:object] = @locals[@variable_name] = object
-
- template = render_template
- @locals[@counter_name] += 1
- @locals.delete(@variable_name)
- @locals.delete(:object)
-
- template
- end
-
- def counter=(num)
- @locals[@counter_name] = num
- end
-
- private
- def add_object_to_local_assigns!(object)
- @locals[:object] ||=
- @locals[@variable_name] ||=
- if object.is_a?(ActionView::Base::ObjectWrapper)
- object.value
- else
- object
- end || @view_controller.instance_variable_get("@#{variable_name}")
- end
-
- def set_path_and_variable_name!(partial_path)
- if partial_path.include?('/')
- @variable_name = File.basename(partial_path)
- @path = "#{File.dirname(partial_path)}/_#{@variable_name}"
- elsif @view_controller
- @variable_name = partial_path
- @path = "#{@view_controller.class.controller_path}/_#{@variable_name}"
- else
- @variable_name = partial_path
- @path = "_#{@variable_name}"
- end
-
- @variable_name = @variable_name.sub(/\..*$/, '').to_sym
- end
-
- def initialize_counter!
- @counter_name ||= "#{@variable_name}_counter".to_sym
- @locals[@counter_name] = 0
- end
- end
-end
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index 6b294be6bd..5aa4c83009 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -104,10 +104,11 @@ module ActionView
module Partials
private
def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc:
+ local_assigns ||= {}
+
case partial_path
when String, Symbol, NilClass
- # Render the template
- ActionView::PartialTemplate.new(self, partial_path, object_assigns, local_assigns).render_template
+ pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns)
when ActionView::Helpers::FormBuilder
builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '')
render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path))
@@ -123,35 +124,33 @@ module ActionView
end
end
- def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}) #:nodoc:
+ def render_partial_collection(partial_path, collection, partial_spacer_template = nil, local_assigns = {}, as = nil) #:nodoc:
return " " if collection.empty?
local_assigns = local_assigns ? local_assigns.clone : {}
spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : ''
+ _paths = {}
+ _templates = {}
- if partial_path.nil?
- render_partial_collection_with_unknown_partial_path(collection, local_assigns)
- else
- render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns)
+ index = 0
+ collection.map do |object|
+ _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path)
+ path = _paths[_partial_path] ||= find_partial_path(_partial_path)
+ template = _templates[path] ||= pick_template(path)
+ local_assigns[template.counter_name] = index
+ result = template.render_partial(self, object, local_assigns, as)
+ index += 1
+ result
end.join(spacer)
end
- def render_partial_collection_with_known_partial_path(collection, partial_path, local_assigns)
- template = ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns)
- collection.map do |element|
- template.render_member(element)
- end
- end
-
- def render_partial_collection_with_unknown_partial_path(collection, local_assigns)
- templates = Hash.new
- i = 0
- collection.map do |element|
- partial_path = ActionController::RecordIdentifier.partial_path(element, controller.class.controller_path)
- template = templates[partial_path] ||= ActionView::PartialTemplate.new(self, partial_path, nil, local_assigns)
- template.counter = i
- i += 1
- template.render_member(element)
+ def find_partial_path(partial_path)
+ if partial_path.include?('/')
+ "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
+ elsif respond_to?(:controller)
+ "#{controller.class.controller_path}/_#{partial_path}"
+ else
+ "_#{partial_path}"
end
end
end
diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb
new file mode 100644
index 0000000000..b0ab7d0c67
--- /dev/null
+++ b/actionpack/lib/action_view/paths.rb
@@ -0,0 +1,96 @@
+module ActionView #:nodoc:
+ class PathSet < Array #:nodoc:
+ def self.type_cast(obj)
+ if obj.is_a?(String)
+ if Base.warn_cache_misses && defined?(Rails) && Rails.initialized?
+ Rails.logger.debug "[PERFORMANCE] Processing view path during a " +
+ "request. This an expense disk operation that should be done at " +
+ "boot. You can manually process this view path with " +
+ "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " +
+ "as your view path"
+ end
+ Path.new(obj)
+ else
+ obj
+ end
+ end
+
+ class Path #:nodoc:
+ attr_reader :path, :paths
+ delegate :to_s, :to_str, :inspect, :to => :path
+
+ def initialize(path)
+ @path = path.freeze
+ reload!
+ end
+
+ def ==(path)
+ to_str == path.to_str
+ end
+
+ def [](path)
+ @paths[path]
+ end
+
+ # Rebuild load path directory cache
+ def reload!
+ @paths = {}
+
+ templates_in_path do |template|
+ @paths[template.path] = template
+ @paths[template.path_without_extension] ||= template
+ end
+
+ @paths.freeze
+ end
+
+ private
+ def templates_in_path
+ (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file|
+ unless File.directory?(file)
+ template = Template.new(file.split("#{self}/").last, self)
+ # Eager load memoized methods and freeze cached template
+ template.freeze if Base.cache_template_loading
+ yield template
+ end
+ end
+ end
+ end
+
+ def initialize(*args)
+ super(*args).map! { |obj| self.class.type_cast(obj) }
+ end
+
+ def reload!
+ each { |path| path.reload! }
+ end
+
+ def <<(obj)
+ super(self.class.type_cast(obj))
+ end
+
+ def push(*objs)
+ delete_paths!(objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+
+ def unshift(*objs)
+ delete_paths!(objs)
+ super(*objs.map { |obj| self.class.type_cast(obj) })
+ end
+
+ def [](template_path)
+ each do |path|
+ if template = path[template_path]
+ return template
+ end
+ end
+ nil
+ end
+
+ private
+ def delete_paths!(paths)
+ paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
new file mode 100644
index 0000000000..f66356c939
--- /dev/null
+++ b/actionpack/lib/action_view/renderable.rb
@@ -0,0 +1,78 @@
+module ActionView
+ module Renderable
+ # NOTE: The template that this mixin is beening include into is frozen
+ # So you can not set or modify any instance variables
+
+ def self.included(base)
+ @@mutex = Mutex.new
+ end
+
+ include ActiveSupport::Memoizable
+
+ def handler
+ Template.handler_class_for_extension(extension)
+ end
+ memoize :handler
+
+ def compiled_source
+ handler.new(nil).compile(self) if handler.compilable?
+ end
+ memoize :compiled_source
+
+ def render(view, local_assigns = {})
+ view._first_render ||= self
+ view._last_render = self
+ view.send(:evaluate_assigns)
+ compile(local_assigns) if handler.compilable?
+ handler.new(view).render(self, local_assigns)
+ end
+
+ def method(local_assigns)
+ if local_assigns && local_assigns.any?
+ local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
+ end
+ ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym
+ end
+
+ private
+ # Compile and evaluate the template's code
+ def compile(local_assigns)
+ render_symbol = method(local_assigns)
+
+ @@mutex.synchronize do
+ return false unless recompile?(render_symbol)
+
+ locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join
+
+ source = <<-end_src
+ def #{render_symbol}(local_assigns)
+ old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
+ ensure
+ self.output_buffer = old_output_buffer
+ end
+ end_src
+
+ begin
+ file_name = respond_to?(:filename) ? filename : 'compiled-template'
+ ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0)
+ rescue Exception => e # errors from template code
+ if logger = ActionController::Base.logger
+ logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
+ logger.debug "Function body: #{source}"
+ logger.debug "Backtrace: #{e.backtrace.join("\n")}"
+ end
+
+ raise ActionView::TemplateError.new(self, {}, e)
+ end
+ end
+ end
+
+ # Method to check whether template compilation is necessary.
+ # The template will be compiled if the file has not been compiled yet, or
+ # if local_assigns has a new key, which isn't supported by the compiled code yet.
+ def recompile?(symbol)
+ meth = Base::CompiledTemplates.instance_method(template.method) rescue nil
+ !(meth && Base.cache_template_loading)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb
new file mode 100644
index 0000000000..fdb1a5e6a7
--- /dev/null
+++ b/actionpack/lib/action_view/renderable_partial.rb
@@ -0,0 +1,36 @@
+module ActionView
+ module RenderablePartial
+ # NOTE: The template that this mixin is beening include into is frozen
+ # So you can not set or modify any instance variables
+
+ include ActiveSupport::Memoizable
+
+ def variable_name
+ name.sub(/\A_/, '').to_sym
+ end
+ memoize :variable_name
+
+ def counter_name
+ "#{variable_name}_counter".to_sym
+ end
+ memoize :counter_name
+
+ def render(view, local_assigns = {})
+ ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do
+ super
+ end
+ end
+
+ def render_partial(view, object = nil, local_assigns = {}, as = nil)
+ object ||= local_assigns[:object] ||
+ local_assigns[variable_name] ||
+ view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller)
+
+ # Ensure correct object is reassigned to other accessors
+ local_assigns[:object] = local_assigns[variable_name] = object
+ local_assigns[as] = object if as
+
+ render_template(view, local_assigns)
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 4c3f252c10..304aec3a4c 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,94 +1,96 @@
module ActionView #:nodoc:
- class Template #:nodoc:
+ class Template
extend TemplateHandlers
+ include ActiveSupport::Memoizable
+ include Renderable
- attr_accessor :locals
- attr_reader :handler, :path, :extension, :filename, :method
+ attr_accessor :filename, :load_path, :base_path, :name, :format, :extension
+ delegate :to_s, :to => :path
- def initialize(view, path, use_full_path, locals = {})
- @view = view
- @paths = view.view_paths
+ def initialize(template_path, load_paths = [])
+ template_path = template_path.dup
+ @base_path, @name, @format, @extension = split(template_path)
+ @base_path.to_s.gsub!(/\/$/, '') # Push to split method
+ @load_path, @filename = find_full_path(template_path, load_paths)
- @original_path = path
- @path = TemplateFile.from_path(path, !use_full_path)
- @view.first_render ||= @path.to_s
- @source = nil # Don't read the source until we know that it is required
- set_extension_and_file_name(use_full_path)
-
- @locals = locals || {}
- @handler = self.class.handler_class_for_extension(@extension).new(@view)
+ # Extend with partial super powers
+ extend RenderablePartial if @name =~ /^_/
end
- def render_template
- render
- rescue Exception => e
- raise e unless filename
- if TemplateError === e
- e.sub_template_of(filename)
- raise e
- else
- raise TemplateError.new(self, @view.assigns, e)
- end
+ def format_and_extension
+ (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions
end
+ memoize :format_and_extension
- def render
- prepare!
- @handler.render(self)
+ def path
+ [base_path, [name, format, extension].compact.join('.')].compact.join('/')
end
+ memoize :path
def path_without_extension
- @path.path_without_extension
+ [base_path, [name, format].compact.join('.')].compact.join('/')
end
+ memoize :path_without_extension
- def source
- @source ||= File.read(self.filename)
+ def path_without_format_and_extension
+ [base_path, name].compact.join('/')
end
+ memoize :path_without_format_and_extension
- def method_key
- @filename
+ def source
+ File.read(filename)
end
+ memoize :source
- def base_path_for_exception
- (@paths.find_load_path_for_path(@path) || @paths.first).to_s
+ def method_segment
+ segment = File.expand_path(filename)
+ segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
+ segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
end
+ memoize :method_segment
- def prepare!
- @view.send :evaluate_assigns
- @view.current_render_extension = @extension
-
- if @handler.compilable?
- @handler.compile_template(self) # compile the given template, if necessary
- @method = @view.method_names[method_key] # Set the method name for this template and run it
+ def render_template(view, local_assigns = {})
+ render(view, local_assigns)
+ rescue Exception => e
+ raise e unless filename
+ if TemplateError === e
+ e.sub_template_of(filename)
+ raise e
+ else
+ raise TemplateError.new(self, view.assigns, e)
end
end
private
- def set_extension_and_file_name(use_full_path)
- @extension = @path.extension
-
- if use_full_path
- unless @extension
- @path = @view.send(:template_file_from_name, @path)
- raise_missing_template_exception unless @path
- @extension = @path.extension
- end
+ def valid_extension?(extension)
+ Template.template_handler_extensions.include?(extension)
+ end
- if @path = @paths.find_template_file_for_path(path)
- @filename = @path.full_path
- @extension = @path.extension
- end
- else
- @filename = @path.full_path
+ def find_full_path(path, load_paths)
+ load_paths = Array(load_paths) + [nil]
+ load_paths.each do |load_path|
+ file = [load_path, path].compact.join('/')
+ return load_path, file if File.exist?(file)
end
-
- raise_missing_template_exception if @filename.blank?
+ raise MissingTemplate.new(load_paths, path)
end
- def raise_missing_template_exception
- full_template_path = @original_path.include?('.') ? @original_path : "#{@original_path}.#{@view.template_format}.erb"
- display_paths = @paths.join(':')
- template_type = (@original_path =~ /layouts/i) ? 'layout' : 'template'
- raise(MissingTemplate, "Missing #{template_type} #{full_template_path} in view path #{display_paths}")
+ # Returns file split into an array
+ # [base_path, name, format, extension]
+ def split(file)
+ if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
+ if m[5] # Mulipart formats
+ [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
+ elsif m[4] # Single format
+ [m[1], m[2], m[3], m[4]]
+ else
+ if valid_extension?(m[3]) # No format
+ [m[1], m[2], nil, m[3]]
+ else # No extension
+ [m[1], m[2], m[3], nil]
+ end
+ end
+ end
end
end
end
diff --git a/actionpack/lib/action_view/template_error.rb b/actionpack/lib/action_view/template_error.rb
index 65d80362b5..35fc07bdb2 100644
--- a/actionpack/lib/action_view/template_error.rb
+++ b/actionpack/lib/action_view/template_error.rb
@@ -7,7 +7,7 @@ module ActionView
attr_reader :original_exception
def initialize(template, assigns, original_exception)
- @base_path = template.base_path_for_exception
+ @base_path = template.base_path
@assigns, @source, @original_exception = assigns.dup, template.source, original_exception
@file_path = template.filename
@backtrace = compute_backtrace
@@ -105,6 +105,6 @@ module ActionView
end
if defined?(Exception::TraceSubstitutions)
- Exception::TraceSubstitutions << [/:in\s+`_run_(html|xml).*'\s*$/, '']
+ Exception::TraceSubstitutions << [/:in\s+`_run_.*'\s*$/, '']
Exception::TraceSubstitutions << [%r{^\s*#{Regexp.escape RAILS_ROOT}/}, ''] if defined?(RAILS_ROOT)
end
diff --git a/actionpack/lib/action_view/template_file.rb b/actionpack/lib/action_view/template_file.rb
deleted file mode 100644
index dd66482b3c..0000000000
--- a/actionpack/lib/action_view/template_file.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-module ActionView #:nodoc:
- # TemplateFile abstracts the pattern of querying a file path for its
- # path with or without its extension. The path is only the partial path
- # from the load path root e.g. "hello/index.html.erb" not
- # "app/views/hello/index.html.erb"
- class TemplateFile
- def self.from_path(path, use_full_path = false)
- path.is_a?(self) ? path : new(path, use_full_path)
- end
-
- def self.from_full_path(load_path, full_path)
- file = new(full_path.split(load_path).last)
- file.load_path = load_path
- file.freeze
- end
-
- attr_accessor :load_path, :base_path, :name, :format, :extension
- delegate :to_s, :inspect, :to => :path
-
- def initialize(path, use_full_path = false)
- path = path.dup
-
- # Clear the forward slash in the beginning unless using full path
- trim_forward_slash!(path) unless use_full_path
-
- @base_path, @name, @format, @extension = split(path)
- end
-
- def freeze
- @load_path.freeze
- @base_path.freeze
- @name.freeze
- @format.freeze
- @extension.freeze
- super
- end
-
- def format_and_extension
- extensions = [format, extension].compact.join(".")
- extensions.blank? ? nil : extensions
- end
-
- def full_path
- if load_path
- "#{load_path}/#{path}"
- else
- path
- end
- end
-
- def path
- base_path.to_s + [name, format, extension].compact.join(".")
- end
-
- def path_without_extension
- base_path.to_s + [name, format].compact.join(".")
- end
-
- def path_without_format_and_extension
- "#{base_path}#{name}"
- end
-
- def dup_with_extension(extension)
- file = dup
- file.extension = extension ? extension.to_s : nil
- file
- end
-
- private
- def trim_forward_slash!(path)
- path.sub!(/^\//, '')
- end
-
- # Returns file split into an array
- # [base_path, name, format, extension]
- def split(file)
- if m = file.match(/^(.*\/)?(\w+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if m[5] # Mulipart formats
- [m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
- elsif m[4] # Single format
- [m[1], m[2], m[3], m[4]]
- else # No format
- [m[1], m[2], nil, m[3]]
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb
index 39e578e586..e2dd305f93 100644
--- a/actionpack/lib/action_view/template_handler.rb
+++ b/actionpack/lib/action_view/template_handler.rb
@@ -1,9 +1,5 @@
module ActionView
class TemplateHandler
- def self.line_offset
- 0
- end
-
def self.compilable?
false
end
@@ -12,7 +8,7 @@ module ActionView
@view = view
end
- def render(template)
+ def render(template, local_assigns = {})
end
def compile(template)
@@ -21,13 +17,5 @@ module ActionView
def compilable?
self.class.compilable?
end
-
- def line_offset
- self.class.line_offset
- end
-
- # Called by CacheHelper#cache
- def cache_fragment(block, name = {}, options = nil)
- end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb
index ee02ce1a6f..335ec1abb4 100644
--- a/actionpack/lib/action_view/template_handlers/builder.rb
+++ b/actionpack/lib/action_view/template_handlers/builder.rb
@@ -5,23 +5,13 @@ module ActionView
class Builder < TemplateHandler
include Compilable
- def self.line_offset
- 2
- end
-
def compile(template)
- content_type_handler = (@view.send!(:controller).respond_to?(:response) ? "controller.response" : "controller")
-
- "#{content_type_handler}.content_type ||= Mime::XML\n" +
- "xml = ::Builder::XmlMarkup.new(:indent => 2)\n" +
+ # ActionMailer does not have a response
+ "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" +
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
+ "self.output_buffer = xml.target!;" +
template.source +
- "\nxml.target!\n"
- end
-
- def cache_fragment(block, name = {}, options = nil)
- @view.fragment_for(block, name, options) do
- eval('xml.target!', block.binding)
- end
+ ";xml.target!;"
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb
index 1aef81ba1a..a0ebaefeef 100644
--- a/actionpack/lib/action_view/template_handlers/compilable.rb
+++ b/actionpack/lib/action_view/template_handlers/compilable.rb
@@ -1,21 +1,8 @@
module ActionView
module TemplateHandlers
module Compilable
-
def self.included(base)
base.extend ClassMethod
-
- # Map method names to their compile time
- base.cattr_accessor :compile_time
- base.compile_time = {}
-
- # Map method names to the names passed in local assigns so far
- base.cattr_accessor :template_args
- base.template_args = {}
-
- # Count the number of inline templates
- base.cattr_accessor :inline_template_count
- base.inline_template_count = 0
end
module ClassMethod
@@ -24,105 +11,10 @@ module ActionView
true
end
end
-
- def render(template)
- @view.send :execute, template
- end
-
- # Compile and evaluate the template's code
- def compile_template(template)
- return unless compile_template?(template)
-
- render_symbol = assign_method_name(template)
- render_source = create_template_source(template, render_symbol)
- line_offset = self.template_args[render_symbol].size + self.line_offset
-
- begin
- file_name = template.filename || 'compiled-template'
- ActionView::Base::CompiledTemplates.module_eval(render_source, file_name, -line_offset)
- rescue Exception => e # errors from template code
- if @view.logger
- @view.logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
- @view.logger.debug "Function body: #{render_source}"
- @view.logger.debug "Backtrace: #{e.backtrace.join("\n")}"
- end
-
- raise ActionView::TemplateError.new(template, @view.assigns, e)
- end
-
- self.compile_time[render_symbol] = Time.now
- # logger.debug "Compiled template #{file_name || template}\n ==> #{render_symbol}" if logger
- end
-
- private
-
- # Method to check whether template compilation is necessary.
- # The template will be compiled if the inline template or file has not been compiled yet,
- # if local_assigns has a new key, which isn't supported by the compiled code yet,
- # or if the file has changed on disk and checking file mods hasn't been disabled.
- def compile_template?(template)
- method_key = template.method_key
- render_symbol = @view.method_names[method_key]
-
- compile_time = self.compile_time[render_symbol]
- if compile_time && supports_local_assigns?(render_symbol, template.locals)
- if template.filename && !@view.cache_template_loading
- template_changed_since?(template.filename, compile_time)
- end
- else
- true
- end
- end
-
- def assign_method_name(template)
- @view.method_names[template.method_key] ||= compiled_method_name(template)
- end
- def compiled_method_name(template)
- ['_run', self.class.to_s.demodulize.underscore, compiled_method_name_file_path_segment(template.filename)].compact.join('_').to_sym
+ def render(template, local_assigns = {})
+ @view.send(:execute, template, local_assigns)
end
-
- def compiled_method_name_file_path_segment(file_name)
- if file_name
- s = File.expand_path(file_name)
- s.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT)
- s.gsub!(/([^a-zA-Z0-9_])/) { $1.ord }
- s
- else
- (self.inline_template_count += 1).to_s
- end
- end
-
- # Method to create the source code for a given template.
- def create_template_source(template, render_symbol)
- body = compile(template)
-
- self.template_args[render_symbol] ||= {}
- locals_keys = self.template_args[render_symbol].keys | template.locals.keys
- self.template_args[render_symbol] = locals_keys.inject({}) { |h, k| h[k] = true; h }
-
- locals_code = ""
- locals_keys.each do |key|
- locals_code << "#{key} = local_assigns[:#{key}]\n"
- end
-
- "def #{render_symbol}(local_assigns)\nold_output_buffer = output_buffer;#{locals_code}#{body}\nensure\nself.output_buffer = old_output_buffer\nend"
- end
-
- # Return true if the given template was compiled for a superset of the keys in local_assigns
- def supports_local_assigns?(render_symbol, local_assigns)
- local_assigns.empty? ||
- ((args = self.template_args[render_symbol]) && local_assigns.all? { |k,_| args.has_key?(k) })
- end
-
- # Method to handle checking a whether a template has changed since last compile; isolated so that templates
- # not stored on the file system can hook and extend appropriately.
- def template_changed_since?(file_name, compile_time)
- lstat = File.lstat(file_name)
- compile_time < lstat.mtime ||
- (lstat.symlink? && compile_time < File.stat(file_name).mtime)
- end
-
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb
index ad4ccc7c42..2f2febaa52 100644
--- a/actionpack/lib/action_view/template_handlers/erb.rb
+++ b/actionpack/lib/action_view/template_handlers/erb.rb
@@ -42,12 +42,14 @@ module ActionView
class ERB < TemplateHandler
include Compilable
- def compile(template)
- ::ERB.new(template.source, nil, @view.erb_trim_mode, '@output_buffer').src
- end
+ # Specify trim mode for the ERB compiler. Defaults to '-'.
+ # See ERb documentation for suitable values.
+ cattr_accessor :erb_trim_mode
+ self.erb_trim_mode = '-'
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) { @view.response.template.output_buffer ||= '' }
+ def compile(template)
+ src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src
+ "__in_erb_template=true;#{src}"
end
end
end
diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb
index 5854e33fed..a700655c9a 100644
--- a/actionpack/lib/action_view/template_handlers/rjs.rb
+++ b/actionpack/lib/action_view/template_handlers/rjs.rb
@@ -3,24 +3,9 @@ module ActionView
class RJS < TemplateHandler
include Compilable
- def self.line_offset
- 2
- end
-
def compile(template)
- "controller.response.content_type ||= Mime::JS\n" +
- "update_page do |page|\n#{template.source}\nend"
- end
-
- def cache_fragment(block, name = {}, options = nil) #:nodoc:
- @view.fragment_for(block, name, options) do
- begin
- debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false
- eval('page.to_s', block.binding)
- ensure
- ActionView::Base.debug_rjs = debug_mode
- end
- end
+ "controller.response.content_type ||= Mime::JS;" +
+ "update_page do |page|;#{template.source}\nend"
end
end
end
diff --git a/actionpack/lib/action_view/view_load_paths.rb b/actionpack/lib/action_view/view_load_paths.rb
deleted file mode 100644
index e873d96aa0..0000000000
--- a/actionpack/lib/action_view/view_load_paths.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-module ActionView #:nodoc:
- class ViewLoadPaths < Array #:nodoc:
- def self.type_cast(obj)
- obj.is_a?(String) ? LoadPath.new(obj) : obj
- end
-
- class LoadPath #:nodoc:
- attr_reader :path, :paths
- delegate :to_s, :inspect, :to => :path
-
- def initialize(path)
- @path = path.freeze
- reload!
- end
-
- def eql?(view_path)
- view_path.is_a?(ViewPath) && @path == view_path.path
- end
-
- def hash
- @path.hash
- end
-
- # Rebuild load path directory cache
- def reload!
- @paths = {}
-
- files.each do |file|
- @paths[file.path] = file
- @paths[file.path_without_extension] ||= file
- end
-
- @paths.freeze
- end
-
- # Tries to find the extension for the template name.
- # If it does not it exist, tries again without the format extension
- # find_template_file_for_partial_path('users/show') => 'html.erb'
- # find_template_file_for_partial_path('users/legacy') => 'rhtml'
- def find_template_file_for_partial_path(file)
- @paths[file.path] || @paths[file.path_without_extension] || @paths[file.path_without_format_and_extension]
- end
-
- private
- # Get all the files and directories in the path
- def files_in_path
- Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")
- end
-
- # Create an array of all the files within the path
- def files
- files_in_path.map do |file|
- TemplateFile.from_full_path(@path, file) unless File.directory?(file)
- end.compact
- end
- end
-
- def initialize(*args)
- super(*args).map! { |obj| self.class.type_cast(obj) }
- end
-
- def reload!
- each { |path| path.reload! }
- end
-
- def <<(obj)
- super(self.class.type_cast(obj))
- end
-
- def push(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def unshift(*objs)
- delete_paths!(objs)
- super(*objs.map { |obj| self.class.type_cast(obj) })
- end
-
- def template_exists?(file)
- find_load_path_for_path(file) ? true : false
- end
-
- def find_load_path_for_path(file)
- find { |path| path.paths[file.to_s] }
- end
-
- def find_template_file_for_path(file)
- file = TemplateFile.from_path(file)
- each do |path|
- if f = path.find_template_file_for_partial_path(file)
- return f
- end
- end
- nil
- end
-
- private
- def delete_paths!(paths)
- paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } }
- end
- end
-end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index fa1c3293b4..0d2e0f273a 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -22,6 +22,9 @@ ActiveSupport::Deprecation.debug = true
ActionController::Base.logger = nil
ActionController::Routing::Routes.reload rescue nil
+ActionView::Base.cache_template_loading = true
+FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures')
+ActionController::Base.view_paths = FIXTURE_LOAD_PATH
# Wrap tests that use Mocha and skip if unavailable.
def uses_mocha(test_name)
diff --git a/actionpack/test/active_record_unit.rb b/actionpack/test/active_record_unit.rb
index a7d526850e..a377ccad24 100644
--- a/actionpack/test/active_record_unit.rb
+++ b/actionpack/test/active_record_unit.rb
@@ -30,7 +30,6 @@ end
$stderr.flush
-
# Define the rest of the connector
class ActiveRecordTestConnector
class << self
@@ -48,46 +47,45 @@ class ActiveRecordTestConnector
end
private
-
- def setup_connection
- if Object.const_defined?(:ActiveRecord)
- defaults = { :database => ':memory:' }
- begin
- options = defaults.merge :adapter => 'sqlite3', :timeout => 500
- ActiveRecord::Base.establish_connection(options)
- ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
- ActiveRecord::Base.connection
- rescue Exception # errors from establishing a connection
- $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
- options = defaults.merge :adapter => 'sqlite'
- ActiveRecord::Base.establish_connection(options)
- ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
- ActiveRecord::Base.connection
+ def setup_connection
+ if Object.const_defined?(:ActiveRecord)
+ defaults = { :database => ':memory:' }
+ begin
+ options = defaults.merge :adapter => 'sqlite3', :timeout => 500
+ ActiveRecord::Base.establish_connection(options)
+ ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
+ ActiveRecord::Base.connection
+ rescue Exception # errors from establishing a connection
+ $stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
+ options = defaults.merge :adapter => 'sqlite'
+ ActiveRecord::Base.establish_connection(options)
+ ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
+ ActiveRecord::Base.connection
+ end
+
+ Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
+ else
+ raise "Can't setup connection since ActiveRecord isn't loaded."
end
-
- Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
- else
- raise "Can't setup connection since ActiveRecord isn't loaded."
end
- end
- # Load actionpack sqlite tables
- def load_schema
- File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql|
- ActiveRecord::Base.connection.execute(sql) unless sql.blank?
+ # Load actionpack sqlite tables
+ def load_schema
+ File.read(File.dirname(__FILE__) + "/fixtures/db_definitions/sqlite.sql").split(';').each do |sql|
+ ActiveRecord::Base.connection.execute(sql) unless sql.blank?
+ end
end
- end
- def require_fixture_models
- Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
- end
+ def require_fixture_models
+ Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
+ end
end
end
class ActiveRecordTestCase < ActiveSupport::TestCase
# Set our fixture path
if ActiveRecordTestConnector.able_to_connect
- self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
+ self.fixture_path = [FIXTURE_LOAD_PATH]
self.use_transactional_fixtures = false
end
diff --git a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
index ed10e72953..a82a1a3023 100644
--- a/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
+++ b/actionpack/test/activerecord/render_partial_with_record_identification_test.rb
@@ -40,11 +40,10 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
render :partial => @developers
end
end
-RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
fixtures :developers, :projects, :developers_projects, :topics, :replies, :companies, :mascots
-
+
def setup
@controller = RenderPartialWithRecordIdentificationController.new
@request = ActionController::TestRequest.new
@@ -55,26 +54,31 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase
def test_rendering_partial_with_has_many_and_belongs_to_association
get :render_with_has_many_and_belongs_to_association
assert_template 'projects/_project'
+ assert_equal 'Active RecordActive Controller', @response.body
end
-
+
def test_rendering_partial_with_has_many_association
get :render_with_has_many_association
assert_template 'replies/_reply'
+ assert_equal 'Birdman is better!', @response.body
end
-
+
def test_rendering_partial_with_named_scope
get :render_with_named_scope
assert_template 'replies/_reply'
+ assert_equal 'Birdman is better!Nuh uh!', @response.body
end
-
+
def test_render_with_record
get :render_with_record
assert_template 'developers/_developer'
+ assert_equal 'David', @response.body
end
-
+
def test_render_with_record_collection
get :render_with_record_collection
assert_template 'developers/_developer'
+ assert_equal 'DavidJamisfixture_3fixture_4fixture_5fixture_6fixture_7fixture_8fixture_9fixture_10Jamis', @response.body
end
def test_rendering_partial_with_has_one_association
@@ -116,7 +120,6 @@ class RenderPartialWithRecordIdentificationController < ActionController::Base
render :partial => @developers
end
end
-RenderPartialWithRecordIdentificationController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
class Game < Struct.new(:name, :id)
def to_param
@@ -134,7 +137,6 @@ module Fun
render :partial => [ Game.new("Pong"), Game.new("Tank") ]
end
end
- NestedController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
module Serious
class NestedDeeperController < ActionController::Base
@@ -146,7 +148,6 @@ module Fun
render :partial => [ Game.new("Chess"), Game.new("Sudoku"), Game.new("Solitaire") ]
end
end
- NestedDeeperController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
end
end
@@ -161,13 +162,14 @@ class RenderPartialWithRecordIdentificationAndNestedControllersTest < ActiveReco
def test_render_with_record_in_nested_controller
get :render_with_record_in_nested_controller
assert_template 'fun/games/_game'
+ assert_equal 'Pong', @response.body
end
def test_render_with_record_collection_in_nested_controller
get :render_with_record_collection_in_nested_controller
assert_template 'fun/games/_game'
+ assert_equal 'PongTank', @response.body
end
-
end
class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < ActiveRecordTestCase
@@ -181,11 +183,12 @@ class RenderPartialWithRecordIdentificationAndNestedDeeperControllersTest < Acti
def test_render_with_record_in_deeper_nested_controller
get :render_with_record_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
+ assert_equal 'Chess', @response.body
end
def test_render_with_record_collection_in_deeper_nested_controller
get :render_with_record_collection_in_deeper_nested_controller
assert_template 'fun/serious/games/_game'
+ assert_equal 'ChessSudokuSolitaire', @response.body
end
-
-end \ No newline at end of file
+end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index c25e9e1df6..56ba36cee5 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -157,21 +157,13 @@ module Admin
def redirect_to_fellow_controller
redirect_to :controller => 'user'
end
-
+
def redirect_to_top_level_named_route
redirect_to top_level_url(:id => "foo")
end
end
end
-# ---------------------------------------------------------------------------
-
-
-# tell the controller where to find its templates but start from parent
-# directory of test_request_response to simulate the behaviour of a
-# production environment
-ActionPackAssertionsController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
# a test case to exercise the new capabilities TestRequest & TestResponse
class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# let's get this party started
@@ -232,7 +224,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
process :redirect_to_named_route
assert_redirected_to 'http://test.host/route_one'
assert_redirected_to route_one_url
- assert_redirected_to :route_one_url
end
end
@@ -253,9 +244,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_raise(Test::Unit::AssertionFailedError) do
assert_redirected_to route_two_url
end
- assert_raise(Test::Unit::AssertionFailedError) do
- assert_redirected_to :route_two_url
- end
end
end
@@ -340,11 +328,11 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# check if we were rendered by a file-based template?
def test_rendered_action
process :nothing
- assert !@response.rendered_with_file?
+ assert_nil @response.rendered_template
process :hello_world
- assert @response.rendered_with_file?
- assert 'hello_world', @response.rendered_file
+ assert @response.rendered_template
+ assert 'hello_world', @response.rendered_template.to_s
end
# check the redirection location
@@ -419,22 +407,6 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_equal "Mr. David", @response.body
end
- def test_follow_redirect
- process :redirect_to_action
- assert_redirected_to :action => "flash_me"
-
- follow_redirect
- assert_equal 1, @request.parameters["id"].to_i
-
- assert "Inconceivable!", @response.body
- end
-
- def test_follow_redirect_outside_current_action
- process :redirect_to_controller
- assert_redirected_to :controller => "elsewhere", :action => "flash_me"
-
- assert_raises(RuntimeError, "Can't follow redirects outside of current controller (elsewhere)") { follow_redirect }
- end
def test_assert_redirection_fails_with_incorrect_controller
process :redirect_to_controller
@@ -448,14 +420,16 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
assert_redirected_to :controller => 'action_pack_assertions', :action => "flash_me", :id => 1, :params => { :panda => 'fun' }
end
- def test_redirected_to_url_leadling_slash
+ def test_redirected_to_url_leading_slash
process :redirect_to_path
assert_redirected_to '/some/path'
end
+
def test_redirected_to_url_no_leadling_slash
process :redirect_to_path
assert_redirected_to 'some/path'
end
+
def test_redirected_to_url_full_url
process :redirect_to_path
assert_redirected_to 'http://test.host/some/path'
@@ -475,7 +449,7 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
def test_redirected_to_with_nested_controller
@controller = Admin::InnerModuleController.new
get :redirect_to_absolute_controller
- assert_redirected_to :controller => 'content'
+ assert_redirected_to :controller => '/content'
get :redirect_to_fellow_controller
assert_redirected_to :controller => 'admin/user'
@@ -533,7 +507,6 @@ class ActionPackHeaderTest < Test::Unit::TestCase
assert_equal('application/pdf; charset=utf-8', @response.headers['type'])
end
-
def test_render_text_with_custom_content_type
get :render_text_with_custom_content_type
assert_equal 'application/rss+xml; charset=utf-8', @response.headers['type']
diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb
index a31734203d..b26cae24fb 100644
--- a/actionpack/test/controller/addresses_render_test.rb
+++ b/actionpack/test/controller/addresses_render_test.rb
@@ -1,7 +1,6 @@
require 'abstract_unit'
class Address
-
def Address.count(conditions = nil, join = nil)
nil
end
@@ -20,8 +19,6 @@ class AddressesTestController < ActionController::Base
def self.controller_path; "addresses"; end
end
-AddressesTestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class AddressesTest < Test::Unit::TestCase
def setup
@controller = AddressesTestController.new
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index 14cf0a86a1..8f53ecd178 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -6,7 +6,6 @@ CACHE_DIR = 'test_cache'
FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
ActionController::Base.page_cache_directory = FILE_STORE_PATH
ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
-ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/' ]
class PageCachingTestController < ActionController::Base
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
@@ -131,8 +130,7 @@ class PageCachingTest < Test::Unit::TestCase
end
def test_page_caching_conditional_options
- @request.env['HTTP_ACCEPT'] = 'application/json'
- get :ok
+ get :ok, :format=>'json'
assert_page_not_cached :ok
end
@@ -152,7 +150,7 @@ end
class ActionCachingTestController < ActionController::Base
- caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }
+ caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour
caches_action :show, :cache_path => 'http://test.host/custom/show'
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
caches_action :with_layout
@@ -188,6 +186,7 @@ class ActionCachingTestController < ActionController::Base
expire_action :controller => 'action_caching_test', :action => 'index'
render :nothing => true
end
+
def expire_xml
expire_action :controller => 'action_caching_test', :action => 'index', :format => 'xml'
render :nothing => true
@@ -218,6 +217,7 @@ class ActionCachingMockController
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
+ def cache_format; nil end
self
EVAL
end
@@ -284,9 +284,19 @@ class ActionCacheTest < Test::Unit::TestCase
end
def test_action_cache_conditional_options
+ old_use_accept_header = ActionController::Base.use_accept_header
+ ActionController::Base.use_accept_header = true
@request.env['HTTP_ACCEPT'] = 'application/json'
get :index
assert !fragment_exist?('hostname.com/action_caching_test')
+ ActionController::Base.use_accept_header = old_use_accept_header
+ end
+
+ def test_action_cache_with_store_options
+ MockTime.expects(:now).returns(12345).once
+ @controller.expects(:read_fragment).with('hostname.com/action_caching_test', :expires_in => 1.hour).once
+ @controller.expects(:write_fragment).with('hostname.com/action_caching_test', '12345.0', :expires_in => 1.hour).once
+ get :index
end
def test_action_cache_with_custom_cache_path
@@ -406,12 +416,6 @@ class ActionCacheTest < Test::Unit::TestCase
assert_equal 'application/xml', @response.content_type
reset!
- @request.env['HTTP_ACCEPT'] = "application/xml"
- get :index
- assert_equal cached_time, @response.body
- assert_equal 'application/xml', @response.content_type
- reset!
-
get :expire_xml
reset!
@@ -551,7 +555,7 @@ class FragmentCachingTest < Test::Unit::TestCase
fragment_computed = false
buffer = 'generated till now -> '
- @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer }
+ @controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
assert fragment_computed
assert_equal 'generated till now -> ', buffer
@@ -562,50 +566,11 @@ class FragmentCachingTest < Test::Unit::TestCase
fragment_computed = false
buffer = 'generated till now -> '
- @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer}
+ @controller.fragment_for(buffer, 'expensive') { fragment_computed = true }
assert !fragment_computed
assert_equal 'generated till now -> fragment content', buffer
end
-
- def test_cache_erb_fragment
- @store.write('views/expensive', 'fragment content')
- @controller.response.template.output_buffer = 'generated till now -> '
-
- assert_equal( 'generated till now -> fragment content',
- ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
- end
-
- def test_cache_rxml_fragment
- @store.write('views/expensive', 'fragment content')
- xml = 'generated till now -> '
- class << xml; def target!; to_s; end; end
-
- assert_equal( 'generated till now -> fragment content',
- ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
- end
-
- def test_cache_rjs_fragment
- @store.write('views/expensive', 'fragment content')
- page = 'generated till now -> '
-
- assert_equal( 'generated till now -> fragment content',
- ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
- end
-
- def test_cache_rjs_fragment_debug_mode_does_not_interfere
- @store.write('views/expensive', 'fragment content')
- page = 'generated till now -> '
-
- begin
- debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true
- assert_equal( 'generated till now -> fragment content',
- ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
- assert ActionView::Base.debug_rjs
- ensure
- ActionView::Base.debug_rjs = debug_mode
- end
- end
end
@@ -631,8 +596,6 @@ class FunctionalCachingController < ActionController::Base
end
end
-FunctionalCachingController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class FunctionalFragmentCachingTest < Test::Unit::TestCase
def setup
ActionController::Base.perform_caching = true
@@ -642,6 +605,7 @@ class FunctionalFragmentCachingTest < Test::Unit::TestCase
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
+
def test_fragment_caching
get :fragment_cached
assert_response :success
@@ -661,6 +625,14 @@ CACHED
assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
end
+ def test_render_inline_before_fragment_caching
+ get :inline_fragment_cached
+ assert_response :success
+ assert_match /Some inline content/, @response.body
+ assert_match /Some cached content/, @response.body
+ assert_match "Some cached content", @store.read('views/test.host/functional_caching/inline_fragment_cached')
+ end
+
def test_fragment_caching_in_rjs_partials
xhr :get, :js_fragment_cached_with_partial
assert_response :success
diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb
index 2604844b84..5ded6a5d26 100644
--- a/actionpack/test/controller/capture_test.rb
+++ b/actionpack/test/controller/capture_test.rb
@@ -23,8 +23,6 @@ class CaptureController < ActionController::Base
def rescue_action(e) raise end
end
-CaptureController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class CaptureTest < Test::Unit::TestCase
def setup
@controller = CaptureController.new
diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb
index 1b1ded4615..bf3b8b788e 100755
--- a/actionpack/test/controller/cgi_test.rb
+++ b/actionpack/test/controller/cgi_test.rb
@@ -3,18 +3,58 @@ require 'action_controller/cgi_process'
class BaseCgiTest < Test::Unit::TestCase
def setup
- @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
+ @request_hash = {
+ "HTTP_MAX_FORWARDS" => "10",
+ "SERVER_NAME" => "glu.ttono.us:8007",
+ "FCGI_ROLE" => "RESPONDER",
+ "AUTH_TYPE" => "Basic",
+ "HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
+ "HTTP_ACCEPT_CHARSET" => "UTF-8",
+ "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
+ "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
+ "HTTP_PRAGMA" => "no-cache",
+ "HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
+ "PATH_INFO" => "/homepage/",
+ "HTTP_ACCEPT_LANGUAGE" => "en",
+ "HTTP_NEGOTIATE" => "trans",
+ "HTTP_HOST" => "glu.ttono.us:8007",
+ "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
+ "HTTP_FROM" => "googlebot",
+ "SERVER_PROTOCOL" => "HTTP/1.1",
+ "REDIRECT_URI" => "/dispatch.fcgi",
+ "SCRIPT_NAME" => "/dispatch.fcgi",
+ "SERVER_ADDR" => "207.7.108.53",
+ "REMOTE_ADDR" => "207.7.108.53",
+ "REMOTE_HOST" => "google.com",
+ "REMOTE_IDENT" => "kevin",
+ "REMOTE_USER" => "kevin",
+ "SERVER_SOFTWARE" => "lighttpd/1.4.5",
+ "HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes",
+ "HTTP_X_FORWARDED_SERVER" => "glu.ttono.us",
+ "REQUEST_URI" => "/admin",
+ "DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public",
+ "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
+ "SERVER_PORT" => "8007",
+ "QUERY_STRING" => "",
+ "REMOTE_PORT" => "63137",
+ "GATEWAY_INTERFACE" => "CGI/1.1",
+ "HTTP_X_FORWARDED_FOR" => "65.88.180.234",
+ "HTTP_ACCEPT" => "*/*",
+ "SCRIPT_FILENAME" => "/home/kevinc/sites/typo/public/dispatch.fcgi",
+ "REDIRECT_STATUS" => "200",
+ "REQUEST_METHOD" => "GET"
+ }
# some Nokia phone browsers omit the space after the semicolon separator.
# some developers have grown accustomed to using comma in cookie values.
@alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
- @fake_cgi = Struct.new(:env_table).new(@request_hash)
- @request = ActionController::CgiRequest.new(@fake_cgi)
+ @cgi = CGI.new
+ @cgi.stubs(:env_table).returns(@request_hash)
+ @request = ActionController::CgiRequest.new(@cgi)
end
def default_test; end
end
-
class CgiRequestTest < BaseCgiTest
def test_proxy_request
assert_equal 'glu.ttono.us', @request.host_with_port
@@ -71,6 +111,37 @@ class CgiRequestTest < BaseCgiTest
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
+ def test_cgi_environment_variables
+ assert_equal "Basic", @request.auth_type
+ assert_equal 0, @request.content_length
+ assert_equal nil, @request.content_type
+ assert_equal "CGI/1.1", @request.gateway_interface
+ assert_equal "*/*", @request.accept
+ assert_equal "UTF-8", @request.accept_charset
+ assert_equal "gzip, deflate", @request.accept_encoding
+ assert_equal "en", @request.accept_language
+ assert_equal "no-cache, max-age=0", @request.cache_control
+ assert_equal "googlebot", @request.from
+ assert_equal "glu.ttono.us", @request.host
+ assert_equal "trans", @request.negotiate
+ assert_equal "no-cache", @request.pragma
+ assert_equal "http://www.google.com/search?q=glu.ttono.us", @request.referer
+ assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", @request.user_agent
+ assert_equal "/homepage/", @request.path_info
+ assert_equal "/home/kevinc/sites/typo/public/homepage/", @request.path_translated
+ assert_equal "", @request.query_string
+ assert_equal "207.7.108.53", @request.remote_addr
+ assert_equal "google.com", @request.remote_host
+ assert_equal "kevin", @request.remote_ident
+ assert_equal "kevin", @request.remote_user
+ assert_equal :get, @request.request_method
+ assert_equal "/dispatch.fcgi", @request.script_name
+ assert_equal "glu.ttono.us:8007", @request.server_name
+ assert_equal 8007, @request.server_port
+ assert_equal "HTTP/1.1", @request.server_protocol
+ assert_equal "lighttpd", @request.server_software
+ end
+
def test_cookie_syntax_resilience
cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
@@ -82,7 +153,6 @@ class CgiRequestTest < BaseCgiTest
end
end
-
class CgiRequestParamsParsingTest < BaseCgiTest
def test_doesnt_break_when_content_type_has_charset
data = 'flamenco=love'
@@ -98,7 +168,6 @@ class CgiRequestParamsParsingTest < BaseCgiTest
end
end
-
class CgiRequestNeedsRewoundTest < BaseCgiTest
def test_body_should_be_rewound
data = 'foo'
@@ -119,8 +188,8 @@ uses_mocha 'CGI Response' do
class CgiResponseTest < BaseCgiTest
def setup
super
- @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
- @response = ActionController::CgiResponse.new(@fake_cgi)
+ @cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
+ @response = ActionController::CgiResponse.new(@cgi)
@output = StringIO.new('')
end
@@ -132,7 +201,7 @@ uses_mocha 'CGI Response' do
end
def test_head_request
- @fake_cgi.env_table['REQUEST_METHOD'] = 'HEAD'
+ @cgi.env_table['REQUEST_METHOD'] = 'HEAD'
@response.body = "Hello, World!"
@response.out(@output)
diff --git a/actionpack/test/controller/components_test.rb b/actionpack/test/controller/components_test.rb
index 82c55483dd..71e8a18071 100644
--- a/actionpack/test/controller/components_test.rb
+++ b/actionpack/test/controller/components_test.rb
@@ -119,7 +119,7 @@ class ComponentsTest < Test::Unit::TestCase
def test_component_redirect_redirects
get :calling_redirected
- assert_redirected_to :action => "being_called"
+ assert_redirected_to :controller=>"callee", :action => "being_called"
end
def test_component_multiple_redirect_redirects
diff --git a/actionpack/test/controller/content_type_test.rb b/actionpack/test/controller/content_type_test.rb
index d262ce8103..d457d13aef 100644
--- a/actionpack/test/controller/content_type_test.rb
+++ b/actionpack/test/controller/content_type_test.rb
@@ -13,12 +13,12 @@ class ContentTypeController < ActionController::Base
def render_content_type_from_render
render :text => "hello world!", :content_type => Mime::RSS
end
-
+
def render_charset_from_body
response.charset = "utf-16"
render :text => "hello world!"
end
-
+
def render_default_for_rhtml
end
@@ -45,8 +45,6 @@ class ContentTypeController < ActionController::Base
def rescue_action(e) raise end
end
-ContentTypeController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class ContentTypeTest < Test::Unit::TestCase
def setup
@controller = ContentTypeController.new
@@ -68,7 +66,7 @@ class ContentTypeTest < Test::Unit::TestCase
def test_render_changed_charset_default
ContentTypeController.default_charset = "utf-16"
get :render_defaults
- assert_equal "utf-16", @response.charset
+ assert_equal "utf-16", @response.charset
assert_equal Mime::HTML, @response.content_type
ContentTypeController.default_charset = "utf-8"
end
@@ -76,13 +74,13 @@ class ContentTypeTest < Test::Unit::TestCase
def test_content_type_from_body
get :render_content_type_from_body
assert_equal "application/rss+xml", @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
def test_content_type_from_render
get :render_content_type_from_render
assert_equal "application/rss+xml", @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
def test_charset_from_body
@@ -94,27 +92,41 @@ class ContentTypeTest < Test::Unit::TestCase
def test_default_for_rhtml
get :render_default_for_rhtml
assert_equal Mime::HTML, @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
def test_default_for_rxml
get :render_default_for_rxml
assert_equal Mime::XML, @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
def test_default_for_rjs
xhr :post, :render_default_for_rjs
assert_equal Mime::JS, @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
def test_change_for_rxml
get :render_change_for_rxml
assert_equal Mime::HTML, @response.content_type
- assert_equal "utf-8", @response.charset
+ assert_equal "utf-8", @response.charset
end
-
+end
+
+class AcceptBasedContentTypeTest < ActionController::TestCase
+
+ tests ContentTypeController
+
+ def setup
+ ActionController::Base.use_accept_header = true
+ end
+
+ def teardown
+ ActionController::Base.use_accept_header = false
+ end
+
+
def test_render_default_content_types_for_respond_to
@request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
get :render_default_content_types_for_respond_to
@@ -130,7 +142,7 @@ class ContentTypeTest < Test::Unit::TestCase
get :render_default_content_types_for_respond_to
assert_equal Mime::XML, @response.content_type
end
-
+
def test_render_default_content_types_for_respond_to_with_overwrite
@request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
get :render_default_content_types_for_respond_to
diff --git a/actionpack/test/controller/custom_handler_test.rb b/actionpack/test/controller/custom_handler_test.rb
deleted file mode 100644
index ac484ae17e..0000000000
--- a/actionpack/test/controller/custom_handler_test.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'abstract_unit'
-
-class CustomHandler < ActionView::TemplateHandler
- def initialize( view )
- @view = view
- end
-
- def render( template )
- [ template.source,
- template.locals,
- @view ]
- end
-end
-
-class CustomHandlerTest < Test::Unit::TestCase
- def setup
- ActionView::Template.register_template_handler "foo", CustomHandler
- ActionView::Template.register_template_handler :foo2, CustomHandler
- @view = ActionView::Base.new
- end
-
- def test_custom_render
- template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo")
-
- result = @view.render_template(template)
- assert_equal(
- [ "hello <%= one %>", { :one => "two" }, @view ],
- result )
- end
-
- def test_custom_render2
- template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo2")
- result = @view.render_template(template)
- assert_equal(
- [ "hello <%= one %>", { :one => "two" }, @view ],
- result )
- end
-
- def test_unhandled_extension
- # uses the ERb handler by default if the extension isn't recognized
- template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "bar")
- result = @view.render_template(template)
- assert_equal "hello two", result
- end
-end
diff --git a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
index 8c1a8954a5..86555a77df 100644
--- a/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
+++ b/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
@@ -2,7 +2,6 @@ require 'abstract_unit'
class DeprecatedBaseMethodsTest < Test::Unit::TestCase
class Target < ActionController::Base
-
def home_url(greeting)
"http://example.com/#{greeting}"
end
@@ -14,8 +13,6 @@ class DeprecatedBaseMethodsTest < Test::Unit::TestCase
def rescue_action(e) raise e end
end
- Target.view_paths = [ File.dirname(__FILE__) + "/../../fixtures" ]
-
def setup
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
diff --git a/actionpack/test/controller/html-scanner/document_test.rb b/actionpack/test/controller/html-scanner/document_test.rb
index 0519533dbd..1c3facb9e3 100644
--- a/actionpack/test/controller/html-scanner/document_test.rb
+++ b/actionpack/test/controller/html-scanner/document_test.rb
@@ -120,4 +120,29 @@ HTML
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => "")
assert doc.find(:tag => "div", :attributes => { :id => "map" }, :content => nil)
end
+
+ def test_parse_invalid_document
+ assert_nothing_raised do
+ doc = HTML::Document.new("<html>
+ <table>
+ <tr>
+ <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
+ </tr>
+ </table>
+ </html>")
+ end
+ end
+
+ def test_invalid_document_raises_exception_when_strict
+ assert_raises RuntimeError do
+ doc = HTML::Document.new("<html>
+ <table>
+ <tr>
+ <td style=\"color: #FFFFFF; height: 17px; onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" style=\"cursor:pointer; height: 17px;\"; nowrap onclick=\"window.location.href='http://www.rmeinc.com/about_rme.aspx'\" onmouseout=\"this.bgColor='#0066cc'; this.style.color='#FFFFFF'\" onmouseover=\"this.bgColor='#ffffff'; this.style.color='#0033cc'\">About Us</td>
+ </tr>
+ </table>
+ </html>", true)
+ end
+ end
+
end
diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb
index 3dc311b78a..92b6aa4f2f 100644
--- a/actionpack/test/controller/layout_test.rb
+++ b/actionpack/test/controller/layout_test.rb
@@ -34,8 +34,8 @@ end
class MabView < ActionView::TemplateHandler
def initialize(view)
end
-
- def render(template)
+
+ def render(template, local_assigns)
template.source
end
end
@@ -63,6 +63,7 @@ class LayoutAutoDiscoveryTest < Test::Unit::TestCase
end
def test_third_party_template_library_auto_discovers_layout
+ ThirdPartyTemplateLibraryController.view_paths.reload!
@controller = ThirdPartyTemplateLibraryController.new
get :hello
assert_equal 'layouts/third_party_template_library', @controller.active_layout
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index c617cb2e84..1701431858 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -107,7 +107,7 @@ class RespondToController < ActionController::Base
type.any(:js, :xml) { render :text => "Either JS or XML" }
end
end
-
+
def handle_any_any
respond_to do |type|
type.html { render :text => 'HTML' }
@@ -120,12 +120,12 @@ class RespondToController < ActionController::Base
type.html
type.js
end
- end
-
- def iphone_with_html_response_type
+ end
+
+ def iphone_with_html_response_type
Mime::Type.register_alias("text/html", :iphone)
request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone"
-
+
respond_to do |type|
type.html { @type = "Firefox" }
type.iphone { @type = "iPhone" }
@@ -138,7 +138,7 @@ class RespondToController < ActionController::Base
def iphone_with_html_response_type_without_layout
Mime::Type.register_alias("text/html", :iphone)
request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone"
-
+
respond_to do |type|
type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" }
type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" }
@@ -162,10 +162,9 @@ class RespondToController < ActionController::Base
end
end
-RespondToController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class MimeControllerTest < Test::Unit::TestCase
def setup
+ ActionController::Base.use_accept_header = true
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
@@ -173,6 +172,10 @@ class MimeControllerTest < Test::Unit::TestCase
@request.host = "www.example.com"
end
+ def teardown
+ ActionController::Base.use_accept_header = false
+ end
+
def test_html
@request.env["HTTP_ACCEPT"] = "text/html"
get :js_or_html
@@ -247,7 +250,7 @@ class MimeControllerTest < Test::Unit::TestCase
get :just_xml
assert_equal 'XML', @response.body
end
-
+
def test_using_defaults
@request.env["HTTP_ACCEPT"] = "*/*"
get :using_defaults
@@ -347,12 +350,12 @@ class MimeControllerTest < Test::Unit::TestCase
get :handle_any_any
assert_equal 'HTML', @response.body
end
-
+
def test_handle_any_any_parameter_format
get :handle_any_any, {:format=>'html'}
assert_equal 'HTML', @response.body
end
-
+
def test_handle_any_any_explicit_html
@request.env["HTTP_ACCEPT"] = "text/html"
get :handle_any_any
@@ -364,7 +367,7 @@ class MimeControllerTest < Test::Unit::TestCase
get :handle_any_any
assert_equal 'Whatever you ask for, I got it', @response.body
end
-
+
def test_handle_any_any_xml
@request.env["HTTP_ACCEPT"] = "text/xml"
get :handle_any_any
@@ -445,31 +448,31 @@ class MimeControllerTest < Test::Unit::TestCase
get :using_defaults, :format => "xml"
assert_equal "using_defaults - xml", @response.body
- end
-
+ end
+
def test_format_with_custom_response_type
get :iphone_with_html_response_type
- assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
-
+ assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
+
get :iphone_with_html_response_type, :format => "iphone"
assert_equal "text/html", @response.content_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
- end
-
+ end
+
def test_format_with_custom_response_type_and_request_headers
@request.env["HTTP_ACCEPT"] = "text/iphone"
get :iphone_with_html_response_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
- end
+ end
def test_format_with_custom_response_type_and_request_headers_with_only_one_layout_present
get :iphone_with_html_response_type_without_layout
- assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body
+ assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body
@request.env["HTTP_ACCEPT"] = "text/iphone"
assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout }
- end
+ end
end
class AbstractPostController < ActionController::Base
@@ -497,7 +500,7 @@ class PostController < AbstractPostController
end
end
-class SuperPostController < PostController
+class SuperPostController < PostController
def index
respond_to do |type|
type.html
@@ -514,25 +517,24 @@ class MimeControllerLayoutsTest < Test::Unit::TestCase
@controller = PostController.new
@request.host = "www.example.com"
end
-
+
def test_missing_layout_renders_properly
get :index
- assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
+ assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body
@request.env["HTTP_ACCEPT"] = "text/iphone"
get :index
assert_equal 'Hello iPhone', @response.body
end
-
+
def test_format_with_inherited_layouts
@controller = SuperPostController.new
-
+
get :index
assert_equal 'Super Firefox', @response.body
-
+
@request.env["HTTP_ACCEPT"] = "text/iphone"
get :index
assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body
end
end
-
diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb
index b77b3ceffa..d2a3a2b0b0 100644
--- a/actionpack/test/controller/new_render_test.rb
+++ b/actionpack/test/controller/new_render_test.rb
@@ -45,11 +45,11 @@ class NewRenderTestController < ActionController::Base
def render_action_hello_world_as_symbol
render :action => :hello_world
end
-
+
def render_text_hello_world
render :text => "hello world"
end
-
+
def render_text_hello_world_with_layout
@variable_for_layout = ", I'm here!"
render :text => "hello world", :layout => true
@@ -68,7 +68,7 @@ class NewRenderTestController < ActionController::Base
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')
render :file => path
end
-
+
def render_file_from_template
@secret = 'in the sauce'
@path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb'))
@@ -76,17 +76,17 @@ class NewRenderTestController < ActionController::Base
def render_file_with_locals
path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')
- render :file => path, :locals => {:secret => 'in the sauce'}
+ render :file => path, :locals => {:secret => 'in the sauce'}
end
def render_file_not_using_full_path
@secret = 'in the sauce'
- render :file => 'test/render_file_with_ivar', :use_full_path => true
+ render :file => 'test/render_file_with_ivar'
end
-
+
def render_file_not_using_full_path_with_dot_in_path
@secret = 'in the sauce'
- render :file => 'test/dot.directory/render_file_with_ivar', :use_full_path => true
+ render :file => 'test/dot.directory/render_file_with_ivar'
end
def render_xml_hello
@@ -105,7 +105,7 @@ class NewRenderTestController < ActionController::Base
def layout_test_with_different_layout
render :action => "hello_world", :layout => "standard"
end
-
+
def rendering_without_layout
render :action => "hello_world", :layout => false
end
@@ -113,11 +113,11 @@ class NewRenderTestController < ActionController::Base
def layout_overriding_layout
render :action => "hello_world", :layout => "standard"
end
-
+
def rendering_nothing_on_layout
render :nothing => true
end
-
+
def builder_layout_test
render :action => "hello"
end
@@ -135,9 +135,9 @@ class NewRenderTestController < ActionController::Base
def partial_only_with_layout
render :partial => "partial_only", :layout => true
end
-
+
def partial_with_locals
- render :partial => "customer", :locals => { :customer => Customer.new("david") }
+ render :partial => "customer", :locals => { :customer => Customer.new("david") }
end
def partial_with_form_builder
@@ -151,11 +151,15 @@ class NewRenderTestController < ActionController::Base
def partial_collection
render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
-
+
+ def partial_collection_with_as
+ render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer
+ end
+
def partial_collection_with_spacer
render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
-
+
def partial_collection_with_counter
render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ]
end
@@ -186,33 +190,33 @@ class NewRenderTestController < ActionController::Base
def empty_partial_collection
render :partial => "customer", :collection => []
end
-
+
def partial_with_hash_object
render :partial => "hash_object", :object => {:first_name => "Sam"}
end
-
+
def partial_hash_collection
render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ]
end
-
+
def partial_hash_collection_with_locals
render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" }
end
-
+
def partial_with_implicit_local_assignment
@customer = Customer.new("Marcel")
render :partial => "customer"
end
-
+
def missing_partial
render :partial => 'thisFileIsntHere'
end
-
+
def hello_in_a_string
@customers = [ Customer.new("david"), Customer.new("mary") ]
render :text => "How's there? " << render_to_string(:template => "test/list")
end
-
+
def render_to_string_with_assigns
@before = "i'm before the render"
render_to_string :text => "foo"
@@ -222,18 +226,18 @@ class NewRenderTestController < ActionController::Base
def render_to_string_with_partial
@partial_only = render_to_string :partial => "partial_only"
- @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
- render :action => "test/hello_world"
- end
-
+ @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") }
+ render :action => "test/hello_world"
+ end
+
def render_to_string_with_exception
- render_to_string :file => "exception that will not be caught - this will certainly not work", :use_full_path => true
+ render_to_string :file => "exception that will not be caught - this will certainly not work"
end
-
+
def render_to_string_with_caught_exception
@before = "i'm before the render"
begin
- render_to_string :file => "exception that will be caught- hope my future instance vars still work!", :use_full_path => true
+ render_to_string :file => "exception that will be caught- hope my future instance vars still work!"
rescue
end
@after = "i'm after the render"
@@ -268,6 +272,10 @@ class NewRenderTestController < ActionController::Base
render :template => "test/hello_world"
end
+ def render_with_explicit_template_with_locals
+ render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' }
+ end
+
def double_render
render :text => "hello"
render :text => "world"
@@ -282,7 +290,7 @@ class NewRenderTestController < ActionController::Base
render :text => "hello"
redirect_to :action => "double_render"
end
-
+
def render_to_string_and_render
@stuff = render_to_string :text => "here is some cached stuff"
render :text => "Hi web users! #{@stuff}"
@@ -329,7 +337,7 @@ class NewRenderTestController < ActionController::Base
def render_with_location
render :xml => "<hello/>", :location => "http://example.com", :status => 201
end
-
+
def render_with_object_location
customer = Customer.new("Some guy", 1)
render :xml => "<customer/>", :location => customer_url(customer), :status => :created
@@ -341,12 +349,12 @@ class NewRenderTestController < ActionController::Base
"<i-am-xml/>"
end
end.new
-
+
render :xml => to_xmlable
end
helper NewRenderTestHelper
- helper do
+ helper do
def rjs_helper_method(value)
page.visual_effect :highlight, value
end
@@ -383,7 +391,7 @@ class NewRenderTestController < ActionController::Base
page.visual_effect :highlight, 'balance'
end
end
-
+
def update_page_with_instance_variables
@money = '$37,000,000.00'
@div_id = 'balance'
@@ -426,12 +434,12 @@ class NewRenderTestController < ActionController::Base
def render_using_layout_around_block_in_main_layout_and_within_content_for_layout
render :action => "using_layout_around_block"
end
-
+
def rescue_action(e) raise end
-
+
private
def determine_layout
- case action_name
+ case action_name
when "hello_world", "layout_test", "rendering_without_layout",
"rendering_nothing_on_layout", "render_text_hello_world",
"render_text_hello_world_with_layout",
@@ -443,7 +451,7 @@ class NewRenderTestController < ActionController::Base
"render_js_with_explicit_template",
"render_js_with_explicit_action_template",
"delete_with_js", "update_page", "update_page_with_instance_variables"
-
+
"layouts/standard"
when "builder_layout_test"
"layouts/builder"
@@ -457,9 +465,6 @@ class NewRenderTestController < ActionController::Base
end
end
-NewRenderTestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-Fun::GamesController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class NewRenderTest < Test::Unit::TestCase
def setup
@controller = NewRenderTestController.new
@@ -481,6 +486,11 @@ class NewRenderTest < Test::Unit::TestCase
assert_equal "<html>Hello world!</html>", @response.body
end
+ def test_renders_default_template_for_missing_action
+ get :'hyphen-ated'
+ assert_template 'test/hyphen-ated'
+ end
+
def test_do_with_render
get :render_hello_world
assert_template "test/hello_world"
@@ -527,7 +537,7 @@ class NewRenderTest < Test::Unit::TestCase
end
def test_render_file_not_using_full_path
- get :render_file_not_using_full_path
+ get :render_file_not_using_full_path
assert_equal "The secret is in the sauce\n", @response.body
end
@@ -540,7 +550,7 @@ class NewRenderTest < Test::Unit::TestCase
get :render_file_with_locals
assert_equal "The secret is in the sauce\n", @response.body
end
-
+
def test_render_file_from_template
get :render_file_from_template
assert_equal "The secret is in the sauce\n", @response.body
@@ -597,8 +607,7 @@ EOS
end
def test_render_with_default_from_accept_header
- @request.env["HTTP_ACCEPT"] = "text/javascript"
- get :greeting
+ xhr :get, :greeting
assert_equal "$(\"body\").visualEffect(\"highlight\");", @response.body
end
@@ -661,7 +670,7 @@ EOS
assert_not_deprecated { get :hello_in_a_string }
assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body
end
-
+
def test_render_to_string_doesnt_break_assigns
get :render_to_string_with_assigns
assert_equal "i'm before the render", assigns(:before)
@@ -672,12 +681,12 @@ EOS
get :render_to_string_with_partial
assert_equal "only partial", assigns(:partial_only)
assert_equal "Hello: david", assigns(:partial_with_locals)
- end
+ end
def test_bad_render_to_string_still_throws_exception
assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception }
end
-
+
def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns
assert_nothing_raised { get :render_to_string_with_caught_exception }
assert_equal "i'm before the render", assigns(:before)
@@ -715,7 +724,7 @@ EOS
def test_render_and_redirect
assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect }
end
-
+
# specify the one exception to double render rule - render_to_string followed by render
def test_render_to_string_and_render
get :render_to_string_and_render
@@ -736,7 +745,7 @@ EOS
get :partials_list
assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body
end
-
+
def test_partial_with_locals
get :partial_with_locals
assert_equal "Hello: david", @response.body
@@ -758,17 +767,22 @@ EOS
get :partial_collection
assert_equal "Hello: davidHello: mary", @response.body
end
-
+
+ def test_partial_collection_with_as
+ get :partial_collection_with_as
+ assert_equal "david david davidmary mary mary", @response.body
+ end
+
def test_partial_collection_with_counter
get :partial_collection_with_counter
assert_equal "david0mary1", @response.body
end
-
+
def test_partial_collection_with_locals
get :partial_collection_with_locals
assert_equal "Bonjour: davidBonjour: mary", @response.body
end
-
+
def test_partial_collection_with_spacer
get :partial_collection_with_spacer
assert_equal "Hello: davidonly partialHello: mary", @response.body
@@ -793,12 +807,12 @@ EOS
get :partial_with_hash_object
assert_equal "Sam\nmaS\n", @response.body
end
-
+
def test_hash_partial_collection
get :partial_hash_collection
assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body
end
-
+
def test_partial_hash_collection_with_locals
get :partial_hash_collection_with_locals
assert_equal "Hola: PratikHola: Amy", @response.body
@@ -808,25 +822,30 @@ EOS
get :partial_with_implicit_local_assignment
assert_equal "Hello: Marcel", @response.body
end
-
+
def test_render_missing_partial_template
assert_raises(ActionView::MissingTemplate) do
get :missing_partial
end
end
-
+
def test_render_text_with_assigns
get :render_text_with_assigns
assert_equal "world", assigns["hello"]
end
-
+
+ def test_template_with_locals
+ get :render_with_explicit_template_with_locals
+ assert_equal "The secret is area51\n", @response.body
+ end
+
def test_update_page
get :update_page
assert_template nil
assert_equal 'text/javascript; charset=utf-8', @response.headers['type']
assert_equal 2, @response.body.split($/).length
end
-
+
def test_update_page_with_instance_variables
get :update_page_with_instance_variables
assert_template nil
@@ -834,7 +853,7 @@ EOS
assert_match /balance/, @response.body
assert_match /\$37/, @response.body
end
-
+
def test_yield_content_for
assert_not_deprecated { get :yield_content_for }
assert_equal "<title>Putting stuff in the title!</title>\n\nGreat stuff!\n", @response.body
@@ -906,12 +925,12 @@ EOS
get :render_with_location
assert_equal "http://example.com", @response.headers["Location"]
end
-
+
def test_rendering_xml_should_call_to_xml_if_possible
get :render_with_to_xml
assert_equal "<i-am-xml/>", @response.body
end
-
+
def test_rendering_with_object_location_should_set_header_with_url_for
ActionController::Routing::Routes.draw do |map|
map.resources :customers
@@ -941,5 +960,4 @@ EOS
get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout
assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body
end
-
end
diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb
index 4ec0d3cd4e..3f52526f08 100644
--- a/actionpack/test/controller/polymorphic_routes_test.rb
+++ b/actionpack/test/controller/polymorphic_routes_test.rb
@@ -118,6 +118,39 @@ uses_mocha 'polymorphic URL helpers' do
polymorphic_url([:site, :admin, @article, @response, @tag])
end
+ def test_nesting_with_array_ending_in_singleton_resource
+ expects(:article_response_url).with(@article)
+ polymorphic_url([@article, :response])
+ end
+
+ def test_nesting_with_array_containing_singleton_resource
+ @tag = Tag.new
+ @tag.save
+ expects(:article_response_tag_url).with(@article, @tag)
+ polymorphic_url([@article, :response, @tag])
+ end
+
+ def test_nesting_with_array_containing_namespace_and_singleton_resource
+ @tag = Tag.new
+ @tag.save
+ expects(:admin_article_response_tag_url).with(@article, @tag)
+ polymorphic_url([:admin, @article, :response, @tag])
+ end
+
+ def test_nesting_with_array_containing_singleton_resource_and_format
+ @tag = Tag.new
+ @tag.save
+ expects(:formatted_article_response_tag_url).with(@article, @tag, :pdf)
+ formatted_polymorphic_url([@article, :response, @tag, :pdf])
+ end
+
+ def test_nesting_with_array_containing_singleton_resource_and_format_option
+ @tag = Tag.new
+ @tag.save
+ expects(:article_response_tag_url).with(@article, @tag, :pdf)
+ polymorphic_url([@article, :response, @tag], :format => :pdf)
+ end
+
# TODO: Needs to be updated to correctly know about whether the object is in a hash or not
def xtest_with_hash
expects(:article_url).with(@article)
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
index 856f24bbdb..486fe49737 100644
--- a/actionpack/test/controller/rack_test.rb
+++ b/actionpack/test/controller/rack_test.rb
@@ -7,22 +7,33 @@ class BaseRackTest < Test::Unit::TestCase
"HTTP_MAX_FORWARDS" => "10",
"SERVER_NAME" => "glu.ttono.us:8007",
"FCGI_ROLE" => "RESPONDER",
+ "AUTH_TYPE" => "Basic",
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
+ "HTTP_ACCEPT_CHARSET" => "UTF-8",
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
+ "HTTP_CACHE_CONTROL" => "no-cache, max-age=0",
+ "HTTP_PRAGMA" => "no-cache",
"HTTP_USER_AGENT" => "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)",
- "PATH_INFO" => "",
+ "PATH_INFO" => "/homepage/",
"HTTP_ACCEPT_LANGUAGE" => "en",
+ "HTTP_NEGOTIATE" => "trans",
"HTTP_HOST" => "glu.ttono.us:8007",
+ "HTTP_REFERER" => "http://www.google.com/search?q=glu.ttono.us",
+ "HTTP_FROM" => "googlebot",
"SERVER_PROTOCOL" => "HTTP/1.1",
"REDIRECT_URI" => "/dispatch.fcgi",
"SCRIPT_NAME" => "/dispatch.fcgi",
"SERVER_ADDR" => "207.7.108.53",
"REMOTE_ADDR" => "207.7.108.53",
+ "REMOTE_HOST" => "google.com",
+ "REMOTE_IDENT" => "kevin",
+ "REMOTE_USER" => "kevin",
"SERVER_SOFTWARE" => "lighttpd/1.4.5",
"HTTP_COOKIE" => "_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes",
"HTTP_X_FORWARDED_SERVER" => "glu.ttono.us",
"REQUEST_URI" => "/admin",
"DOCUMENT_ROOT" => "/home/kevinc/sites/typo/public",
+ "PATH_TRANSLATED" => "/home/kevinc/sites/typo/public/homepage/",
"SERVER_PORT" => "8007",
"QUERY_STRING" => "",
"REMOTE_PORT" => "63137",
@@ -42,7 +53,6 @@ class BaseRackTest < Test::Unit::TestCase
def default_test; end
end
-
class RackRequestTest < BaseRackTest
def test_proxy_request
assert_equal 'glu.ttono.us', @request.host_with_port
@@ -99,6 +109,37 @@ class RackRequestTest < BaseRackTest
assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
end
+ def test_cgi_environment_variables
+ assert_equal "Basic", @request.auth_type
+ assert_equal 0, @request.content_length
+ assert_equal nil, @request.content_type
+ assert_equal "CGI/1.1", @request.gateway_interface
+ assert_equal "*/*", @request.accept
+ assert_equal "UTF-8", @request.accept_charset
+ assert_equal "gzip, deflate", @request.accept_encoding
+ assert_equal "en", @request.accept_language
+ assert_equal "no-cache, max-age=0", @request.cache_control
+ assert_equal "googlebot", @request.from
+ assert_equal "glu.ttono.us", @request.host
+ assert_equal "trans", @request.negotiate
+ assert_equal "no-cache", @request.pragma
+ assert_equal "http://www.google.com/search?q=glu.ttono.us", @request.referer
+ assert_equal "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en)", @request.user_agent
+ assert_equal "/homepage/", @request.path_info
+ assert_equal "/home/kevinc/sites/typo/public/homepage/", @request.path_translated
+ assert_equal "", @request.query_string
+ assert_equal "207.7.108.53", @request.remote_addr
+ assert_equal "google.com", @request.remote_host
+ assert_equal "kevin", @request.remote_ident
+ assert_equal "kevin", @request.remote_user
+ assert_equal :get, @request.request_method
+ assert_equal "/dispatch.fcgi", @request.script_name
+ assert_equal "glu.ttono.us:8007", @request.server_name
+ assert_equal 8007, @request.server_port
+ assert_equal "HTTP/1.1", @request.server_protocol
+ assert_equal "lighttpd", @request.server_software
+ end
+
def test_cookie_syntax_resilience
cookies = @request.cookies
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
@@ -110,7 +151,6 @@ class RackRequestTest < BaseRackTest
end
end
-
class RackRequestParamsParsingTest < BaseRackTest
def test_doesnt_break_when_content_type_has_charset
data = 'flamenco=love'
@@ -126,7 +166,6 @@ class RackRequestParamsParsingTest < BaseRackTest
end
end
-
class RackRequestNeedsRewoundTest < BaseRackTest
def test_body_should_be_rewound
data = 'foo'
@@ -143,7 +182,6 @@ class RackRequestNeedsRewoundTest < BaseRackTest
end
end
-
class RackResponseTest < BaseRackTest
def setup
super
@@ -155,7 +193,7 @@ class RackResponseTest < BaseRackTest
@response.body = "Hello, World!"
status, headers, body = @response.out(@output)
- assert_equal 200, status
+ assert_equal "200 OK", status
assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
parts = []
@@ -169,7 +207,7 @@ class RackResponseTest < BaseRackTest
end
status, headers, body = @response.out(@output)
- assert_equal 200, status
+ assert_equal "200 OK", status
assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => []}, headers)
parts = []
@@ -184,7 +222,7 @@ class RackResponseTest < BaseRackTest
@response.body = "Hello, World!"
status, headers, body = @response.out(@output)
- assert_equal 200, status
+ assert_equal "200 OK", status
assert_equal({
"Content-Type" => "text/html",
"Cache-Control" => "no-cache",
diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb
index 0e85347bad..28da5c6163 100755
--- a/actionpack/test/controller/redirect_test.rb
+++ b/actionpack/test/controller/redirect_test.rb
@@ -168,21 +168,6 @@ class RedirectTest < Test::Unit::TestCase
assert_redirected_to :action => "other_host", :only_path => false, :host => 'other.test.host'
end
- def test_redirect_error_with_pretty_diff
- get :host_redirect
- assert_response :redirect
- begin
- assert_redirected_to :action => "other_host", :only_path => true
- rescue Test::Unit::AssertionFailedError => err
- expected_msg, redirection_msg, diff_msg = err.message.scan(/<\{[^\}]+\}>/).collect { |s| s[2..-3] }
- assert_match %r("only_path"=>false), redirection_msg
- assert_match %r("host"=>"other.test.host"), redirection_msg
- assert_match %r("action"=>"other_host"), redirection_msg
- assert_match %r("only_path"=>false), diff_msg
- assert_match %r("host"=>"other.test.host"), diff_msg
- end
- end
-
def test_module_redirect
get :module_redirect
assert_response :redirect
@@ -235,9 +220,16 @@ class RedirectTest < Test::Unit::TestCase
get :redirect_to_existing_record
assert_equal "http://test.host/workshops/5", redirect_to_url
+ assert_redirected_to Workshop.new(5, false)
get :redirect_to_new_record
assert_equal "http://test.host/workshops", redirect_to_url
+ assert_redirected_to Workshop.new(5, true)
+ end
+
+ def test_redirect_with_partial_params
+ get :module_redirect
+ assert_redirected_to :action => 'hello_world'
end
def test_redirect_to_nil
@@ -283,7 +275,7 @@ module ModuleTest
def test_module_redirect_using_options
get :module_redirect
assert_response :redirect
- assert_redirected_to :controller => 'redirect', :action => "hello_world"
+ assert_redirected_to :controller => '/redirect', :action => "hello_world"
end
end
end
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index 066fa6acd4..9a94db4b00 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -23,11 +23,11 @@ class TestController < ActionController::Base
def render_hello_world_with_forward_slash
render :template => "/test/hello_world"
end
-
+
def render_template_in_top_directory
render :template => 'shared'
end
-
+
def render_template_in_top_directory_with_slash
render :template => '/shared'
end
@@ -86,7 +86,7 @@ class TestController < ActionController::Base
def render_nothing_with_appendix
render :text => "appended"
end
-
+
def render_invalid_args
render("test/hello")
end
@@ -101,12 +101,7 @@ class TestController < ActionController::Base
end
def render_line_offset
- begin
- render :inline => '<% raise %>', :locals => {:foo => 'bar'}
- rescue => exc
- end
- line = exc.backtrace.first
- render :text => line
+ render :inline => '<% raise %>', :locals => {:foo => 'bar'}
end
def heading
@@ -171,7 +166,7 @@ class TestController < ActionController::Base
def partial_dot_html
render :partial => 'partial.html.erb'
end
-
+
def partial_as_rjs
render :update do |page|
page.replace :foo, :partial => 'partial'
@@ -217,9 +212,6 @@ class TestController < ActionController::Base
end
end
-TestController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-Fun::GamesController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class RenderTest < Test::Unit::TestCase
def setup
@request = ActionController::TestRequest.new
@@ -241,23 +233,28 @@ class RenderTest < Test::Unit::TestCase
end
def test_line_offset
- get :render_line_offset
- line = @response.body
- assert(line =~ %r{:(\d+):})
- assert_equal "1", $1
+ begin
+ get :render_line_offset
+ flunk "the action should have raised an exception"
+ rescue RuntimeError => exc
+ line = exc.backtrace.first
+ assert(line =~ %r{:(\d+):})
+ assert_equal "1", $1,
+ "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}"
+ end
end
def test_render_with_forward_slash
get :render_hello_world_with_forward_slash
assert_template "test/hello_world"
end
-
+
def test_render_in_top_directory
get :render_template_in_top_directory
assert_template "shared"
assert_equal "Elastica", @response.body
end
-
+
def test_render_in_top_directory_with_slash
get :render_template_in_top_directory_with_slash
assert_template "shared"
@@ -336,11 +333,11 @@ class RenderTest < Test::Unit::TestCase
assert_response 200
assert_equal 'appended', @response.body
end
-
+
def test_attempt_to_render_with_invalid_arguments
assert_raises(ActionController::RenderError) { get :render_invalid_args }
end
-
+
def test_attempt_to_access_object_method
assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone }
end
@@ -467,17 +464,17 @@ class RenderTest < Test::Unit::TestCase
get :formatted_html_erb
assert_equal 'formatted html erb', @response.body
end
-
+
def test_should_render_formatted_xml_erb_template
get :formatted_xml_erb, :format => :xml
assert_equal '<test>passed formatted xml erb</test>', @response.body
end
-
+
def test_should_render_formatted_html_erb_template
get :formatted_xml_erb
assert_equal '<test>passed formatted html erb</test>', @response.body
end
-
+
def test_should_render_formatted_html_erb_template_with_faulty_accepts_header
@request.env["HTTP_ACCEPT"] = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*"
get :formatted_xml_erb
@@ -520,7 +517,6 @@ class RenderTest < Test::Unit::TestCase
end
protected
-
def etag_for(text)
%("#{Digest::MD5.hexdigest(text)}")
end
diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb
index 2bd489b2c7..932c0e21a1 100644
--- a/actionpack/test/controller/request_test.rb
+++ b/actionpack/test/controller/request_test.rb
@@ -386,7 +386,7 @@ class RequestTest < Test::Unit::TestCase
def test_nil_format
@request.instance_eval { @parameters = { :format => nil } }
- @request.env["HTTP_ACCEPT"] = "text/javascript"
+ @request.env["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest"
assert_equal Mime::JS, @request.format
end
@@ -909,15 +909,21 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest
end
class JsonParamsParsingTest < Test::Unit::TestCase
- def test_hash_params
- person = parse_body({:person => {:name => "David"}}.to_json)[:person]
+ def test_hash_params_for_application_json
+ person = parse_body({:person => {:name => "David"}}.to_json,'application/json')[:person]
+ assert_kind_of Hash, person
+ assert_equal 'David', person['name']
+ end
+
+ def test_hash_params_for_application_jsonrequest
+ person = parse_body({:person => {:name => "David"}}.to_json,'application/jsonrequest')[:person]
assert_kind_of Hash, person
assert_equal 'David', person['name']
end
private
- def parse_body(body)
- env = { 'CONTENT_TYPE' => 'application/json',
+ def parse_body(body,content_type)
+ env = { 'CONTENT_TYPE' => content_type,
'CONTENT_LENGTH' => body.size.to_s }
cgi = ActionController::Integration::Session::StubCGI.new(env, body)
ActionController::CgiRequest.new(cgi).request_parameters
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index 0d089d0f23..0f7924649a 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -28,18 +28,16 @@ module Backoffice
end
class ResourcesTest < Test::Unit::TestCase
-
-
# The assertions in these tests are incompatible with the hash method
# optimisation. This could indicate user level problems
def setup
ActionController::Base.optimise_named_routes = false
end
-
- def tear_down
+
+ def teardown
ActionController::Base.optimise_named_routes = true
end
-
+
def test_should_arrange_actions
resource = ActionController::Resources::Resource.new(:messages,
:collection => { :rss => :get, :reorder => :post, :csv => :post },
@@ -159,14 +157,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_actions_and_name_prefix
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
-
+
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method)
end
end
-
+
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action
@@ -177,14 +175,14 @@ class ResourcesTest < Test::Unit::TestCase
def test_with_collection_action_and_name_prefix_and_formatted
actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete }
-
+
with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions do
assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.each do |action, method|
assert_recognizes(options.merge(:action => action, :format => 'xml'), :path => "/threads/1/messages/#{action}.xml", :method => method)
end
end
-
+
assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options|
actions.keys.each do |action|
assert_named_route "/threads/1/messages/#{action}.xml", "formatted_#{action}_thread_messages_path", :action => action, :format => 'xml'
@@ -279,7 +277,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
-
+
def test_with_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1'}
@@ -293,7 +291,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
-
+
def test_with_formatted_new_action_with_name_prefix
with_restful_routing :messages, :new => { :preview => :post }, :path_prefix => '/threads/:thread_id', :name_prefix => 'thread_' do
preview_options = {:action => 'preview', :thread_id => '1', :format => 'xml'}
@@ -307,7 +305,7 @@ class ResourcesTest < Test::Unit::TestCase
end
end
end
-
+
def test_override_new_method
with_restful_routing :messages do
assert_restful_routes_for :messages do |options|
@@ -524,9 +522,9 @@ class ResourcesTest < Test::Unit::TestCase
map.resources :messages, :collection => {:search => :get}, :new => {:preview => :any}, :name_prefix => 'thread_', :path_prefix => '/threads/:thread_id'
map.resource :account, :member => {:login => :get}, :new => {:preview => :any}, :name_prefix => 'admin_', :path_prefix => '/admin'
end
-
+
action_separator = ActionController::Base.resource_action_separator
-
+
assert_simply_restful_for :messages, :name_prefix => 'thread_', :path_prefix => 'threads/1/', :options => { :thread_id => '1' }
assert_named_route "/threads/1/messages#{action_separator}search", "search_thread_messages_path", {}
assert_named_route "/threads/1/messages/new", "new_thread_message_path", {}
@@ -623,7 +621,7 @@ class ResourcesTest < Test::Unit::TestCase
assert_simply_restful_for :products, :controller => "backoffice/products"
end
end
-
+
def test_nested_resources_using_namespace
with_routing do |set|
set.draw do |map|
@@ -795,7 +793,7 @@ class ResourcesTest < Test::Unit::TestCase
yield options[:options] if block_given?
end
-
+
def assert_singleton_routes_for(singleton_name, options = {})
options[:options] ||= {}
options[:options][:controller] = options[:controller] || singleton_name.to_s.pluralize
@@ -855,7 +853,7 @@ class ResourcesTest < Test::Unit::TestCase
actual = @controller.send(route, options) rescue $!.class.name
assert_equal expected, actual, "Error on route: #{route}(#{options.inspect})"
end
-
+
def assert_resource_methods(expected, resource, action_method, method)
assert_equal expected.length, resource.send("#{action_method}_methods")[method].size, "#{resource.send("#{action_method}_methods")[method].inspect}"
expected.each do |action|
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 07c13ebbf7..c5ccb71582 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -2039,6 +2039,26 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do
Object.send(:remove_const, :Api)
end
+ def test_namespace_with_path_prefix
+ Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) })
+
+ set.draw do |map|
+
+ map.namespace 'api', :path_prefix => 'prefix' do |api|
+ api.route 'inventory', :controller => "products", :action => 'inventory'
+ end
+
+ end
+
+ request.path = "/prefix/inventory"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("api/products", request.path_parameters[:controller])
+ assert_equal("inventory", request.path_parameters[:action])
+ ensure
+ Object.send(:remove_const, :Api)
+ end
+
def test_generate_finds_best_fit
set.draw do |map|
map.connect "/people", :controller => "people", :action => "index"
diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb
index 406825fe59..c003abf094 100644
--- a/actionpack/test/controller/send_file_test.rb
+++ b/actionpack/test/controller/send_file_test.rb
@@ -1,13 +1,11 @@
require 'abstract_unit'
-
module TestFileUtils
def file_name() File.basename(__FILE__) end
def file_path() File.expand_path(__FILE__) end
def file_data() File.open(file_path, 'rb') { |f| f.read } end
end
-
class SendFileController < ActionController::Base
include TestFileUtils
layout "layouts/standard" # to make sure layouts don't interfere
@@ -21,8 +19,6 @@ class SendFileController < ActionController::Base
def rescue_action(e) raise end
end
-SendFileController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
-
class SendFileTest < Test::Unit::TestCase
include TestFileUtils
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 38898a1f75..b624005a57 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -566,24 +566,6 @@ XML
assert_raise(RuntimeError) { ActionController::TestUploadedFile.new('non_existent_file') }
end
- def test_assert_follow_redirect_to_same_controller
- with_foo_routing do |set|
- get :redirect_to_same_controller
- assert_response :redirect
- assert_redirected_to :controller => 'test_test/test', :action => 'test_uri', :id => 5
- assert_nothing_raised { follow_redirect }
- end
- end
-
- def test_assert_follow_redirect_to_different_controller
- with_foo_routing do |set|
- get :redirect_to_different_controller
- assert_response :redirect
- assert_redirected_to :controller => 'fail', :id => 5
- assert_raise(RuntimeError) { follow_redirect }
- end
- end
-
def test_redirect_url_only_cares_about_location_header
get :create
assert_response :created
diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb
index 1b4c1fae2f..85fa58a45b 100644
--- a/actionpack/test/controller/view_paths_test.rb
+++ b/actionpack/test/controller/view_paths_test.rb
@@ -1,10 +1,6 @@
require 'abstract_unit'
class ViewLoadPathsTest < Test::Unit::TestCase
- LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures')
-
- ActionController::Base.view_paths = [LOAD_PATH_ROOT]
-
class TestController < ActionController::Base
def self.controller_path() "test" end
def rescue_action(e) raise end
@@ -16,7 +12,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase
private
def add_view_path
- prepend_view_path "#{LOAD_PATH_ROOT}/override"
+ prepend_view_path "#{FIXTURE_LOAD_PATH}/override"
end
end
@@ -47,35 +43,35 @@ class ViewLoadPathsTest < Test::Unit::TestCase
end
def test_template_load_path_was_set_correctly
- assert_equal [ LOAD_PATH_ROOT ], @controller.view_paths.map(&:to_s)
+ assert_equal [FIXTURE_LOAD_PATH], @controller.view_paths
end
def test_controller_appends_view_path_correctly
@controller.append_view_path 'foo'
- assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths.map(&:to_s)
+ assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths
@controller.append_view_path(%w(bar baz))
- assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
- @controller.append_view_path(LOAD_PATH_ROOT)
- assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ @controller.append_view_path(FIXTURE_LOAD_PATH)
+ assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
- @controller.append_view_path([LOAD_PATH_ROOT])
- assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ @controller.append_view_path([FIXTURE_LOAD_PATH])
+ assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
end
def test_controller_prepends_view_path_correctly
@controller.prepend_view_path 'baz'
- assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths
@controller.prepend_view_path(%w(foo bar))
- assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
- @controller.prepend_view_path(LOAD_PATH_ROOT)
- assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
+ @controller.prepend_view_path(FIXTURE_LOAD_PATH)
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
- @controller.prepend_view_path([LOAD_PATH_ROOT])
- assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
+ @controller.prepend_view_path([FIXTURE_LOAD_PATH])
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
end
def test_template_appends_view_path_correctly
@@ -83,10 +79,10 @@ class ViewLoadPathsTest < Test::Unit::TestCase
class_view_paths = TestController.view_paths
@controller.append_view_path 'foo'
- assert_equal [LOAD_PATH_ROOT, 'foo'], @controller.view_paths.map(&:to_s)
+ assert_equal [FIXTURE_LOAD_PATH, 'foo'], @controller.view_paths
@controller.append_view_path(%w(bar baz))
- assert_equal [LOAD_PATH_ROOT, 'foo', 'bar', 'baz'], @controller.view_paths.map(&:to_s)
+ assert_equal [FIXTURE_LOAD_PATH, 'foo', 'bar', 'baz'], @controller.view_paths
assert_equal class_view_paths, TestController.view_paths
end
@@ -95,10 +91,10 @@ class ViewLoadPathsTest < Test::Unit::TestCase
class_view_paths = TestController.view_paths
@controller.prepend_view_path 'baz'
- assert_equal ['baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ assert_equal ['baz', FIXTURE_LOAD_PATH], @controller.view_paths
@controller.prepend_view_path(%w(foo bar))
- assert_equal ['foo', 'bar', 'baz', LOAD_PATH_ROOT], @controller.view_paths.map(&:to_s)
+ assert_equal ['foo', 'bar', 'baz', FIXTURE_LOAD_PATH], @controller.view_paths
assert_equal class_view_paths, TestController.view_paths
end
@@ -109,7 +105,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase
end
def test_view_paths_override
- TestController.prepend_view_path "#{LOAD_PATH_ROOT}/override"
+ TestController.prepend_view_path "#{FIXTURE_LOAD_PATH}/override"
get :hello_world
assert_response :success
assert_equal "Hello overridden world!", @response.body
@@ -117,7 +113,7 @@ class ViewLoadPathsTest < Test::Unit::TestCase
def test_view_paths_override_for_layouts_in_controllers_with_a_module
@controller = Test::SubController.new
- Test::SubController.view_paths = [ "#{LOAD_PATH_ROOT}/override", LOAD_PATH_ROOT, "#{LOAD_PATH_ROOT}/override2" ]
+ Test::SubController.view_paths = [ "#{FIXTURE_LOAD_PATH}/override", FIXTURE_LOAD_PATH, "#{FIXTURE_LOAD_PATH}/override2" ]
get :hello_world
assert_response :success
assert_equal "layout: Hello overridden world!", @response.body
@@ -140,26 +136,12 @@ class ViewLoadPathsTest < Test::Unit::TestCase
A.view_paths = ['a/path']
- assert_equal ['a/path'], A.view_paths.map(&:to_s)
+ assert_equal ['a/path'], A.view_paths
assert_equal A.view_paths, B.view_paths
assert_equal original_load_paths, C.view_paths
C.view_paths = []
assert_nothing_raised { C.view_paths << 'c/path' }
- assert_equal ['c/path'], C.view_paths.map(&:to_s)
- end
-
- def test_find_template_file_for_path
- assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.erb").to_s
- assert_equal "test/hello.builder", @controller.view_paths.find_template_file_for_path("test/hello.builder").to_s
- assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.erb")
- end
-
- def test_view_paths_find_template_file_for_path
- assert_equal "test/formatted_html_erb.html.erb", @controller.view_paths.find_template_file_for_path("test/formatted_html_erb.html").to_s
- assert_equal "test/formatted_xml_erb.xml.erb", @controller.view_paths.find_template_file_for_path("test/formatted_xml_erb.xml").to_s
- assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.html").to_s
- assert_equal "test/hello_world.erb", @controller.view_paths.find_template_file_for_path("test/hello_world.xml").to_s
- assert_equal nil, @controller.view_paths.find_template_file_for_path("test/missing.html")
+ assert_equal ['c/path'], C.view_paths
end
end
diff --git a/actionpack/test/fixtures/developers/_developer.erb b/actionpack/test/fixtures/developers/_developer.erb
new file mode 100644
index 0000000000..904a3137e7
--- /dev/null
+++ b/actionpack/test/fixtures/developers/_developer.erb
@@ -0,0 +1 @@
+<%= developer.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/fun/games/_game.erb b/actionpack/test/fixtures/fun/games/_game.erb
new file mode 100644
index 0000000000..d51b7b3ebc
--- /dev/null
+++ b/actionpack/test/fixtures/fun/games/_game.erb
@@ -0,0 +1 @@
+<%= game.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/fun/serious/games/_game.erb b/actionpack/test/fixtures/fun/serious/games/_game.erb
new file mode 100644
index 0000000000..d51b7b3ebc
--- /dev/null
+++ b/actionpack/test/fixtures/fun/serious/games/_game.erb
@@ -0,0 +1 @@
+<%= game.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb b/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb
new file mode 100644
index 0000000000..87309b8ccb
--- /dev/null
+++ b/actionpack/test/fixtures/functional_caching/inline_fragment_cached.html.erb
@@ -0,0 +1,2 @@
+<%= render :inline => 'Some inline content' %>
+<% cache do %>Some cached content<% end %>
diff --git a/actionpack/test/fixtures/projects/_project.erb b/actionpack/test/fixtures/projects/_project.erb
new file mode 100644
index 0000000000..480c4c2af3
--- /dev/null
+++ b/actionpack/test/fixtures/projects/_project.erb
@@ -0,0 +1 @@
+<%= project.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/public/javascripts/subdir/subdir.js b/actionpack/test/fixtures/public/javascripts/subdir/subdir.js
new file mode 100644
index 0000000000..9d23a67aa1
--- /dev/null
+++ b/actionpack/test/fixtures/public/javascripts/subdir/subdir.js
@@ -0,0 +1 @@
+// subdir js
diff --git a/actionpack/test/fixtures/public/stylesheets/subdir/subdir.css b/actionpack/test/fixtures/public/stylesheets/subdir/subdir.css
new file mode 100644
index 0000000000..241152a905
--- /dev/null
+++ b/actionpack/test/fixtures/public/stylesheets/subdir/subdir.css
@@ -0,0 +1 @@
+/* subdir.css */
diff --git a/actionpack/test/fixtures/replies/_reply.erb b/actionpack/test/fixtures/replies/_reply.erb
new file mode 100644
index 0000000000..68baf548d8
--- /dev/null
+++ b/actionpack/test/fixtures/replies/_reply.erb
@@ -0,0 +1 @@
+<%= reply.content %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_customer_with_var.erb b/actionpack/test/fixtures/test/_customer_with_var.erb
new file mode 100644
index 0000000000..3379246b7e
--- /dev/null
+++ b/actionpack/test/fixtures/test/_customer_with_var.erb
@@ -0,0 +1 @@
+<%= customer.name %> <%= object.name %> <%= customer_with_var.name %> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/_local_inspector.html.erb b/actionpack/test/fixtures/test/_local_inspector.html.erb
new file mode 100644
index 0000000000..c5a6e3e5bc
--- /dev/null
+++ b/actionpack/test/fixtures/test/_local_inspector.html.erb
@@ -0,0 +1 @@
+<%= local_assigns.keys.map(&:to_s).sort.join(",") -%> \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hello.builder b/actionpack/test/fixtures/test/hello.builder
index 82a4a310d3..86a8bb3d7b 100644
--- a/actionpack/test/fixtures/test/hello.builder
+++ b/actionpack/test/fixtures/test/hello.builder
@@ -1,4 +1,4 @@
xml.html do
xml.p "Hello #{@name}"
- xml << render_file("test/greeting")
+ xml << render("test/greeting")
end \ No newline at end of file
diff --git a/actionpack/test/fixtures/test/hyphen-ated.erb b/actionpack/test/fixtures/test/hyphen-ated.erb
new file mode 100644
index 0000000000..cd0875583a
--- /dev/null
+++ b/actionpack/test/fixtures/test/hyphen-ated.erb
@@ -0,0 +1 @@
+Hello world!
diff --git a/actionpack/test/fixtures/test/non_erb_block_content_for.builder b/actionpack/test/fixtures/test/non_erb_block_content_for.builder
index 6ff6db0f95..a94643561c 100644
--- a/actionpack/test/fixtures/test/non_erb_block_content_for.builder
+++ b/actionpack/test/fixtures/test/non_erb_block_content_for.builder
@@ -1,4 +1,4 @@
content_for :title do
'Putting stuff in the title!'
end
-xml << "\nGreat stuff!" \ No newline at end of file
+xml << "\nGreat stuff!"
diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb
index 4a8117a88a..3cfc8fa4ed 100644
--- a/actionpack/test/template/asset_tag_helper_test.rb
+++ b/actionpack/test/template/asset_tag_helper_test.rb
@@ -83,6 +83,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(javascript_include_tag("common.javascript", "/elsewhere/cools")) => %(<script src="/javascripts/common.javascript" type="text/javascript"></script>\n<script src="/elsewhere/cools.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag(:all)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
+ %(javascript_include_tag(:all, :recursive => true)) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
%(javascript_include_tag(:defaults, "test")) => %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>),
%(javascript_include_tag("test", :defaults)) => %(<script src="/javascripts/test.js" type="text/javascript"></script>\n<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>)
}
@@ -108,6 +109,7 @@ class AssetTagHelperTest < ActionView::TestCase
%(stylesheet_link_tag("dir/file")) => %(<link href="/stylesheets/dir/file.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("style", :media => "all")) => %(<link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag(:all)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ %(stylesheet_link_tag(:all, :recursive => true)) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag(:all, :media => "all")) => %(<link href="/stylesheets/bank.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="all" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="all" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("random.styles", "/css/stylish")) => %(<link href="/stylesheets/random.styles" media="screen" rel="stylesheet" type="text/css" />\n<link href="/css/stylish.css" media="screen" rel="stylesheet" type="text/css" />),
%(stylesheet_link_tag("http://www.example.com/styles/style")) => %(<link href="http://www.example.com/styles/style.css" media="screen" rel="stylesheet" type="text/css" />)
@@ -333,8 +335,9 @@ class AssetTagHelperTest < ActionView::TestCase
ActionController::Base.asset_host = 'http://a%d.example.com'
ActionController::Base.perform_caching = true
+ hash = '/javascripts/cache/money.js'.hash % 4
assert_dom_equal(
- %(<script src="http://a3.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
+ %(<script src="http://a#{hash}.example.com/javascripts/cache/money.js" type="text/javascript"></script>),
javascript_include_tag(:all, :cache => "cache/money")
)
@@ -343,6 +346,27 @@ class AssetTagHelperTest < ActionView::TestCase
FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'cache', 'money.js'))
end
+ def test_caching_javascript_include_tag_with_all_and_recursive_puts_defaults_at_the_start_of_the_file
+ ENV["RAILS_ASSET_ID"] = ""
+ ActionController::Base.asset_host = 'http://a0.example.com'
+ ActionController::Base.perform_caching = true
+
+ assert_dom_equal(
+ %(<script src="http://a0.example.com/javascripts/combined.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => "combined", :recursive => true)
+ )
+
+ assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
+
+ assert_equal(
+ %(// prototype js\n\n// effects js\n\n// dragdrop js\n\n// controls js\n\n// application js\n\n// bank js\n\n// robber js\n\n// subdir js\n\n\n// version.1.0 js),
+ IO.read(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
+ )
+
+ ensure
+ FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'combined.js'))
+ end
+
def test_caching_javascript_include_tag_with_all_puts_defaults_at_the_start_of_the_file
ENV["RAILS_ASSET_ID"] = ""
ActionController::Base.asset_host = 'http://a0.example.com'
@@ -373,6 +397,11 @@ class AssetTagHelperTest < ActionView::TestCase
javascript_include_tag(:all, :cache => true)
)
+ assert_dom_equal(
+ %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => true, :recursive => true)
+ )
+
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js'))
assert_dom_equal(
@@ -380,6 +409,11 @@ class AssetTagHelperTest < ActionView::TestCase
javascript_include_tag(:all, :cache => "money")
)
+ assert_dom_equal(
+ %(<script src="/javascripts/prototype.js" type="text/javascript"></script>\n<script src="/javascripts/effects.js" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js" type="text/javascript"></script>\n<script src="/javascripts/controls.js" type="text/javascript"></script>\n<script src="/javascripts/application.js" type="text/javascript"></script>\n<script src="/javascripts/bank.js" type="text/javascript"></script>\n<script src="/javascripts/robber.js" type="text/javascript"></script>\n<script src="/javascripts/subdir/subdir.js" type="text/javascript"></script>\n<script src="/javascripts/version.1.0.js" type="text/javascript"></script>),
+ javascript_include_tag(:all, :cache => "money", :recursive => true)
+ )
+
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js'))
end
@@ -432,6 +466,11 @@ class AssetTagHelperTest < ActionView::TestCase
stylesheet_link_tag(:all, :cache => true)
)
+ assert_dom_equal(
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => true, :recursive => true)
+ )
+
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css'))
assert_dom_equal(
@@ -439,6 +478,11 @@ class AssetTagHelperTest < ActionView::TestCase
stylesheet_link_tag(:all, :cache => "money")
)
+ assert_dom_equal(
+ %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/subdir/subdir.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/stylesheets/version.1.0.css" media="screen" rel="stylesheet" type="text/css" />),
+ stylesheet_link_tag(:all, :cache => "money", :recursive => true)
+ )
+
assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css'))
end
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 11b3bdb3fa..8b4e94c67f 100755
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1155,6 +1155,30 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", {}, :class => 'selector')
end
+ def test_date_select_with_html_options_within_fields_for
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+
+ fields_for :post, @post do |f|
+ concat f.date_select(:written_on, {}, :class => 'selector')
+ end
+
+ expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]" class="selector">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]" class="selector">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_written_on_3i" name="post[written_on(3i)]" class="selector">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+
+ expected << "</select>\n"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_time_select
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -1174,6 +1198,21 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on")
end
+ def test_time_select_without_date_hidden_fields
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ expected = %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, time_select("post", "written_on", :ignore_date => true)
+ end
+
def test_time_select_with_seconds
@post = Post.new
@post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
@@ -1216,6 +1255,29 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, time_select("post", "written_on", {}, :class => 'selector')
end
+ def test_time_select_with_html_options_within_fields_for
+ @post = Post.new
+ @post.written_on = Time.local(2004, 6, 15, 15, 16, 35)
+
+ fields_for :post, @post do |f|
+ concat f.time_select(:written_on, {}, :class => 'selector')
+ end
+
+ expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n}
+ expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n}
+ expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n}
+
+ expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]" class="selector">\n)
+ 0.upto(23) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 15}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+ expected << " : "
+ expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]" class="selector">\n)
+ 0.upto(59) { |i| expected << %(<option value="#{leading_zero_on_single_digits(i)}"#{' selected="selected"' if i == 16}>#{leading_zero_on_single_digits(i)}</option>\n) }
+ expected << "</select>\n"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_datetime_select
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -1281,21 +1343,21 @@ class DateHelperTest < ActionView::TestCase
end
end
- def test_datetime_select_within_fields_for
+ def test_datetime_select_with_html_options_within_fields_for
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
fields_for :post, @post do |f|
- concat f.datetime_select(:updated_at)
+ concat f.datetime_select(:updated_at, {}, :class => 'selector')
end
- expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
- expected << "<select id='post_updated_at_2i' name='post[updated_at(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
- expected << "<select id='post_updated_at_3i' name='post[updated_at(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
- expected << " &mdash; <select id='post_updated_at_4i' name='post[updated_at(4i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n"
- expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
+ expected = "<select id='post_updated_at_1i' name='post[updated_at(1i)]' class='selector'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_updated_at_2i' name='post[updated_at(2i)]' class='selector'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_updated_at_3i' name='post[updated_at(3i)]' class='selector'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+ expected << " &mdash; <select id='post_updated_at_4i' name='post[updated_at(4i)]' class='selector'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option selected='selected' value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n</select>\n"
+ expected << " : <select id='post_updated_at_5i' name='post[updated_at(5i)]' class='selector'>\n<option value='00'>00</option>\n<option value='01'>01</option>\n<option value='02'>02</option>\n<option value='03'>03</option>\n<option value='04'>04</option>\n<option value='05'>05</option>\n<option value='06'>06</option>\n<option value='07'>07</option>\n<option value='08'>08</option>\n<option value='09'>09</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n<option value='32'>32</option>\n<option value='33'>33</option>\n<option value='34'>34</option>\n<option selected='selected' value='35'>35</option>\n<option value='36'>36</option>\n<option value='37'>37</option>\n<option value='38'>38</option>\n<option value='39'>39</option>\n<option value='40'>40</option>\n<option value='41'>41</option>\n<option value='42'>42</option>\n<option value='43'>43</option>\n<option value='44'>44</option>\n<option value='45'>45</option>\n<option value='46'>46</option>\n<option value='47'>47</option>\n<option value='48'>48</option>\n<option value='49'>49</option>\n<option value='50'>50</option>\n<option value='51'>51</option>\n<option value='52'>52</option>\n<option value='53'>53</option>\n<option value='54'>54</option>\n<option value='55'>55</option>\n<option value='56'>56</option>\n<option value='57'>57</option>\n<option value='58'>58</option>\n<option value='59'>59</option>\n</select>\n"
- assert_dom_equal(expected, output_buffer)
+ assert_dom_equal expected, output_buffer
end
def test_date_select_with_zero_value_and_no_start_year
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index 3f89a5e426..9dd43d7b4f 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -1,66 +1,43 @@
require 'abstract_unit'
-class MockTimeZone
- attr_reader :name
-
- def initialize( name )
- @name = name
- end
-
- def self.all
- [ "A", "B", "C", "D", "E" ].map { |s| new s }
- end
-
- def ==( z )
- z && @name == z.name
- end
-
- def to_s
- @name
- end
-end
-
-class FormOptionsHelperTest < ActionView::TestCase
- tests ActionView::Helpers::FormOptionsHelper
-
- silence_warnings do
- Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
- Continent = Struct.new('Continent', :continent_name, :countries)
- Country = Struct.new('Country', :country_id, :country_name)
- Firm = Struct.new('Firm', :time_zone)
- Album = Struct.new('Album', :id, :title, :genre)
-
- ActiveSupport::TimeZone = MockTimeZone
- end
+TZInfo::Timezone.cattr_reader :loaded_zones
+
+uses_mocha "FormOptionsHelperTest" do
+ class FormOptionsHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::FormOptionsHelper
+
+ silence_warnings do
+ Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
+ Continent = Struct.new('Continent', :continent_name, :countries)
+ Country = Struct.new('Country', :country_id, :country_name)
+ Firm = Struct.new('Firm', :time_zone)
+ Album = Struct.new('Album', :id, :title, :genre)
+ end
- def test_collection_options
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
-
- assert_dom_equal(
- "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
- options_from_collection_for_select(@posts, "author_name", "title")
- )
- end
+ def setup
+ @fake_timezones = %w(A B C D E).inject([]) do |zones, id|
+ tz = TZInfo::Timezone.loaded_zones[id] = stub(:name => id, :to_s => id)
+ ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz)
+ zones << tz
+ end
+ ActiveSupport::TimeZone.stubs(:all).returns(@fake_timezones)
+ end
+ def test_collection_options
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- def test_collection_options_with_preselected_value
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title")
+ )
+ end
- assert_dom_equal(
- "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
- options_from_collection_for_select(@posts, "author_name", "title", "Babe")
- )
- end
- def test_collection_options_with_preselected_value_array
+ def test_collection_options_with_preselected_value
@posts = [
Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
Post.new("Babe went home", "Babe", "To a little house", "shh!"),
@@ -68,354 +45,437 @@ class FormOptionsHelperTest < ActionView::TestCase
]
assert_dom_equal(
- "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
- options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", "Babe")
)
- end
+ end
- def test_array_options_for_select
- assert_dom_equal(
- "<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
- options_for_select([ "<Denmark>", "USA", "Sweden" ])
- )
- end
+ def test_collection_options_with_preselected_value_array
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ assert_dom_equal(
+ "<option value=\"&lt;Abe&gt;\">&lt;Abe&gt; went home</option>\n<option value=\"Babe\" selected=\"selected\">Babe went home</option>\n<option value=\"Cabe\" selected=\"selected\">Cabe went home</option>",
+ options_from_collection_for_select(@posts, "author_name", "title", [ "Babe", "Cabe" ])
+ )
+ end
- def test_array_options_for_select_with_selection
- assert_dom_equal(
- "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
- options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
- )
- end
+ def test_array_options_for_select
+ assert_dom_equal(
+ "<option value=\"&lt;Denmark&gt;\">&lt;Denmark&gt;</option>\n<option value=\"USA\">USA</option>\n<option value=\"Sweden\">Sweden</option>",
+ options_for_select([ "<Denmark>", "USA", "Sweden" ])
+ )
+ end
- def test_array_options_for_select_with_selection_array
+ def test_array_options_for_select_with_selection
assert_dom_equal(
- "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
- options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
+ "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\">Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], "<USA>")
)
- end
+ end
+
+ def test_array_options_for_select_with_selection_array
+ assert_dom_equal(
+ "<option value=\"Denmark\">Denmark</option>\n<option value=\"&lt;USA&gt;\" selected=\"selected\">&lt;USA&gt;</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>",
+ options_for_select([ "Denmark", "<USA>", "Sweden" ], [ "<USA>", "Sweden" ])
+ )
+ end
+
+ def test_array_options_for_string_include_in_other_string_bug_fix
+ assert_dom_equal(
+ "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
+ options_for_select([ "ruby", "rubyonrails" ], "rubyonrails")
+ )
+ assert_dom_equal(
+ "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>",
+ options_for_select([ "ruby", "rubyonrails" ], "ruby")
+ )
+ assert_dom_equal(
+ %(<option value="ruby" selected="selected">ruby</option>\n<option value="rubyonrails">rubyonrails</option>\n<option value=""></option>),
+ options_for_select([ "ruby", "rubyonrails", nil ], "ruby")
+ )
+ end
- def test_array_options_for_string_include_in_other_string_bug_fix
+ def test_hash_options_for_select
assert_dom_equal(
- "<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
- options_for_select([ "ruby", "rubyonrails" ], "rubyonrails")
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
+ options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").sort.join("\n")
)
assert_dom_equal(
- "<option value=\"ruby\" selected=\"selected\">ruby</option>\n<option value=\"rubyonrails\">rubyonrails</option>",
- options_for_select([ "ruby", "rubyonrails" ], "ruby")
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").sort.join("\n")
)
assert_dom_equal(
- %(<option value="ruby" selected="selected">ruby</option>\n<option value="rubyonrails">rubyonrails</option>\n<option value=""></option>),
- options_for_select([ "ruby", "rubyonrails", nil ], "ruby")
+ "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").sort.join("\n")
)
- end
+ end
- def test_hash_options_for_select
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
- options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").sort.join("\n")
- )
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").sort.join("\n")
- )
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").sort.join("\n")
- )
- end
+ def test_ducktyped_options_for_select
+ quack = Struct.new(:first, :last)
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")])
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], "Dollar")
+ )
+ assert_dom_equal(
+ "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
+ options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], ["Dollar", "<Kroner>"])
+ )
+ end
- def test_ducktyped_options_for_select
- quack = Struct.new(:first, :last)
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\">$</option>",
- options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")])
- )
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], "Dollar")
- )
- assert_dom_equal(
- "<option value=\"&lt;Kroner&gt;\" selected=\"selected\">&lt;DKR&gt;</option>\n<option value=\"Dollar\" selected=\"selected\">$</option>",
- options_for_select([quack.new("<DKR>", "<Kroner>"), quack.new("$", "Dollar")], ["Dollar", "<Kroner>"])
- )
- end
+ def test_option_groups_from_collection_for_select
+ @continents = [
+ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
+ Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
+ ]
- def test_option_groups_from_collection_for_select
- @continents = [
- Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ),
- Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] )
- ]
+ assert_dom_equal(
+ "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
+ option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
+ )
+ end
- assert_dom_equal(
- "<optgroup label=\"&lt;Africa&gt;\"><option value=\"&lt;sa&gt;\">&lt;South Africa&gt;</option>\n<option value=\"so\">Somalia</option></optgroup><optgroup label=\"Europe\"><option value=\"dk\" selected=\"selected\">Denmark</option>\n<option value=\"ie\">Ireland</option></optgroup>",
- option_groups_from_collection_for_select(@continents, "countries", "continent_name", "country_id", "country_name", "dk")
- )
- end
+ def test_time_zone_options_no_parms
+ opts = time_zone_options_for_select
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
- def test_time_zone_options_no_parms
- opts = time_zone_options_for_select
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
- "<option value=\"E\">E</option>",
- opts
- end
+ def test_time_zone_options_with_selected
+ opts = time_zone_options_for_select( "D" )
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
- def test_time_zone_options_with_selected
- opts = time_zone_options_for_select( "D" )
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>",
- opts
- end
+ def test_time_zone_options_with_unknown_selected
+ opts = time_zone_options_for_select( "K" )
+ assert_dom_equal "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>\n" +
+ "<option value=\"E\">E</option>",
+ opts
+ end
- def test_time_zone_options_with_unknown_selected
- opts = time_zone_options_for_select( "K" )
- assert_dom_equal "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
- "<option value=\"E\">E</option>",
- opts
- end
+ def test_time_zone_options_with_priority_zones
+ zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( nil, zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
- def test_time_zone_options_with_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( nil, zones )
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>",
- opts
- end
+ def test_time_zone_options_with_selected_priority_zones
+ zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( "E", zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\" selected=\"selected\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
- def test_time_zone_options_with_selected_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( "E", zones )
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\" selected=\"selected\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>",
- opts
- end
+ def test_time_zone_options_with_unselected_priority_zones
+ zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
+ opts = time_zone_options_for_select( "C", zones )
+ assert_dom_equal "<option value=\"B\">B</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"C\" selected=\"selected\">C</option>\n" +
+ "<option value=\"D\">D</option>",
+ opts
+ end
- def test_time_zone_options_with_unselected_priority_zones
- zones = [ ActiveSupport::TimeZone.new( "B" ), ActiveSupport::TimeZone.new( "E" ) ]
- opts = time_zone_options_for_select( "C", zones )
- assert_dom_equal "<option value=\"B\">B</option>\n" +
- "<option value=\"E\">E</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"C\" selected=\"selected\">C</option>\n" +
- "<option value=\"D\">D</option>",
- opts
- end
+ def test_select
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><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))
+ )
+ end
- def test_select
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><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))
- )
- end
+ def test_select_under_fields_for
+ @post = Post.new
+ @post.category = "<mus>"
+
+ fields_for :post, @post do |f|
+ concat f.select(:category, %w( abe <mus> hest))
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ output_buffer
+ )
+ end
+
+ def test_select_under_fields_for_with_index
+ @post = Post.new
+ @post.category = "<mus>"
- def test_select_under_fields_for
- @post = Post.new
- @post.category = "<mus>"
+ fields_for :post, @post, :index => 108 do |f|
+ concat f.select(:category, %w( abe <mus> hest))
+ end
- fields_for :post, @post do |f|
- concat f.select(:category, %w( abe <mus> hest))
+ assert_dom_equal(
+ "<select id=\"post_108_category\" name=\"post[108][category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ output_buffer
+ )
end
-
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
- output_buffer
- )
- end
- def test_select_with_blank
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></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 => true)
- )
- end
+ def test_select_under_fields_for_with_auto_index
+ @post = Post.new
+ @post.category = "<mus>"
+ def @post.to_param; 108; end
- def test_select_with_blank_as_string
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">None</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
+ fields_for "post[]", @post do |f|
+ concat f.select(:category, %w( abe <mus> hest))
+ end
- def test_select_with_default_prompt
- @post = Post.new
- @post.category = ""
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</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 => true)
- )
- end
+ assert_dom_equal(
+ "<select id=\"post_108_category\" name=\"post[108][category]\"><option value=\"abe\">abe</option>\n<option value=\"&lt;mus&gt;\" selected=\"selected\">&lt;mus&gt;</option>\n<option value=\"hest\">hest</option></select>",
+ output_buffer
+ )
+ end
- def test_select_no_prompt_when_select_has_value
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><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), :prompt => true)
- )
- end
+ def test_select_with_blank
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\"></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 => true)
+ )
+ end
- def test_select_with_given_prompt
- @post = Post.new
- @post.category = ""
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</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_blank_as_string
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">None</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_prompt_and_blank
- @post = Post.new
- @post.category = ""
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></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 => true, :include_blank => true)
- )
- end
+ def test_select_with_default_prompt
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</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 => true)
+ )
+ end
- def test_select_with_selected_value
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">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 ), :selected => 'abe')
- )
- end
+ def test_select_no_prompt_when_select_has_value
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><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), :prompt => true)
+ )
+ end
+
+ def test_select_with_given_prompt
+ @post = Post.new
+ @post.category = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">The prompt</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 = ""
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"\">Please select</option>\n<option value=\"\"></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 => true, :include_blank => true)
+ )
+ end
+
+ def test_select_with_selected_value
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><option value=\"abe\" selected=\"selected\">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 ), :selected => 'abe')
+ )
+ end
- def test_select_with_index_option
- @album = Album.new
- @album.id = 1
+ def test_select_with_index_option
+ @album = Album.new
+ @album.id = 1
- expected = "<select id=\"album__genre\" name=\"album[][genre]\"><option value=\"rap\">rap</option>\n<option value=\"rock\">rock</option>\n<option value=\"country\">country</option></select>"
+ expected = "<select id=\"album__genre\" name=\"album[][genre]\"><option value=\"rap\">rap</option>\n<option value=\"rock\">rock</option>\n<option value=\"country\">country</option></select>"
- assert_dom_equal(
- expected,
- select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
- )
- end
+ assert_dom_equal(
+ expected,
+ select("album[]", "genre", %w[rap rock country], {}, { :index => nil })
+ )
+ end
- def test_select_with_selected_nil
- @post = Post.new
- @post.category = "<mus>"
- assert_dom_equal(
- "<select id=\"post_category\" name=\"post[category]\"><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 ), :selected => nil)
- )
- end
+ def test_select_with_selected_nil
+ @post = Post.new
+ @post.category = "<mus>"
+ assert_dom_equal(
+ "<select id=\"post_category\" name=\"post[category]\"><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 ), :selected => nil)
+ )
+ end
- def test_collection_select
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ def test_collection_select
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- @post = Post.new
- @post.author_name = "Babe"
+ @post = Post.new
+ @post.author_name = "Babe"
- assert_dom_equal(
- "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
- collection_select("post", "author_name", @posts, "author_name", "author_name")
- )
- end
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name")
+ )
+ end
- def test_collection_select_under_fields_for
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ def test_collection_select_under_fields_for
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- @post = Post.new
- @post.author_name = "Babe"
+ @post = Post.new
+ @post.author_name = "Babe"
- fields_for :post, @post do |f|
- concat f.collection_select(:author_name, @posts, :author_name, :author_name)
- end
+ fields_for :post, @post do |f|
+ concat f.collection_select(:author_name, @posts, :author_name, :author_name)
+ end
- assert_dom_equal(
- "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
- output_buffer
- )
- end
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ output_buffer
+ )
+ end
- def test_collection_select_with_blank_and_style
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ def test_collection_select_under_fields_for_with_index
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- @post = Post.new
- @post.author_name = "Babe"
+ @post = Post.new
+ @post.author_name = "Babe"
- assert_dom_equal(
- "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
- collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
- )
- end
+ fields_for :post, @post, :index => 815 do |f|
+ concat f.collection_select(:author_name, @posts, :author_name, :author_name)
+ end
- def test_collection_select_with_blank_as_string_and_style
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ assert_dom_equal(
+ "<select id=\"post_815_author_name\" name=\"post[815][author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ output_buffer
+ )
+ end
+
+ def test_collection_select_under_fields_for_with_auto_index
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- @post = Post.new
- @post.author_name = "Babe"
+ @post = Post.new
+ @post.author_name = "Babe"
+ def @post.to_param; 815; end
- assert_dom_equal(
- "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\">No Selection</option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
- collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => 'No Selection' }, "style" => "width: 200px")
- )
- end
+ fields_for "post[]", @post do |f|
+ concat f.collection_select(:author_name, @posts, :author_name, :author_name)
+ end
+
+ assert_dom_equal(
+ "<select id=\"post_815_author_name\" name=\"post[815][author_name]\"><option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ output_buffer
+ )
+ end
+
+ def test_collection_select_with_blank_and_style
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- def test_collection_select_with_multiple_option_appends_array_brackets
- @posts = [
- Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
- Post.new("Babe went home", "Babe", "To a little house", "shh!"),
- Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
- ]
+ @post = Post.new
+ @post.author_name = "Babe"
- @post = Post.new
- @post.author_name = "Babe"
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, "style" => "width: 200px")
+ )
+ end
- expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
+ def test_collection_select_with_blank_as_string_and_style
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
- # Should suffix default name with [].
- assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
+ @post = Post.new
+ @post.author_name = "Babe"
- # Shouldn't suffix custom name with [].
- assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
- end
+ assert_dom_equal(
+ "<select id=\"post_author_name\" name=\"post[author_name]\" style=\"width: 200px\"><option value=\"\">No Selection</option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>",
+ collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => 'No Selection' }, "style" => "width: 200px")
+ )
+ end
- def test_country_select
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
+ def test_collection_select_with_multiple_option_appends_array_brackets
+ @posts = [
+ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"),
+ Post.new("Babe went home", "Babe", "To a little house", "shh!"),
+ Post.new("Cabe went home", "Cabe", "To a little house", "shh!")
+ ]
+
+ @post = Post.new
+ @post.author_name = "Babe"
+
+ expected = "<select id=\"post_author_name\" name=\"post[author_name][]\" multiple=\"multiple\"><option value=\"\"></option>\n<option value=\"&lt;Abe&gt;\">&lt;Abe&gt;</option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\">Cabe</option></select>"
+
+ # Should suffix default name with [].
+ assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true }, :multiple => true)
+
+ # Shouldn't suffix custom name with [].
+ assert_dom_equal expected, collection_select("post", "author_name", @posts, "author_name", "author_name", { :include_blank => true, :name => 'post[author_name][]' }, :multiple => true)
+ end
+
+ def test_country_select
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option>
<option value="Aland Islands">Aland Islands</option>
<option value="Albania">Albania</option>
@@ -661,14 +721,14 @@ class FormOptionsHelperTest < ActionView::TestCase
<option value="Yemen">Yemen</option>
<option value="Zambia">Zambia</option>
<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
- end
+ COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin"))
+ end
- def test_country_select_with_priority_countries
- @post = Post.new
- @post.origin = "Denmark"
- expected_select = <<-COUNTRIES
+ def test_country_select_with_priority_countries
+ @post = Post.new
+ @post.origin = "Denmark"
+ expected_select = <<-COUNTRIES
<select id="post_origin" name="post[origin]"><option value="New Zealand">New Zealand</option>
<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
<option value="Afghanistan">Afghanistan</option>
@@ -916,14 +976,14 @@ COUNTRIES
<option value="Yemen">Yemen</option>
<option value="Zambia">Zambia</option>
<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
+ COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
- def test_country_select_with_selected_priority_country
- @post = Post.new
- @post.origin = "New Zealand"
- expected_select = <<-COUNTRIES
+ def test_country_select_with_selected_priority_country
+ @post = Post.new
+ @post.origin = "New Zealand"
+ expected_select = <<-COUNTRIES
<select id="post_origin" name="post[origin]"><option selected="selected" value="New Zealand">New Zealand</option>
<option value="Nicaragua">Nicaragua</option><option value="" disabled="disabled">-------------</option>
<option value="Afghanistan">Afghanistan</option>
@@ -1171,153 +1231,876 @@ COUNTRIES
<option value="Yemen">Yemen</option>
<option value="Zambia">Zambia</option>
<option value="Zimbabwe">Zimbabwe</option></select>
-COUNTRIES
- assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
- end
+ COUNTRIES
+ assert_dom_equal(expected_select[0..-2], country_select("post", "origin", ["New Zealand", "Nicaragua"]))
+ end
- def test_time_zone_select
- @firm = Firm.new("D")
- html = time_zone_select( "firm", "time_zone" )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- end
+ def test_country_select_under_fields_for
+ @post = Post.new
+ @post.origin = "Australia"
+ expected_select = <<-COUNTRIES
+<select id="post_origin" name="post[origin]"><option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option selected="selected" value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+ COUNTRIES
+
+ fields_for :post, @post do |f|
+ concat f.country_select("origin")
+ end
+
+ assert_dom_equal(expected_select[0..-2], output_buffer)
+ end
+
+ def test_country_select_under_fields_for_with_index
+ @post = Post.new
+ @post.origin = "United States"
+ expected_select = <<-COUNTRIES
+<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option selected="selected" value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+ COUNTRIES
- def test_time_zone_select_under_fields_for
- @firm = Firm.new("D")
+ fields_for :post, @post, :index => 325 do |f|
+ concat f.country_select("origin")
+ end
- fields_for :firm, @firm do |f|
- concat f.time_zone_select(:time_zone)
+ assert_dom_equal(expected_select[0..-2], output_buffer)
end
+
+ def test_country_select_under_fields_for_with_auto_index
+ @post = Post.new
+ @post.origin = "Iraq"
+ def @post.to_param; 325; end
+
+ expected_select = <<-COUNTRIES
+<select id="post_325_origin" name="post[325][origin]"><option value="Afghanistan">Afghanistan</option>
+<option value="Aland Islands">Aland Islands</option>
+<option value="Albania">Albania</option>
+<option value="Algeria">Algeria</option>
+<option value="American Samoa">American Samoa</option>
+<option value="Andorra">Andorra</option>
+<option value="Angola">Angola</option>
+<option value="Anguilla">Anguilla</option>
+<option value="Antarctica">Antarctica</option>
+<option value="Antigua And Barbuda">Antigua And Barbuda</option>
+<option value="Argentina">Argentina</option>
+<option value="Armenia">Armenia</option>
+<option value="Aruba">Aruba</option>
+<option value="Australia">Australia</option>
+<option value="Austria">Austria</option>
+<option value="Azerbaijan">Azerbaijan</option>
+<option value="Bahamas">Bahamas</option>
+<option value="Bahrain">Bahrain</option>
+<option value="Bangladesh">Bangladesh</option>
+<option value="Barbados">Barbados</option>
+<option value="Belarus">Belarus</option>
+<option value="Belgium">Belgium</option>
+<option value="Belize">Belize</option>
+<option value="Benin">Benin</option>
+<option value="Bermuda">Bermuda</option>
+<option value="Bhutan">Bhutan</option>
+<option value="Bolivia">Bolivia</option>
+<option value="Bosnia and Herzegowina">Bosnia and Herzegowina</option>
+<option value="Botswana">Botswana</option>
+<option value="Bouvet Island">Bouvet Island</option>
+<option value="Brazil">Brazil</option>
+<option value="British Indian Ocean Territory">British Indian Ocean Territory</option>
+<option value="Brunei Darussalam">Brunei Darussalam</option>
+<option value="Bulgaria">Bulgaria</option>
+<option value="Burkina Faso">Burkina Faso</option>
+<option value="Burundi">Burundi</option>
+<option value="Cambodia">Cambodia</option>
+<option value="Cameroon">Cameroon</option>
+<option value="Canada">Canada</option>
+<option value="Cape Verde">Cape Verde</option>
+<option value="Cayman Islands">Cayman Islands</option>
+<option value="Central African Republic">Central African Republic</option>
+<option value="Chad">Chad</option>
+<option value="Chile">Chile</option>
+<option value="China">China</option>
+<option value="Christmas Island">Christmas Island</option>
+<option value="Cocos (Keeling) Islands">Cocos (Keeling) Islands</option>
+<option value="Colombia">Colombia</option>
+<option value="Comoros">Comoros</option>
+<option value="Congo">Congo</option>
+<option value="Congo, the Democratic Republic of the">Congo, the Democratic Republic of the</option>
+<option value="Cook Islands">Cook Islands</option>
+<option value="Costa Rica">Costa Rica</option>
+<option value="Cote d'Ivoire">Cote d'Ivoire</option>
+<option value="Croatia">Croatia</option>
+<option value="Cuba">Cuba</option>
+<option value="Cyprus">Cyprus</option>
+<option value="Czech Republic">Czech Republic</option>
+<option value="Denmark">Denmark</option>
+<option value="Djibouti">Djibouti</option>
+<option value="Dominica">Dominica</option>
+<option value="Dominican Republic">Dominican Republic</option>
+<option value="Ecuador">Ecuador</option>
+<option value="Egypt">Egypt</option>
+<option value="El Salvador">El Salvador</option>
+<option value="Equatorial Guinea">Equatorial Guinea</option>
+<option value="Eritrea">Eritrea</option>
+<option value="Estonia">Estonia</option>
+<option value="Ethiopia">Ethiopia</option>
+<option value="Falkland Islands (Malvinas)">Falkland Islands (Malvinas)</option>
+<option value="Faroe Islands">Faroe Islands</option>
+<option value="Fiji">Fiji</option>
+<option value="Finland">Finland</option>
+<option value="France">France</option>
+<option value="French Guiana">French Guiana</option>
+<option value="French Polynesia">French Polynesia</option>
+<option value="French Southern Territories">French Southern Territories</option>
+<option value="Gabon">Gabon</option>
+<option value="Gambia">Gambia</option>
+<option value="Georgia">Georgia</option>
+<option value="Germany">Germany</option>
+<option value="Ghana">Ghana</option>
+<option value="Gibraltar">Gibraltar</option>
+<option value="Greece">Greece</option>
+<option value="Greenland">Greenland</option>
+<option value="Grenada">Grenada</option>
+<option value="Guadeloupe">Guadeloupe</option>
+<option value="Guam">Guam</option>
+<option value="Guatemala">Guatemala</option>
+<option value="Guernsey">Guernsey</option>
+<option value="Guinea">Guinea</option>
+<option value="Guinea-Bissau">Guinea-Bissau</option>
+<option value="Guyana">Guyana</option>
+<option value="Haiti">Haiti</option>
+<option value="Heard and McDonald Islands">Heard and McDonald Islands</option>
+<option value="Holy See (Vatican City State)">Holy See (Vatican City State)</option>
+<option value="Honduras">Honduras</option>
+<option value="Hong Kong">Hong Kong</option>
+<option value="Hungary">Hungary</option>
+<option value="Iceland">Iceland</option>
+<option value="India">India</option>
+<option value="Indonesia">Indonesia</option>
+<option value="Iran, Islamic Republic of">Iran, Islamic Republic of</option>
+<option selected="selected" value="Iraq">Iraq</option>
+<option value="Ireland">Ireland</option>
+<option value="Isle of Man">Isle of Man</option>
+<option value="Israel">Israel</option>
+<option value="Italy">Italy</option>
+<option value="Jamaica">Jamaica</option>
+<option value="Japan">Japan</option>
+<option value="Jersey">Jersey</option>
+<option value="Jordan">Jordan</option>
+<option value="Kazakhstan">Kazakhstan</option>
+<option value="Kenya">Kenya</option>
+<option value="Kiribati">Kiribati</option>
+<option value="Korea, Democratic People's Republic of">Korea, Democratic People's Republic of</option>
+<option value="Korea, Republic of">Korea, Republic of</option>
+<option value="Kuwait">Kuwait</option>
+<option value="Kyrgyzstan">Kyrgyzstan</option>
+<option value="Lao People's Democratic Republic">Lao People's Democratic Republic</option>
+<option value="Latvia">Latvia</option>
+<option value="Lebanon">Lebanon</option>
+<option value="Lesotho">Lesotho</option>
+<option value="Liberia">Liberia</option>
+<option value="Libyan Arab Jamahiriya">Libyan Arab Jamahiriya</option>
+<option value="Liechtenstein">Liechtenstein</option>
+<option value="Lithuania">Lithuania</option>
+<option value="Luxembourg">Luxembourg</option>
+<option value="Macao">Macao</option>
+<option value="Macedonia, The Former Yugoslav Republic Of">Macedonia, The Former Yugoslav Republic Of</option>
+<option value="Madagascar">Madagascar</option>
+<option value="Malawi">Malawi</option>
+<option value="Malaysia">Malaysia</option>
+<option value="Maldives">Maldives</option>
+<option value="Mali">Mali</option>
+<option value="Malta">Malta</option>
+<option value="Marshall Islands">Marshall Islands</option>
+<option value="Martinique">Martinique</option>
+<option value="Mauritania">Mauritania</option>
+<option value="Mauritius">Mauritius</option>
+<option value="Mayotte">Mayotte</option>
+<option value="Mexico">Mexico</option>
+<option value="Micronesia, Federated States of">Micronesia, Federated States of</option>
+<option value="Moldova, Republic of">Moldova, Republic of</option>
+<option value="Monaco">Monaco</option>
+<option value="Mongolia">Mongolia</option>
+<option value="Montenegro">Montenegro</option>
+<option value="Montserrat">Montserrat</option>
+<option value="Morocco">Morocco</option>
+<option value="Mozambique">Mozambique</option>
+<option value="Myanmar">Myanmar</option>
+<option value="Namibia">Namibia</option>
+<option value="Nauru">Nauru</option>
+<option value="Nepal">Nepal</option>
+<option value="Netherlands">Netherlands</option>
+<option value="Netherlands Antilles">Netherlands Antilles</option>
+<option value="New Caledonia">New Caledonia</option>
+<option value="New Zealand">New Zealand</option>
+<option value="Nicaragua">Nicaragua</option>
+<option value="Niger">Niger</option>
+<option value="Nigeria">Nigeria</option>
+<option value="Niue">Niue</option>
+<option value="Norfolk Island">Norfolk Island</option>
+<option value="Northern Mariana Islands">Northern Mariana Islands</option>
+<option value="Norway">Norway</option>
+<option value="Oman">Oman</option>
+<option value="Pakistan">Pakistan</option>
+<option value="Palau">Palau</option>
+<option value="Palestinian Territory, Occupied">Palestinian Territory, Occupied</option>
+<option value="Panama">Panama</option>
+<option value="Papua New Guinea">Papua New Guinea</option>
+<option value="Paraguay">Paraguay</option>
+<option value="Peru">Peru</option>
+<option value="Philippines">Philippines</option>
+<option value="Pitcairn">Pitcairn</option>
+<option value="Poland">Poland</option>
+<option value="Portugal">Portugal</option>
+<option value="Puerto Rico">Puerto Rico</option>
+<option value="Qatar">Qatar</option>
+<option value="Reunion">Reunion</option>
+<option value="Romania">Romania</option>
+<option value="Russian Federation">Russian Federation</option>
+<option value="Rwanda">Rwanda</option>
+<option value="Saint Barthelemy">Saint Barthelemy</option>
+<option value="Saint Helena">Saint Helena</option>
+<option value="Saint Kitts and Nevis">Saint Kitts and Nevis</option>
+<option value="Saint Lucia">Saint Lucia</option>
+<option value="Saint Pierre and Miquelon">Saint Pierre and Miquelon</option>
+<option value="Saint Vincent and the Grenadines">Saint Vincent and the Grenadines</option>
+<option value="Samoa">Samoa</option>
+<option value="San Marino">San Marino</option>
+<option value="Sao Tome and Principe">Sao Tome and Principe</option>
+<option value="Saudi Arabia">Saudi Arabia</option>
+<option value="Senegal">Senegal</option>
+<option value="Serbia">Serbia</option>
+<option value="Seychelles">Seychelles</option>
+<option value="Sierra Leone">Sierra Leone</option>
+<option value="Singapore">Singapore</option>
+<option value="Slovakia">Slovakia</option>
+<option value="Slovenia">Slovenia</option>
+<option value="Solomon Islands">Solomon Islands</option>
+<option value="Somalia">Somalia</option>
+<option value="South Africa">South Africa</option>
+<option value="South Georgia and the South Sandwich Islands">South Georgia and the South Sandwich Islands</option>
+<option value="Spain">Spain</option>
+<option value="Sri Lanka">Sri Lanka</option>
+<option value="Sudan">Sudan</option>
+<option value="Suriname">Suriname</option>
+<option value="Svalbard and Jan Mayen">Svalbard and Jan Mayen</option>
+<option value="Swaziland">Swaziland</option>
+<option value="Sweden">Sweden</option>
+<option value="Switzerland">Switzerland</option>
+<option value="Syrian Arab Republic">Syrian Arab Republic</option>
+<option value="Taiwan, Province of China">Taiwan, Province of China</option>
+<option value="Tajikistan">Tajikistan</option>
+<option value="Tanzania, United Republic of">Tanzania, United Republic of</option>
+<option value="Thailand">Thailand</option>
+<option value="Timor-Leste">Timor-Leste</option>
+<option value="Togo">Togo</option>
+<option value="Tokelau">Tokelau</option>
+<option value="Tonga">Tonga</option>
+<option value="Trinidad and Tobago">Trinidad and Tobago</option>
+<option value="Tunisia">Tunisia</option>
+<option value="Turkey">Turkey</option>
+<option value="Turkmenistan">Turkmenistan</option>
+<option value="Turks and Caicos Islands">Turks and Caicos Islands</option>
+<option value="Tuvalu">Tuvalu</option>
+<option value="Uganda">Uganda</option>
+<option value="Ukraine">Ukraine</option>
+<option value="United Arab Emirates">United Arab Emirates</option>
+<option value="United Kingdom">United Kingdom</option>
+<option value="United States">United States</option>
+<option value="United States Minor Outlying Islands">United States Minor Outlying Islands</option>
+<option value="Uruguay">Uruguay</option>
+<option value="Uzbekistan">Uzbekistan</option>
+<option value="Vanuatu">Vanuatu</option>
+<option value="Venezuela">Venezuela</option>
+<option value="Viet Nam">Viet Nam</option>
+<option value="Virgin Islands, British">Virgin Islands, British</option>
+<option value="Virgin Islands, U.S.">Virgin Islands, U.S.</option>
+<option value="Wallis and Futuna">Wallis and Futuna</option>
+<option value="Western Sahara">Western Sahara</option>
+<option value="Yemen">Yemen</option>
+<option value="Zambia">Zambia</option>
+<option value="Zimbabwe">Zimbabwe</option></select>
+ COUNTRIES
+
+ fields_for "post[]", @post do |f|
+ concat f.country_select("origin")
+ end
+
+ assert_dom_equal(expected_select[0..-2], output_buffer)
+ end
+
+ def test_time_zone_select
+ @firm = Firm.new("D")
+ html = time_zone_select( "firm", "time_zone" )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_under_fields_for
+ @firm = Firm.new("D")
+
+ fields_for :firm, @firm do |f|
+ concat f.time_zone_select(:time_zone)
+ end
- assert_dom_equal(
- "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- output_buffer
- )
- end
+ assert_dom_equal(
+ "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ output_buffer
+ )
+ end
- def test_time_zone_select_with_blank
- @firm = Firm.new("D")
- html = time_zone_select("firm", "time_zone", nil, :include_blank => true)
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"\"></option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- end
+ def test_time_zone_select_under_fields_for_with_index
+ @firm = Firm.new("D")
- def test_time_zone_select_with_blank_as_string
- @firm = Firm.new("D")
- html = time_zone_select("firm", "time_zone", nil, :include_blank => 'No Zone')
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"\">No Zone</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- end
+ fields_for :firm, @firm, :index => 305 do |f|
+ concat f.time_zone_select(:time_zone)
+ end
- def test_time_zone_select_with_style
- @firm = Firm.new("D")
- html = time_zone_select("firm", "time_zone", nil, {},
- "style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
- :style => "color: red")
- end
+ assert_dom_equal(
+ "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ output_buffer
+ )
+ end
- def test_time_zone_select_with_blank_and_style
- @firm = Firm.new("D")
- html = time_zone_select("firm", "time_zone", nil,
- { :include_blank => true }, "style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"\"></option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
- { :include_blank => true }, :style => "color: red")
- end
+ def test_time_zone_select_under_fields_for_with_auto_index
+ @firm = Firm.new("D")
+ def @firm.to_param; 305; end
- def test_time_zone_select_with_blank_as_string_and_style
- @firm = Firm.new("D")
- html = time_zone_select("firm", "time_zone", nil,
- { :include_blank => 'No Zone' }, "style" => "color: red")
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
- "<option value=\"\">No Zone</option>\n" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
- { :include_blank => 'No Zone' }, :style => "color: red")
- end
+ fields_for "firm[]", @firm do |f|
+ concat f.time_zone_select(:time_zone)
+ end
- def test_time_zone_select_with_priority_zones
- @firm = Firm.new("D")
- zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]
- html = time_zone_select("firm", "time_zone", zones )
- assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
- "<option value=\"A\">A</option>\n" +
- "<option value=\"D\" selected=\"selected\">D</option>" +
- "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
- "<option value=\"B\">B</option>\n" +
- "<option value=\"C\">C</option>\n" +
- "<option value=\"E\">E</option>" +
- "</select>",
- html
- end
+ assert_dom_equal(
+ "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ output_buffer
+ )
+ end
- def test_time_zone_select_with_default_time_zone_and_nil_value
- @firm = Firm.new()
- @firm.time_zone = nil
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ def test_time_zone_select_with_blank
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, :include_blank => true)
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\"></option>\n" +
"<option value=\"A\">A</option>\n" +
- "<option value=\"B\" selected=\"selected\">B</option>\n" +
+ "<option value=\"B\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
- "<option value=\"D\">D</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
"<option value=\"E\">E</option>" +
"</select>",
html
- end
+ end
- def test_time_zone_select_with_default_time_zone_and_value
- @firm = Firm.new('D')
- html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ def test_time_zone_select_with_blank_as_string
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, :include_blank => 'No Zone')
assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"\">No Zone</option>\n" +
"<option value=\"A\">A</option>\n" +
"<option value=\"B\">B</option>\n" +
"<option value=\"C\">C</option>\n" +
@@ -1325,6 +2108,117 @@ COUNTRIES
"<option value=\"E\">E</option>" +
"</select>",
html
- end
+ end
+
+ def test_time_zone_select_with_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil, {},
+ "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {},
+ :style => "color: red")
+ end
+
+ def test_time_zone_select_with_blank_and_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil,
+ { :include_blank => true }, "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"\"></option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
+ { :include_blank => true }, :style => "color: red")
+ end
+
+ def test_time_zone_select_with_blank_as_string_and_style
+ @firm = Firm.new("D")
+ html = time_zone_select("firm", "time_zone", nil,
+ { :include_blank => 'No Zone' }, "style" => "color: red")
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" +
+ "<option value=\"\">No Zone</option>\n" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ assert_dom_equal html, time_zone_select("firm", "time_zone", nil,
+ { :include_blank => 'No Zone' }, :style => "color: red")
+ end
+
+ def test_time_zone_select_with_priority_zones
+ @firm = Firm.new("D")
+ zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]
+ html = time_zone_select("firm", "time_zone", zones )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_with_priority_zones_as_regexp
+ @firm = Firm.new("D")
+ @fake_timezones.each_with_index do |tz, i|
+ tz.stubs(:=~).returns(i.zero? || i == 3)
+ end
+
+ html = time_zone_select("firm", "time_zone", /A|D/)
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>" +
+ "<option value=\"\" disabled=\"disabled\">-------------</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_with_default_time_zone_and_nil_value
+ @firm = Firm.new()
+ @firm.time_zone = nil
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\" selected=\"selected\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
+
+ def test_time_zone_select_with_default_time_zone_and_value
+ @firm = Firm.new('D')
+ html = time_zone_select( "firm", "time_zone", nil, :default => 'B' )
+ assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" +
+ "<option value=\"A\">A</option>\n" +
+ "<option value=\"B\">B</option>\n" +
+ "<option value=\"C\">C</option>\n" +
+ "<option value=\"D\" selected=\"selected\">D</option>\n" +
+ "<option value=\"E\">E</option>" +
+ "</select>",
+ html
+ end
-end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index 47b3605849..4e4102aec7 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -35,21 +35,23 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>)
assert_dom_equal expected, actual
end
-
+
def test_form_tag_with_method_delete
actual = form_tag({}, { :method => :delete })
expected = %(<form action="http://www.example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="delete" /></div>)
assert_dom_equal expected, actual
end
- def test_form_tag_with_block
+ def test_form_tag_with_block_in_erb
+ __in_erb_template = ''
form_tag("http://example.com") { concat "Hello world!" }
expected = %(<form action="http://example.com" method="post">Hello world!</form>)
assert_dom_equal expected, output_buffer
end
- def test_form_tag_with_block_and_method
+ def test_form_tag_with_block_and_method_in_erb
+ __in_erb_template = ''
form_tag("http://example.com", :method => :put) { concat "Hello world!" }
expected = %(<form action="http://example.com" method="post"><div style='margin:0;padding:0'><input type="hidden" name="_method" value="put" /></div>Hello world!</form>)
@@ -88,11 +90,11 @@ class FormTagHelperTest < ActionView::TestCase
actual = radio_button_tag("gender", "m") + radio_button_tag("gender", "f")
expected = %(<input id="gender_m" name="gender" type="radio" value="m" /><input id="gender_f" name="gender" type="radio" value="f" />)
assert_dom_equal expected, actual
-
+
actual = radio_button_tag("opinion", "-1") + radio_button_tag("opinion", "1")
expected = %(<input id="opinion_-1" name="opinion" type="radio" value="-1" /><input id="opinion_1" name="opinion" type="radio" value="1" />)
assert_dom_equal expected, actual
-
+
actual = radio_button_tag("person[gender]", "m")
expected = %(<input id="person_gender_m" name="person[gender]" type="radio" value="m" />)
assert_dom_equal expected, actual
@@ -103,13 +105,13 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<select id="people" name="people"><option>david</option></select>)
assert_dom_equal expected, actual
end
-
+
def test_select_tag_with_multiple
actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>", :multiple => :true
expected = %(<select id="colors" multiple="multiple" name="colors"><option>Red</option><option>Blue</option><option>Green</option></select>)
assert_dom_equal expected, actual
end
-
+
def test_select_tag_disabled
actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>", :disabled => :true
expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
@@ -145,37 +147,37 @@ class FormTagHelperTest < ActionView::TestCase
expected = %(<input class="admin" id="title" name="title" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_tag_size_symbol
actual = text_field_tag "title", "Hello!", :size => 75
expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_tag_size_string
actual = text_field_tag "title", "Hello!", "size" => "75"
expected = %(<input id="title" name="title" size="75" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_tag_maxlength_symbol
actual = text_field_tag "title", "Hello!", :maxlength => 75
expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_tag_maxlength_string
actual = text_field_tag "title", "Hello!", "maxlength" => "75"
expected = %(<input id="title" name="title" maxlength="75" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_disabled
actual = text_field_tag "title", "Hello!", :disabled => :true
expected = %(<input id="title" name="title" disabled="disabled" type="text" value="Hello!" />)
assert_dom_equal expected, actual
end
-
+
def test_text_field_tag_with_multiple_options
actual = text_field_tag "title", "Hello!", :size => 70, :maxlength => 80
expected = %(<input id="title" name="title" size="70" maxlength="80" type="text" value="Hello!" />)
@@ -220,18 +222,26 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_submit_tag_with_no_onclick_options
+ assert_dom_equal(
+ %(<input name='commit' type='submit' value='Save' onclick="this.setAttribute('originalValue', this.value);this.disabled=true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue'); this.disabled = false };return result;" />),
+ submit_tag("Save", :disable_with => "Saving...")
+ )
+ end
+
def test_submit_tag_with_confirmation
assert_dom_equal(
%(<input name='commit' type='submit' value='Save' onclick="return confirm('Are you sure?');"/>),
submit_tag("Save", :confirm => "Are you sure?")
)
end
-
+
def test_pass
assert_equal 1, 1
end
- def test_field_set_tag
+ def test_field_set_tag_in_erb
+ __in_erb_template = ''
field_set_tag("Your details") { concat "Hello world!" }
expected = %(<fieldset><legend>Your details</legend>Hello world!</fieldset>)
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 8c649ea544..36dfeba5ed 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -3,13 +3,7 @@ require 'abstract_unit'
class JavaScriptHelperTest < ActionView::TestCase
tests ActionView::Helpers::JavaScriptHelper
- def test_define_javascript_functions
- # check if prototype.js is included first
- assert_not_nil define_javascript_functions.split("\n")[1].match(/Prototype JavaScript framework/)
-
- # check that scriptaculous.js is not in here, only needed if loaded remotely
- assert_nil define_javascript_functions.split("\n")[1].match(/var Scriptaculous = \{/)
- end
+ attr_accessor :output_buffer
def test_escape_javascript
assert_equal '', escape_javascript(nil)
@@ -48,7 +42,7 @@ class JavaScriptHelperTest < ActionView::TestCase
end
def test_link_to_function_with_href
- assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
+ assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>),
link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/')
end
@@ -95,12 +89,14 @@ class JavaScriptHelperTest < ActionView::TestCase
javascript_tag("alert('hello')", :id => "the_js_tag")
end
- def test_javascript_tag_with_block
+ def test_javascript_tag_with_block_in_erb
+ __in_erb_template = ''
javascript_tag { concat "alert('hello')" }
assert_dom_equal "<script type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer
end
- def test_javascript_tag_with_block_and_options
+ def test_javascript_tag_with_block_and_options_in_erb
+ __in_erb_template = ''
javascript_tag(:id => "the_js_tag") { concat "alert('hello')" }
assert_dom_equal "<script id=\"the_js_tag\" type=\"text/javascript\">\n//<![CDATA[\nalert('hello')\n//]]>\n</script>", output_buffer
end
diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb
index a5be0d2789..5528430d80 100644
--- a/actionpack/test/template/prototype_helper_test.rb
+++ b/actionpack/test/template/prototype_helper_test.rb
@@ -25,7 +25,7 @@ class Author::Nested < Author; end
class PrototypeHelperBaseTest < ActionView::TestCase
- attr_accessor :template_format
+ attr_accessor :template_format, :output_buffer
def setup
@template = nil
@@ -54,7 +54,7 @@ class PrototypeHelperBaseTest < ActionView::TestCase
end
def create_generator
- block = Proc.new { |*args| yield *args if block_given? }
+ block = Proc.new { |*args| yield *args if block_given? }
JavaScriptGenerator.new self, &block
end
end
@@ -70,7 +70,7 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }}, { :class => "fine" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onComplete:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
- link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" })
+ link_to_remote("Remote outauthor", :complete => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onSuccess:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :success => "alert(request.responseText)", :url => { :action => "whatnot" })
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
@@ -78,12 +78,12 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_dom_equal %(<a href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot?a=10&amp;b=20', {asynchronous:true, evalScripts:true, onFailure:function(request){alert(request.responseText)}}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", :failure => "alert(request.responseText)", :url => { :action => "whatnot", :a => '10', :b => '20' })
end
-
+
def test_link_to_remote_html_options
assert_dom_equal %(<a class=\"fine\" href=\"#\" onclick=\"new Ajax.Request('http://www.example.com/whatnot', {asynchronous:true, evalScripts:true}); return false;\">Remote outauthor</a>),
link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } })
end
-
+
def test_link_to_remote_url_quote_escaping
assert_dom_equal %(<a href="#" onclick="new Ajax.Request('http://www.example.com/whatnot\\\'s', {asynchronous:true, evalScripts:true}); return false;">Remote</a>),
link_to_remote("Remote", { :url => { :action => "whatnot's" } })
@@ -93,14 +93,14 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>),
periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" })
end
-
+
def test_periodically_call_remote_with_frequency
assert_dom_equal(
"<script type=\"text/javascript\">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Request('http://www.example.com/', {asynchronous:true, evalScripts:true})}, 2)\n//]]>\n</script>",
periodically_call_remote(:frequency => 2)
)
end
-
+
def test_form_remote_tag
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast })
@@ -117,21 +117,22 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :html => { :method => :put })
end
- def test_form_remote_tag_with_block
+ def test_form_remote_tag_with_block_in_erb
+ __in_erb_template = ''
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }) { concat "Hello world!" }
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;\">Hello world!</form>), output_buffer
end
def test_remote_form_for_with_record_identification_with_new_record
remote_form_for(@record, {:html => { :id => 'create-author' }}) {}
-
+
expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' id='create-author' method='post'></form>)
assert_dom_equal expected, output_buffer
end
def test_remote_form_for_with_record_identification_without_html_options
remote_form_for(@record) {}
-
+
expected = %(<form action='#{authors_path}' onsubmit="new Ajax.Request('#{authors_path}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_author' method='post' id='new_author'></form>)
assert_dom_equal expected, output_buffer
end
@@ -139,23 +140,23 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
def test_remote_form_for_with_record_identification_with_existing_record
@record.save
remote_form_for(@record) {}
-
+
expected = %(<form action='#{author_path(@record)}' id='edit_author_1' method='post' onsubmit="new Ajax.Request('#{author_path(@record)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_author'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>)
assert_dom_equal expected, output_buffer
end
def test_remote_form_for_with_new_object_in_list
remote_form_for([@author, @article]) {}
-
+
expected = %(<form action='#{author_articles_path(@author)}' onsubmit="new Ajax.Request('#{author_articles_path(@author)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='new_article' method='post' id='new_article'></form>)
assert_dom_equal expected, output_buffer
end
-
+
def test_remote_form_for_with_existing_object_in_list
@author.save
@article.save
remote_form_for([@author, @article]) {}
-
+
expected = %(<form action='#{author_article_path(@author, @article)}' id='edit_article_1' method='post' onsubmit="new Ajax.Request('#{author_article_path(@author, @article)}', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;" class='edit_article'><div style='margin:0;padding:0'><input name='_method' type='hidden' value='put' /></div></form>)
assert_dom_equal expected, output_buffer
end
@@ -172,18 +173,18 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater({success:'glass_of_beer',failure:'glass_of_water'}, 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => { :success => "glass_of_beer", :failure => "glass_of_water" }, :url => { :action => :fast }, callback=>"monkeys();")
end
-
+
#HTTP status codes 200 up to 599 have callbacks
#these should work
100.upto(599) do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on#{callback.to_s.capitalize}:function(request){monkeys();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
-
+
#test 200 and 404
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, 200=>"monkeys();", 404=>"bananas();")
-
+
#these shouldn't
1.upto(99) do |callback|
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
@@ -193,44 +194,44 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, callback=>"monkeys();")
end
-
+
#test ultimate combo
assert_dom_equal %(<form action=\"http://www.example.com/fast\" method=\"post\" onsubmit=\"new Ajax.Updater('glass_of_beer', 'http://www.example.com/fast', {asynchronous:true, evalScripts:true, on200:function(request){monkeys();}, on404:function(request){bananas();}, onComplete:function(request){c();}, onFailure:function(request){f();}, onLoading:function(request){c1()}, onSuccess:function(request){s()}, parameters:Form.serialize(this)}); return false;\">),
form_remote_tag(:update => "glass_of_beer", :url => { :action => :fast }, :loading => "c1()", :success => "s()", :failure => "f();", :complete => "c();", 200=>"monkeys();", 404=>"bananas();")
-
+
end
-
- def test_submit_to_remote
+
+ def test_button_to_remote
assert_dom_equal %(<input name=\"More beer!\" onclick=\"new Ajax.Updater('empty_bottle', 'http://www.example.com/', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); return false;\" type=\"button\" value=\"1000000\" />),
- submit_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
+ button_to_remote("More beer!", 1_000_000, :update => "empty_bottle")
end
-
+
def test_observe_field
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/reorder_if_empty', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
observe_field("glass", :frequency => 5.minutes, :url => { :action => "reorder_if_empty" })
end
-
+
def test_observe_field_using_with_option
expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(value)})})\n//]]>\n</script>)
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => 'id')
assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "'id=' + encodeURIComponent(value)")
end
-
+
def test_observe_field_using_json_in_with_option
expected = %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {new Ajax.Request('http://www.example.com/check_value', {asynchronous:true, evalScripts:true, parameters:{'id':value}})})\n//]]>\n</script>)
- assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}")
+ assert_dom_equal expected, observe_field("glass", :frequency => 5.minutes, :url => { :action => "check_value" }, :with => "{'id':value}")
end
-
+
def test_observe_field_using_function_for_callback
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Element.Observer('glass', 300, function(element, value) {alert('Element changed')})\n//]]>\n</script>),
observe_field("glass", :frequency => 5.minutes, :function => "alert('Element changed')")
end
-
+
def test_observe_form
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {new Ajax.Request('http://www.example.com/cart_changed', {asynchronous:true, evalScripts:true, parameters:value})})\n//]]>\n</script>),
observe_form("cart", :frequency => 2, :url => { :action => "cart_changed" })
end
-
+
def test_observe_form_using_function_for_callback
assert_dom_equal %(<script type=\"text/javascript\">\n//<![CDATA[\nnew Form.Observer('cart', 2, function(element, value) {alert('Form changed')})\n//]]>\n</script>),
observe_form("cart", :frequency => 2, :function => "alert('Form changed')")
@@ -245,7 +246,7 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
block = Proc.new { |page| page.replace_html('foo', 'bar') }
assert_equal create_generator(&block).to_s, update_page(&block)
end
-
+
def test_update_page_tag
block = Proc.new { |page| page.replace_html('foo', 'bar') }
assert_equal javascript_tag(create_generator(&block).to_s), update_page_tag(&block)
@@ -261,15 +262,15 @@ class PrototypeHelperTest < PrototypeHelperBaseTest
def author_path(record)
"/authors/#{record.id}"
end
-
+
def authors_path
"/authors"
end
-
+
def author_articles_path(author)
"/authors/#{author.id}/articles"
end
-
+
def author_article_path(author, article)
"/authors/#{author.id}/articles/#{article.id}"
end
@@ -280,7 +281,7 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
super
@generator = create_generator
end
-
+
def test_insert_html_with_string
assert_equal 'new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
@@ -291,56 +292,56 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
assert_equal 'new Insertion.After("element", "\\u003Cp\u003EThis is a test\\u003C/p\u003E");',
@generator.insert_html(:after, 'element', '<p>This is a test</p>')
end
-
+
def test_replace_html_with_string
assert_equal 'Element.update("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");',
@generator.replace_html('element', '<p>This is a test</p>')
end
-
+
def test_replace_element_with_string
assert_equal 'Element.replace("element", "\\u003Cdiv id=\"element\"\\u003E\\u003Cp\\u003EThis is a test\\u003C/p\\u003E\\u003C/div\\u003E");',
@generator.replace('element', '<div id="element"><p>This is a test</p></div>')
end
-
+
def test_remove
assert_equal 'Element.remove("foo");',
@generator.remove('foo')
assert_equal '["foo", "bar", "baz"].each(Element.remove);',
@generator.remove('foo', 'bar', 'baz')
end
-
+
def test_show
assert_equal 'Element.show("foo");',
@generator.show('foo')
assert_equal '["foo", "bar", "baz"].each(Element.show);',
- @generator.show('foo', 'bar', 'baz')
+ @generator.show('foo', 'bar', 'baz')
end
-
+
def test_hide
assert_equal 'Element.hide("foo");',
@generator.hide('foo')
assert_equal '["foo", "bar", "baz"].each(Element.hide);',
- @generator.hide('foo', 'bar', 'baz')
+ @generator.hide('foo', 'bar', 'baz')
end
-
+
def test_toggle
assert_equal 'Element.toggle("foo");',
@generator.toggle('foo')
assert_equal '["foo", "bar", "baz"].each(Element.toggle);',
- @generator.toggle('foo', 'bar', 'baz')
+ @generator.toggle('foo', 'bar', 'baz')
end
-
+
def test_alert
assert_equal 'alert("hello");', @generator.alert('hello')
end
-
+
def test_redirect_to
assert_equal 'window.location.href = "http://www.example.com/welcome";',
@generator.redirect_to(:action => 'welcome')
assert_equal 'window.location.href = "http://www.example.com/welcome?a=b&c=d";',
@generator.redirect_to("http://www.example.com/welcome?a=b&c=d")
end
-
+
def test_reload
assert_equal 'window.location.reload();',
@generator.reload
@@ -350,16 +351,16 @@ class JavaScriptGeneratorTest < PrototypeHelperBaseTest
@generator.delay(20) do
@generator.hide('foo')
end
-
+
assert_equal "setTimeout(function() {\n;\nElement.hide(\"foo\");\n}, 20000);", @generator.to_s
end
-
+
def test_to_s
@generator.insert_html(:top, 'element', '<p>This is a test</p>')
@generator.insert_html(:bottom, 'element', '<p>This is a test</p>')
@generator.remove('foo', 'bar')
@generator.replace_html('baz', '<p>This is a test</p>')
-
+
assert_equal <<-EOS.chomp, @generator.to_s
new Insertion.Top("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
new Insertion.Bottom("element", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
@@ -381,12 +382,12 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
@generator['hello'].hide
assert_equal %($("hello").hide();), @generator.to_s
end
-
+
def test_element_proxy_variable_access
@generator['hello']['style']
assert_equal %($("hello").style;), @generator.to_s
end
-
+
def test_element_proxy_variable_access_with_assignment
@generator['hello']['style']['color'] = 'red'
assert_equal %($("hello").style.color = "red";), @generator.to_s
@@ -401,7 +402,7 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
@generator['hello'].hide("first").clean_whitespace
assert_equal %($("hello").hide("first").cleanWhitespace();), @generator.to_s
end
-
+
def test_select_access
assert_equal %($$("div.hello");), @generator.select('div.hello')
end
@@ -410,29 +411,29 @@ Element.update("baz", "\\u003Cp\\u003EThis is a test\\u003C/p\\u003E");
@generator.select('p.welcome b').first.hide
assert_equal %($$("p.welcome b").first().hide();), @generator.to_s
end
-
+
def test_visual_effect
- assert_equal %(new Effect.Puff("blah",{});),
+ assert_equal %(new Effect.Puff("blah",{});),
@generator.visual_effect(:puff,'blah')
- end
-
+ end
+
def test_visual_effect_toggle
- assert_equal %(Effect.toggle("blah",'appear',{});),
+ assert_equal %(Effect.toggle("blah",'appear',{});),
@generator.visual_effect(:toggle_appear,'blah')
end
-
+
def test_sortable
- assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
+ assert_equal %(Sortable.create("blah", {onUpdate:function(){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:Sortable.serialize("blah")})}});),
@generator.sortable('blah', :url => { :action => "order" })
end
-
+
def test_draggable
- assert_equal %(new Draggable("blah", {});),
+ assert_equal %(new Draggable("blah", {});),
@generator.draggable('blah')
end
-
+
def test_drop_receiving
- assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
+ assert_equal %(Droppables.add("blah", {onDrop:function(element){new Ajax.Request('http://www.example.com/order', {asynchronous:true, evalScripts:true, parameters:'id=' + encodeURIComponent(element.id)})}});),
@generator.drop_receiving('blah', :url => { :action => "order" })
end
@@ -534,7 +535,7 @@ return array.reverse();
});
EOS
end
-
+
def test_collection_proxy_with_find_all
@generator.select('p').find_all 'a' do |value, index|
@generator << '(value.className == "welcome")'
@@ -546,7 +547,7 @@ return (value.className == "welcome");
});
EOS
end
-
+
def test_collection_proxy_with_in_groups_of
@generator.select('p').in_groups_of('a', 3)
@generator.select('p').in_groups_of('a', 3, 'x')
@@ -555,13 +556,13 @@ var a = $$("p").inGroupsOf(3);
var a = $$("p").inGroupsOf(3, "x");
EOS
end
-
+
def test_collection_proxy_with_each_slice
@generator.select('p').each_slice('a', 3)
@generator.select('p').each_slice('a', 3) do |group, index|
group.reverse
end
-
+
assert_equal <<-EOS.strip, @generator.to_s
var a = $$("p").eachSlice(3);
var a = $$("p").eachSlice(3, function(value, index) {
@@ -569,7 +570,7 @@ return value.reverse();
});
EOS
end
-
+
def test_debug_rjs
ActionView::Base.debug_rjs = true
@generator['welcome'].replace_html 'Welcome'
@@ -577,7 +578,7 @@ return value.reverse();
ensure
ActionView::Base.debug_rjs = false
end
-
+
def test_literal
literal = @generator.literal("function() {}")
assert_equal "function() {}", literal.to_json
@@ -588,7 +589,7 @@ return value.reverse();
@generator.form.focus('my_field')
assert_equal "Form.focus(\"my_field\");", @generator.to_s
end
-
+
def test_call_with_block
@generator.call(:before)
@generator.call(:my_method) do |p|
@@ -601,7 +602,7 @@ return value.reverse();
end
assert_equal "before();\nmy_method(function() { $(\"one\").show();\n$(\"two\").hide(); });\nin_between();\nmy_method_with_arguments(true, \"hello\", function() { $(\"three\").visualEffect(\"highlight\"); });", @generator.to_s
end
-
+
def test_class_proxy_call_with_block
@generator.my_object.my_method do |p|
p[:one].show
diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb
index 441dc6b720..34a200b933 100644
--- a/actionpack/test/template/record_tag_helper_test.rb
+++ b/actionpack/test/template/record_tag_helper_test.rb
@@ -2,7 +2,7 @@ require 'abstract_unit'
class Post
def id
- 45
+ 45
end
def body
"What a wonderful world!"
@@ -15,35 +15,36 @@ class RecordTagHelperTest < ActionView::TestCase
def setup
@post = Post.new
end
-
+
def test_content_tag_for
expected = %(<li class="post bar" id="post_45"></li>)
actual = content_tag_for(:li, @post, :class => 'bar') { }
assert_dom_equal expected, actual
end
-
+
def test_content_tag_for_prefix
expected = %(<ul class="post" id="archived_post_45"></ul>)
actual = content_tag_for(:ul, @post, :archived) { }
- assert_dom_equal expected, actual
+ assert_dom_equal expected, actual
end
-
+
def test_content_tag_for_with_extra_html_tags
expected = %(<tr class="post bar" id="post_45" style='background-color: #f0f0f0'></tr>)
actual = content_tag_for(:tr, @post, {:class => "bar", :style => "background-color: #f0f0f0"}) { }
- assert_dom_equal expected, actual
+ assert_dom_equal expected, actual
end
-
- def test_block_works_with_content_tag_for
+
+ def test_block_works_with_content_tag_for_in_erb
+ __in_erb_template = ''
expected = %(<tr class="post" id="post_45">#{@post.body}</tr>)
actual = content_tag_for(:tr, @post) { concat @post.body }
- assert_dom_equal expected, actual
+ assert_dom_equal expected, actual
end
-
- def test_div_for
+
+ def test_div_for_in_erb
+ __in_erb_template = ''
expected = %(<div class="post bar" id="post_45">#{@post.body}</div>)
actual = div_for(@post, :class => "bar") { concat @post.body }
assert_dom_equal expected, actual
- end
-
+ end
end
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
new file mode 100644
index 0000000000..cc5b4900dc
--- /dev/null
+++ b/actionpack/test/template/render_test.rb
@@ -0,0 +1,131 @@
+require 'abstract_unit'
+require 'controller/fake_models'
+
+class ViewRenderTest < Test::Unit::TestCase
+ def setup
+ @assigns = { :secret => 'in the sauce' }
+ @view = ActionView::Base.new(ActionController::Base.view_paths, @assigns)
+ end
+
+ def test_render_file
+ assert_equal "Hello world!", @view.render("test/hello_world.erb")
+ end
+
+ def test_render_file_not_using_full_path
+ assert_equal "Hello world!", @view.render(:file => "test/hello_world.erb")
+ end
+
+ def test_render_file_without_specific_extension
+ assert_equal "Hello world!", @view.render("test/hello_world")
+ end
+
+ def test_render_file_with_full_path
+ template_path = File.join(File.dirname(__FILE__), '../fixtures/test/hello_world.erb')
+ assert_equal "Hello world!", @view.render(:file => template_path)
+ end
+
+ def test_render_file_with_instance_variables
+ assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_ivar.erb")
+ end
+
+ def test_render_file_with_locals
+ locals = { :secret => 'in the sauce' }
+ assert_equal "The secret is in the sauce\n", @view.render("test/render_file_with_locals.erb", locals)
+ end
+
+ def test_render_file_not_using_full_path_with_dot_in_path
+ assert_equal "The secret is in the sauce\n", @view.render("test/dot.directory/render_file_with_ivar")
+ end
+
+ def test_render_update
+ # TODO: You should not have to stub out template because template is self!
+ @view.instance_variable_set(:@template, @view)
+ assert_equal 'alert("Hello, World!");', @view.render(:update) { |page| page.alert('Hello, World!') }
+ end
+
+ def test_render_partial
+ assert_equal "only partial", @view.render(:partial => "test/partial_only")
+ end
+
+ def test_render_partial_with_errors
+ assert_raise(ActionView::TemplateError) { @view.render(:partial => "test/raise") }
+ end
+
+ def test_render_partial_collection
+ assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ])
+ end
+
+ def test_render_partial_collection_as
+ assert_equal "david david davidmary mary mary",
+ @view.render(:partial => "test/customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer)
+ end
+
+ def test_render_partial_collection_without_as
+ assert_equal "local_inspector,local_inspector_counter,object",
+ @view.render(:partial => "test/local_inspector", :collection => [ Customer.new("mary") ])
+ end
+
+ # TODO: The reason for this test is unclear, improve documentation
+ def test_render_partial_and_fallback_to_layout
+ assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
+ end
+
+ # TODO: The reason for this test is unclear, improve documentation
+ def test_render_js_partial_and_fallback_to_erb_layout
+ @view.template_format = :js
+ assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" })
+ end
+
+ # TODO: The reason for this test is unclear, improve documentation
+ def test_render_missing_xml_partial_and_raise_missing_template
+ @view.template_format = :xml
+ assert_raise(ActionView::MissingTemplate) { @view.render(:partial => "test/layout_for_partial") }
+ end
+
+ def test_render_inline
+ assert_equal "Hello, World!", @view.render(:inline => "Hello, World!")
+ end
+
+ def test_render_inline_with_locals
+ assert_equal "Hello, Josh!", @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" })
+ end
+
+ def test_render_fallbacks_to_erb_for_unknown_types
+ assert_equal "Hello, World!", @view.render(:inline => "Hello, World!", :type => :foo)
+ end
+
+ class CustomHandler < ActionView::TemplateHandler
+ def render(template, local_assigns)
+ [template.source, local_assigns].inspect
+ end
+ end
+
+ def test_render_inline_with_custom_type
+ ActionView::Template.register_template_handler :foo, CustomHandler
+ assert_equal '["Hello, World!", {}]', @view.render(:inline => "Hello, World!", :type => :foo)
+ end
+
+ def test_render_inline_with_locals_and_custom_type
+ ActionView::Template.register_template_handler :foo, CustomHandler
+ assert_equal '["Hello, <%= name %>!", {:name=>"Josh"}]', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
+ end
+
+ class CompilableCustomHandler < ActionView::TemplateHandler
+ include ActionView::TemplateHandlers::Compilable
+
+ def compile(template)
+ "@output_buffer = ''\n" +
+ "@output_buffer << 'source: #{template.source.inspect}'\n"
+ end
+ end
+
+ def test_render_inline_with_compilable_custom_type
+ ActionView::Template.register_template_handler :foo, CompilableCustomHandler
+ assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo)
+ end
+
+ def test_render_inline_with_locals_and_compilable_custom_type
+ ActionView::Template.register_template_handler :foo, CompilableCustomHandler
+ assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo)
+ end
+end
diff --git a/actionpack/test/template/tag_helper_test.rb b/actionpack/test/template/tag_helper_test.rb
index 2941dfe217..fc49d340ef 100644
--- a/actionpack/test/template/tag_helper_test.rb
+++ b/actionpack/test/template/tag_helper_test.rb
@@ -33,23 +33,40 @@ class TagHelperTest < ActionView::TestCase
assert_equal content_tag("a", "Create", "href" => "create"),
content_tag("a", "Create", :href => "create")
end
-
- def test_content_tag_with_block
+
+ def test_content_tag_with_block_in_erb
+ __in_erb_template = ''
content_tag(:div) { concat "Hello world!" }
assert_dom_equal "<div>Hello world!</div>", output_buffer
end
-
- def test_content_tag_with_block_and_options
+
+ def test_content_tag_with_block_and_options_in_erb
+ __in_erb_template = ''
content_tag(:div, :class => "green") { concat "Hello world!" }
assert_dom_equal %(<div class="green">Hello world!</div>), output_buffer
end
-
- def test_content_tag_with_block_and_options_outside_of_action_view
- self.output_buffer = nil
+
+ def test_content_tag_with_block_and_options_out_of_erb
+ assert_dom_equal %(<div class="green">Hello world!</div>), content_tag(:div, :class => "green") { "Hello world!" }
+ end
+
+ def test_content_tag_with_block_and_options_outside_out_of_erb
assert_equal content_tag("a", "Create", :href => "create"),
- content_tag("a", "href" => "create") { "Create" }
+ content_tag("a", "href" => "create") { "Create" }
end
-
+
+ def test_content_tag_nested_in_content_tag_out_of_erb
+ assert_equal content_tag("p", content_tag("b", "Hello")),
+ content_tag("p") { content_tag("b", "Hello") },
+ output_buffer
+ end
+
+ def test_content_tag_nested_in_content_tag_in_erb
+ __in_erb_template = true
+ content_tag("p") { concat content_tag("b", "Hello") }
+ assert_equal '<p><b>Hello</b></p>', output_buffer
+ end
+
def test_cdata_section
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
diff --git a/actionpack/test/template/template_file_test.rb b/actionpack/test/template/template_file_test.rb
deleted file mode 100644
index d14a966c1c..0000000000
--- a/actionpack/test/template/template_file_test.rb
+++ /dev/null
@@ -1,95 +0,0 @@
-require 'abstract_unit'
-
-class TemplateFileTest < Test::Unit::TestCase
- LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures')
-
- def setup
- @template = ActionView::TemplateFile.new("test/hello_world.html.erb")
- @another_template = ActionView::TemplateFile.new("test/hello_world.erb")
- @file_only = ActionView::TemplateFile.new("hello_world.erb")
- @full_path = ActionView::TemplateFile.new("/u/app/scales/config/../app/views/test/hello_world.erb", true)
- @layout = ActionView::TemplateFile.new("layouts/hello")
- @multipart = ActionView::TemplateFile.new("test_mailer/implicitly_multipart_example.text.html.erb")
- end
-
- def test_path
- assert_equal "test/hello_world.html.erb", @template.path
- assert_equal "test/hello_world.erb", @another_template.path
- assert_equal "hello_world.erb", @file_only.path
- assert_equal "/u/app/scales/config/../app/views/test/hello_world.erb", @full_path.path
- assert_equal "layouts/hello", @layout.path
- assert_equal "test_mailer/implicitly_multipart_example.text.html.erb", @multipart.path
- end
-
- def test_path_without_extension
- assert_equal "test/hello_world.html", @template.path_without_extension
- assert_equal "test/hello_world", @another_template.path_without_extension
- assert_equal "hello_world", @file_only.path_without_extension
- assert_equal "layouts/hello", @layout.path_without_extension
- assert_equal "test_mailer/implicitly_multipart_example.text.html", @multipart.path_without_extension
- end
-
- def test_path_without_format_and_extension
- assert_equal "test/hello_world", @template.path_without_format_and_extension
- assert_equal "test/hello_world", @another_template.path_without_format_and_extension
- assert_equal "hello_world", @file_only.path_without_format_and_extension
- assert_equal "layouts/hello", @layout.path_without_format_and_extension
- assert_equal "test_mailer/implicitly_multipart_example", @multipart.path_without_format_and_extension
- end
-
- def test_name
- assert_equal "hello_world", @template.name
- assert_equal "hello_world", @another_template.name
- assert_equal "hello_world", @file_only.name
- assert_equal "hello_world", @full_path.name
- assert_equal "hello", @layout.name
- assert_equal "implicitly_multipart_example", @multipart.name
- end
-
- def test_format
- assert_equal "html", @template.format
- assert_equal nil, @another_template.format
- assert_equal nil, @layout.format
- assert_equal "text.html", @multipart.format
- end
-
- def test_extension
- assert_equal "erb", @template.extension
- assert_equal "erb", @another_template.extension
- assert_equal nil, @layout.extension
- assert_equal "erb", @multipart.extension
- end
-
- def test_format_and_extension
- assert_equal "html.erb", @template.format_and_extension
- assert_equal "erb", @another_template.format_and_extension
- assert_equal nil, @layout.format_and_extension
- assert_equal "text.html.erb", @multipart.format_and_extension
- end
-
- def test_new_file_with_extension
- file = @template.dup_with_extension(:haml)
- assert_equal "test/hello_world.html", file.path_without_extension
- assert_equal "haml", file.extension
- assert_equal "test/hello_world.html.haml", file.path
-
- file = @another_template.dup_with_extension(:haml)
- assert_equal "test/hello_world", file.path_without_extension
- assert_equal "haml", file.extension
- assert_equal "test/hello_world.haml", file.path
-
- file = @another_template.dup_with_extension(nil)
- assert_equal "test/hello_world", file.path_without_extension
- assert_equal nil, file.extension
- assert_equal "test/hello_world", file.path
- end
-
- def test_freezes_entire_contents
- @template.freeze
- assert @template.frozen?
- assert @template.base_path.frozen?
- assert @template.name.frozen?
- assert @template.format.frozen?
- assert @template.extension.frozen?
- end
-end
diff --git a/actionpack/test/template/template_object_test.rb b/actionpack/test/template/template_object_test.rb
deleted file mode 100644
index 2cfc4523c6..0000000000
--- a/actionpack/test/template/template_object_test.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-require 'abstract_unit'
-
-class TemplateObjectTest < Test::Unit::TestCase
- LOAD_PATH_ROOT = File.join(File.dirname(__FILE__), '..', 'fixtures')
-
- class TemplateTest < Test::Unit::TestCase
- def setup
- @view = ActionView::Base.new(LOAD_PATH_ROOT)
- @path = "test/hello_world.erb"
- end
-
- def test_should_create_valid_template
- template = ActionView::Template.new(@view, @path, true)
-
- assert_kind_of ActionView::TemplateHandlers::ERB, template.handler
- assert_equal "test/hello_world.erb", template.path.to_s
- assert_nil template.instance_variable_get(:"@source")
- assert_equal "erb", template.extension
- end
-
- uses_mocha 'Template preparation tests' do
- def test_should_prepare_template_properly
- template = ActionView::Template.new(@view, @path, true)
- view = template.instance_variable_get(:"@view")
-
- view.expects(:evaluate_assigns)
- template.handler.expects(:compile_template).with(template)
- view.expects(:method_names).returns({})
-
- template.prepare!
- end
- end
- end
-
- class PartialTemplateTest < Test::Unit::TestCase
- def setup
- @view = ActionView::Base.new(LOAD_PATH_ROOT)
- @path = "test/partial_only"
- end
-
- def test_should_create_valid_partial_template
- template = ActionView::PartialTemplate.new(@view, @path, nil)
-
- assert_equal "test/_partial_only", template.path.path_without_format_and_extension
- assert_equal :partial_only, template.variable_name
-
- assert template.locals.has_key?(:object)
- assert template.locals.has_key?(:partial_only)
- end
-
- def test_partial_with_errors
- template = ActionView::PartialTemplate.new(@view, 'test/raise', nil)
- assert_raise(ActionView::TemplateError) { template.render_template }
- end
-
- uses_mocha 'Partial template preparation tests' do
- def test_should_prepare_on_initialization
- ActionView::PartialTemplate.any_instance.expects(:prepare!)
- template = ActionView::PartialTemplate.new(@view, @path, 1)
- end
- end
- end
-
- class PartialTemplateFallbackTest < Test::Unit::TestCase
- def setup
- @view = ActionView::Base.new(LOAD_PATH_ROOT)
- @path = 'test/layout_for_partial'
- end
-
- def test_default
- template = ActionView::PartialTemplate.new(@view, @path, nil)
- assert_equal 'test/_layout_for_partial', template.path.path_without_format_and_extension
- assert_equal 'erb', template.extension
- assert_equal :html, @view.template_format
- end
-
- def test_js
- @view.template_format = :js
- template = ActionView::PartialTemplate.new(@view, @path, nil)
- assert_equal 'test/_layout_for_partial', template.path.path_without_format_and_extension
- assert_equal 'erb', template.extension
- assert_equal :html, @view.template_format
- end
-
- def test_xml
- @view.template_format = :xml
- assert_raise ActionView::MissingTemplate do
- ActionView::PartialTemplate.new(@view, @path, nil)
- end
- end
- end
-end
diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb
index cbb5c7ee74..4999525939 100644
--- a/actionpack/test/template/text_helper_test.rb
+++ b/actionpack/test/template/text_helper_test.rb
@@ -13,9 +13,7 @@ class TextHelperTest < ActionView::TestCase
def test_concat
self.output_buffer = 'foo'
- concat 'bar'
- assert_equal 'foobar', output_buffer
- assert_nothing_raised { concat nil }
+ assert_equal 'foobar', concat('bar')
assert_equal 'foobar', output_buffer
end
@@ -195,6 +193,7 @@ class TextHelperTest < ActionView::TestCase
http://www.mail-archive.com/rails@lists.rubyonrails.org/
http://www.amazon.com/Testing-Equal-Sign-In-Path/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1198861734&sr=8-1
http://en.wikipedia.org/wiki/Sprite_(computer_graphics)
+ http://en.wikipedia.org/wiki/Texas_hold'em
)
urls.each do |url|
diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb
index 0713cea8ac..91d5c6ffb5 100644
--- a/actionpack/test/template/url_helper_test.rb
+++ b/actionpack/test/template/url_helper_test.rb
@@ -80,7 +80,7 @@ class UrlHelperTest < ActionView::TestCase
def test_link_tag_with_straight_url
assert_dom_equal "<a href=\"http://www.example.com\">Hello</a>", link_to("Hello", "http://www.example.com")
end
-
+
def test_link_tag_without_host_option
ActionController::Base.class_eval { attr_accessor :url }
url = {:controller => 'weblog', :action => 'show'}
@@ -121,7 +121,7 @@ class UrlHelperTest < ActionView::TestCase
@controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'})
assert_dom_equal "<a href=\"http://www.example.com/referer\">go back</a>", link_to('go back', :back)
end
-
+
def test_link_tag_with_back_and_no_referer
@controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {})
assert_dom_equal "<a href=\"javascript:history.back()\">go back</a>", link_to('go back', :back)
@@ -212,14 +212,14 @@ class UrlHelperTest < ActionView::TestCase
assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
end
- def test_link_tag_using_block
- self.output_buffer = ''
+ def test_link_tag_using_block_in_erb
+ __in_erb_template = ''
link_to("http://example.com") { concat("Example site") }
assert_equal '<a href="http://example.com">Example site</a>', output_buffer
end
-
+
def test_link_to_unless
assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog")
assert_dom_equal "<a href=\"http://www.example.com\">Listing</a>", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")
@@ -292,8 +292,9 @@ class UrlHelperTest < ActionView::TestCase
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">My email</a>", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
assert_dom_equal "<a href=\"&#109;&#97;&#105;&#108;&#116;&#111;&#58;%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d\">&#109;&#101;&#40;&#97;&#116;&#41;&#100;&#111;&#109;&#97;&#105;&#110;&#40;&#100;&#111;&#116;&#41;&#99;&#111;&#109;</a>", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "<script type=\"text/javascript\">eval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
end
-
+
def protect_against_forgery?
false
end
@@ -301,8 +302,6 @@ end
class UrlHelperWithControllerTest < ActionView::TestCase
class UrlHelperController < ActionController::Base
- self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ]
-
def self.controller_path; 'url_helper_with_controller' end
def show_url_for
@@ -313,6 +312,10 @@ class UrlHelperWithControllerTest < ActionView::TestCase
render :inline => "<%= show_named_route_#{params[:kind]} %>"
end
+ def nil_url_for
+ render :inline => '<%= url_for(nil) %>'
+ end
+
def rescue_action(e) raise e end
end
@@ -329,7 +332,7 @@ class UrlHelperWithControllerTest < ActionView::TestCase
assert_equal '/url_helper_with_controller/show_url_for', @response.body
end
- def test_named_route_shows_host_and_path
+ def test_named_route_url_shows_host_and_path
with_url_helper_routing do
get :show_named_route, :kind => 'url'
assert_equal 'http://test.host/url_helper_with_controller/show_named_route', @response.body
@@ -343,6 +346,11 @@ class UrlHelperWithControllerTest < ActionView::TestCase
end
end
+ def test_url_for_nil_returns_current_path
+ get :nil_url_for
+ assert_equal '/url_helper_with_controller/nil_url_for', @response.body
+ end
+
protected
def with_url_helper_routing
with_routing do |set|
@@ -356,8 +364,6 @@ end
class LinkToUnlessCurrentWithControllerTest < ActionView::TestCase
class TasksController < ActionController::Base
- self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
-
def self.controller_path; 'tasks' end
def index
@@ -420,11 +426,11 @@ class Workshop
def initialize(id, new_record)
@id, @new_record = id, new_record
end
-
+
def new_record?
@new_record
end
-
+
def to_s
id.to_s
end
@@ -448,8 +454,6 @@ end
class PolymorphicControllerTest < ActionView::TestCase
class WorkshopsController < ActionController::Base
- self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
-
def self.controller_path; 'workshops' end
def index
@@ -466,8 +470,6 @@ class PolymorphicControllerTest < ActionView::TestCase
end
class SessionsController < ActionController::Base
- self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
-
def self.controller_path; 'sessions' end
def index
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 87e9b547f3..4b60f8d682 100644
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -1,16 +1,24 @@
#!/usr/bin/env ruby
-$LOAD_PATH << File.join(File.dirname(__FILE__), 'vendor', 'rspec', 'lib')
require 'rake'
-require 'spec/rake/spectask'
+require 'rake/testtask'
require 'rake/rdoctask'
+task :default => :test
+
+Rake::TestTask.new do |t|
+ t.libs << "test"
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+ t.warning = true
+end
+
# Generate the RDoc documentation
-Rake::RDocTask.new { |rdoc|
+Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Active Model"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGES')
rdoc.rdoc_files.include('lib/**/*.rb')
-} \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 369c7fed33..4ed7b0889d 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,17 +1,5 @@
-$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', 'activesupport', 'lib')
-
-# premature optimization?
-require 'active_support/inflector'
-require 'active_support/core_ext/string/inflections'
-String.send :include, ActiveSupport::CoreExtensions::String::Inflections
-
-require 'active_model/base'
require 'active_model/observing'
-require 'active_model/callbacks'
-require 'active_model/validations'
-
-ActiveModel::Base.class_eval do
- include ActiveModel::Observing
- include ActiveModel::Callbacks
- include ActiveModel::Validations
-end \ No newline at end of file
+# disabled until they're tested
+# require 'active_model/callbacks'
+# require 'active_model/validations'
+require 'active_model/base' \ No newline at end of file
diff --git a/activemodel/lib/active_model/base.rb b/activemodel/lib/active_model/base.rb
index 1141156da4..a500adfdf1 100644
--- a/activemodel/lib/active_model/base.rb
+++ b/activemodel/lib/active_model/base.rb
@@ -1,4 +1,8 @@
module ActiveModel
class Base
+ include Observing
+ # disabled, until they're tested
+ # include Callbacks
+ # include Validations
end
end \ No newline at end of file
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 0114fc386b..c94f76109f 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,3 +1,5 @@
+require 'active_model/core'
+
module ActiveModel
module Callbacks
diff --git a/activemodel/lib/active_model/core.rb b/activemodel/lib/active_model/core.rb
new file mode 100644
index 0000000000..47b968e121
--- /dev/null
+++ b/activemodel/lib/active_model/core.rb
@@ -0,0 +1,7 @@
+# This file is required by each major ActiveModel component for the core requirements. This allows you to
+# load individual pieces of ActiveModel as needed.
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', '..', '..', 'activesupport', 'lib')
+
+# premature optimization?
+# So far, we only need the string inflections and not the rest of ActiveSupport.
+require 'active_support/inflector' \ No newline at end of file
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index db758f5185..9e99d7472c 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,4 +1,6 @@
require 'observer'
+require 'singleton'
+require 'active_model/core'
module ActiveModel
module Observing
@@ -73,7 +75,7 @@ module ActiveModel
# Start observing the declared classes and their subclasses.
def initialize
self.observed_classes = self.class.models if self.class.models
- observed_classes.each { |klass| add_observer! klass }
+ observed_classes.each { |klass| klass.add_observer(self) }
end
# Send observed_method(object) if the method exists.
@@ -85,16 +87,12 @@ module ActiveModel
# Passes the new subclass.
def observed_class_inherited(subclass) #:nodoc:
self.class.observe(observed_classes + [subclass])
- add_observer!(subclass)
+ subclass.add_observer(self)
end
- protected
- def observed_classes
- @observed_classes ||= [self.class.observed_class]
- end
-
- def add_observer!(klass)
- klass.add_observer(self)
- end
+ protected
+ def observed_classes
+ @observed_classes ||= [self.class.observed_class]
+ end
end
end \ No newline at end of file
diff --git a/activemodel/lib/active_model/state_machine.rb b/activemodel/lib/active_model/state_machine.rb
new file mode 100644
index 0000000000..96df6539ae
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine.rb
@@ -0,0 +1,66 @@
+Dir[File.dirname(__FILE__) + "/state_machine/*.rb"].sort.each do |path|
+ filename = File.basename(path)
+ require "active_model/state_machine/#{filename}"
+end
+
+module ActiveModel
+ module StateMachine
+ class InvalidTransition < Exception
+ end
+
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ def inherited(klass)
+ super
+ klass.state_machines = state_machines
+ end
+
+ def state_machines
+ @state_machines ||= {}
+ end
+
+ def state_machines=(value)
+ @state_machines = value ? value.dup : nil
+ end
+
+ def state_machine(name = nil, options = {}, &block)
+ if name.is_a?(Hash)
+ options = name
+ name = nil
+ end
+ name ||= :default
+ state_machines[name] ||= Machine.new(self, name)
+ block ? state_machines[name].update(options, &block) : state_machines[name]
+ end
+ end
+
+ def current_state(name = nil, new_state = nil, persist = false)
+ sm = self.class.state_machine(name)
+ ivar = sm.current_state_variable
+ if name && new_state
+ if persist && respond_to?(:write_state)
+ write_state(sm, new_state)
+ end
+
+ if respond_to?(:write_state_without_persistence)
+ write_state_without_persistence(sm, new_state)
+ end
+
+ instance_variable_set(ivar, new_state)
+ else
+ instance_variable_set(ivar, nil) unless instance_variable_defined?(ivar)
+ value = instance_variable_get(ivar)
+ return value if value
+
+ if respond_to?(:read_state)
+ value = instance_variable_set(ivar, read_state(sm))
+ end
+
+ value || sm.initial_state
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/state_machine/event.rb b/activemodel/lib/active_model/state_machine/event.rb
new file mode 100644
index 0000000000..e8bc8ebdb7
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/event.rb
@@ -0,0 +1,62 @@
+module ActiveModel
+ module StateMachine
+ class Event
+ attr_reader :name, :success
+
+ def initialize(machine, name, options = {}, &block)
+ @machine, @name, @transitions = machine, name, []
+ if machine
+ machine.klass.send(:define_method, "#{name.to_s}!") do |*args|
+ machine.fire_event(name, self, true, *args)
+ end
+
+ machine.klass.send(:define_method, "#{name.to_s}") do |*args|
+ machine.fire_event(name, self, false, *args)
+ end
+ end
+ update(options, &block)
+ end
+
+ def fire(obj, to_state = nil, *args)
+ transitions = @transitions.select { |t| t.from == obj.current_state(@machine ? @machine.name : nil) }
+ raise InvalidTransition if transitions.size == 0
+
+ next_state = nil
+ transitions.each do |transition|
+ next if to_state && !Array(transition.to).include?(to_state)
+ if transition.perform(obj)
+ next_state = to_state || Array(transition.to).first
+ transition.execute(obj, *args)
+ break
+ end
+ end
+ next_state
+ end
+
+ def transitions_from_state?(state)
+ @transitions.any? { |t| t.from? state }
+ end
+
+ def ==(event)
+ if event.is_a? Symbol
+ name == event
+ else
+ name == event.name
+ end
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:success) then @success = options[:success] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ private
+ def transitions(trans_opts)
+ Array(trans_opts[:from]).each do |s|
+ @transitions << StateTransition.new(trans_opts.merge({:from => s.to_sym}))
+ end
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/state_machine/machine.rb b/activemodel/lib/active_model/state_machine/machine.rb
new file mode 100644
index 0000000000..170505c0b2
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/machine.rb
@@ -0,0 +1,74 @@
+module ActiveModel
+ module StateMachine
+ class Machine
+ attr_accessor :initial_state, :states, :events, :state_index
+ attr_reader :klass, :name
+
+ def initialize(klass, name, options = {}, &block)
+ @klass, @name, @states, @state_index, @events = klass, name, [], {}, {}
+ update(options, &block)
+ end
+
+ def initial_state
+ @initial_state ||= (states.first ? states.first.name : nil)
+ end
+
+ def update(options = {}, &block)
+ if options.key?(:initial) then @initial_state = options[:initial] end
+ if block then instance_eval(&block) end
+ self
+ end
+
+ def fire_event(event, record, persist, *args)
+ state_index[record.current_state(@name)].call_action(:exit, record)
+ if new_state = @events[event].fire(record, *args)
+ state_index[new_state].call_action(:enter, record)
+
+ if record.respond_to?(event_fired_callback)
+ record.send(event_fired_callback, record.current_state, new_state)
+ end
+
+ record.current_state(@name, new_state, persist)
+ record.send(@events[event].success) if @events[event].success
+ true
+ else
+ if record.respond_to?(event_failed_callback)
+ record.send(event_failed_callback, event)
+ end
+
+ false
+ end
+ end
+
+ def states_for_select
+ states.map { |st| [st.display_name, st.name.to_s] }
+ end
+
+ def events_for(state)
+ events = @events.values.select { |event| event.transitions_from_state?(state) }
+ events.map! { |event| event.name }
+ end
+
+ def current_state_variable
+ "@#{@name}_current_state"
+ end
+
+ private
+ def state(name, options = {})
+ @states << (state_index[name] ||= State.new(name, :machine => self)).update(options)
+ end
+
+ def event(name, options = {}, &block)
+ (@events[name] ||= Event.new(self, name)).update(options, &block)
+ end
+
+ def event_fired_callback
+ @event_fired_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_fired'
+ end
+
+ def event_failed_callback
+ @event_failed_callback ||= (@name == :default ? '' : "#{@name}_") + 'event_failed'
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/state_machine/state.rb b/activemodel/lib/active_model/state_machine/state.rb
new file mode 100644
index 0000000000..68eb2aa34a
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/state.rb
@@ -0,0 +1,50 @@
+module ActiveModel
+ module StateMachine
+ class State
+ attr_reader :name, :options
+
+ def initialize(name, options = {})
+ @name = name
+ machine = options.delete(:machine)
+ if machine
+ machine.klass.send(:define_method, "#{name}?") do
+ current_state.to_s == name.to_s
+ end
+ end
+ update(options)
+ end
+
+ def ==(state)
+ if state.is_a? Symbol
+ name == state
+ else
+ name == state.name
+ end
+ end
+
+ def call_action(action, record)
+ action = @options[action]
+ case action
+ when Symbol, String
+ record.send(action)
+ when Proc
+ action.call(record)
+ end
+ end
+
+ def display_name
+ @display_name ||= name.to_s.gsub(/_/, ' ').capitalize
+ end
+
+ def for_select
+ [display_name, name.to_s]
+ end
+
+ def update(options = {})
+ if options.key?(:display) then @display_name = options.delete(:display) end
+ @options = options
+ self
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/state_machine/state_transition.rb b/activemodel/lib/active_model/state_machine/state_transition.rb
new file mode 100644
index 0000000000..f9df998ea4
--- /dev/null
+++ b/activemodel/lib/active_model/state_machine/state_transition.rb
@@ -0,0 +1,40 @@
+module ActiveModel
+ module StateMachine
+ class StateTransition
+ attr_reader :from, :to, :options
+
+ def initialize(opts)
+ @from, @to, @guard, @on_transition = opts[:from], opts[:to], opts[:guard], opts[:on_transition]
+ @options = opts
+ end
+
+ def perform(obj)
+ case @guard
+ when Symbol, String
+ obj.send(@guard)
+ when Proc
+ @guard.call(obj)
+ else
+ true
+ end
+ end
+
+ def execute(obj, *args)
+ case @on_transition
+ when Symbol, String
+ obj.send(@on_transition, *args)
+ when Proc
+ @on_transition.call(obj, *args)
+ end
+ end
+
+ def ==(obj)
+ @from == obj.from && @to == obj.to
+ end
+
+ def from?(value)
+ @from == value
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 34ef3b8f6e..7efe9901ca 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,3 +1,5 @@
+require 'active_model/core'
+
module ActiveModel
module Validations
def self.included(base) # :nodoc:
diff --git a/activemodel/spec/observing_spec.rb b/activemodel/spec/observing_spec.rb
deleted file mode 100644
index 1919bb5991..0000000000
--- a/activemodel/spec/observing_spec.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-require File.join(File.dirname(__FILE__), 'spec_helper')
-
-class ObservedModel < ActiveModel::Base
- class Observer
- end
-end
-
-class FooObserver < ActiveModel::Observer
- class << self
- public :new
- end
-
- attr_accessor :stub
-
- def on_spec(record)
- stub.event_with(record) if stub
- end
-end
-
-class Foo < ActiveModel::Base
-end
-
-module ActiveModel
- describe Observing do
- before do
- ObservedModel.observers.clear
- end
-
- it "initializes model with no cached observers" do
- ObservedModel.observers.should be_empty
- end
-
- it "stores cached observers in an array" do
- ObservedModel.observers << :foo
- ObservedModel.observers.should include(:foo)
- end
-
- it "flattens array of assigned cached observers" do
- ObservedModel.observers = [[:foo], :bar]
- ObservedModel.observers.should include(:foo)
- ObservedModel.observers.should include(:bar)
- end
-
- it "instantiates observer names passed as strings" do
- ObservedModel.observers << 'foo_observer'
- FooObserver.should_receive(:instance)
- ObservedModel.instantiate_observers
- end
-
- it "instantiates observer names passed as symbols" do
- ObservedModel.observers << :foo_observer
- FooObserver.should_receive(:instance)
- ObservedModel.instantiate_observers
- end
-
- it "instantiates observer classes" do
- ObservedModel.observers << ObservedModel::Observer
- ObservedModel::Observer.should_receive(:instance)
- ObservedModel.instantiate_observers
- end
-
- it "should pass observers to subclasses" do
- FooObserver.instance
- bar = Class.new(Foo)
- bar.count_observers.should == 1
- end
- end
-
- describe Observer do
- before do
- ObservedModel.observers = :foo_observer
- FooObserver.models = nil
- end
-
- it "guesses implicit observable model name" do
- FooObserver.observed_class_name.should == 'Foo'
- end
-
- it "tracks implicit observable models" do
- instance = FooObserver.new
- instance.send(:observed_classes).should include(Foo)
- instance.send(:observed_classes).should_not include(ObservedModel)
- end
-
- it "tracks explicit observed model class" do
- FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
- FooObserver.observe ObservedModel
- instance = FooObserver.new
- instance.send(:observed_classes).should include(ObservedModel)
- end
-
- it "tracks explicit observed model as string" do
- FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
- FooObserver.observe 'observed_model'
- instance = FooObserver.new
- instance.send(:observed_classes).should include(ObservedModel)
- end
-
- it "tracks explicit observed model as symbol" do
- FooObserver.new.send(:observed_classes).should_not include(ObservedModel)
- FooObserver.observe :observed_model
- instance = FooObserver.new
- instance.send(:observed_classes).should include(ObservedModel)
- end
-
- it "calls existing observer event" do
- foo = Foo.new
- FooObserver.instance.stub = stub!(:stub)
- FooObserver.instance.stub.should_receive(:event_with).with(foo)
- Foo.send(:changed)
- Foo.send(:notify_observers, :on_spec, foo)
- end
-
- it "skips nonexistent observer event" do
- foo = Foo.new
- Foo.send(:changed)
- Foo.send(:notify_observers, :whatever, foo)
- end
- end
-end \ No newline at end of file
diff --git a/activemodel/spec/spec_helper.rb b/activemodel/spec/spec_helper.rb
deleted file mode 100644
index 004fdfca07..0000000000
--- a/activemodel/spec/spec_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-ENV['LOG_NAME'] = 'spec'
-$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'vendor', 'rspec', 'lib')
-$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
-require 'active_model'
-begin
- require 'spec'
-rescue LoadError
- require 'rubygems'
- require 'spec'
-end
-
-begin
- require 'ruby-debug'
- Debugger.start
-rescue LoadError
- # you do not know the ways of ruby-debug yet, what a shame
-end \ No newline at end of file
diff --git a/activemodel/test/observing_test.rb b/activemodel/test/observing_test.rb
new file mode 100644
index 0000000000..6e124de52f
--- /dev/null
+++ b/activemodel/test/observing_test.rb
@@ -0,0 +1,123 @@
+require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
+
+class ObservedModel < ActiveModel::Base
+ class Observer
+ end
+end
+
+class FooObserver < ActiveModel::Observer
+ class << self
+ public :new
+ end
+
+ attr_accessor :stub
+
+ def on_spec(record)
+ stub.event_with(record) if stub
+ end
+end
+
+class Foo < ActiveModel::Base
+end
+
+class ObservingTest < ActiveModel::TestCase
+ def setup
+ ObservedModel.observers.clear
+ end
+
+ test "initializes model with no cached observers" do
+ assert ObservedModel.observers.empty?, "Not empty: #{ObservedModel.observers.inspect}"
+ end
+
+ test "stores cached observers in an array" do
+ ObservedModel.observers << :foo
+ assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
+ end
+
+ test "flattens array of assigned cached observers" do
+ ObservedModel.observers = [[:foo], :bar]
+ assert ObservedModel.observers.include?(:foo), ":foo not in #{ObservedModel.observers.inspect}"
+ assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}"
+ end
+
+ uses_mocha "observer instantiation" do
+ test "instantiates observer names passed as strings" do
+ ObservedModel.observers << 'foo_observer'
+ FooObserver.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ test "instantiates observer names passed as symbols" do
+ ObservedModel.observers << :foo_observer
+ FooObserver.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+
+ test "instantiates observer classes" do
+ ObservedModel.observers << ObservedModel::Observer
+ ObservedModel::Observer.expects(:instance)
+ ObservedModel.instantiate_observers
+ end
+ end
+
+ test "passes observers to subclasses" do
+ FooObserver.instance
+ bar = Class.new(Foo)
+ assert_equal Foo.count_observers, bar.count_observers
+ end
+end
+
+class ObserverTest < ActiveModel::TestCase
+ def setup
+ ObservedModel.observers = :foo_observer
+ FooObserver.models = nil
+ end
+
+ test "guesses implicit observable model name" do
+ assert_equal 'Foo', FooObserver.observed_class_name
+ end
+
+ test "tracks implicit observable models" do
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
+ assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model class" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe ObservedModel
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model as string" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe 'observed_model'
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "tracks explicit observed model as symbol" do
+ old_instance = FooObserver.new
+ assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
+ FooObserver.observe :observed_model
+ instance = FooObserver.new
+ assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ end
+
+ test "calls existing observer event" do
+ foo = Foo.new
+ FooObserver.instance.stub = stub
+ FooObserver.instance.stub.expects(:event_with).with(foo)
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :on_spec, foo)
+ end
+
+ test "skips nonexistent observer event" do
+ foo = Foo.new
+ Foo.send(:changed)
+ Foo.send(:notify_observers, :whatever, foo)
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/state_machine/event_test.rb b/activemodel/test/state_machine/event_test.rb
new file mode 100644
index 0000000000..40b630da7c
--- /dev/null
+++ b/activemodel/test/state_machine/event_test.rb
@@ -0,0 +1,51 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class EventTest < ActiveModel::TestCase
+ def setup
+ @name = :close_order
+ @success = :success_callback
+ end
+
+ def new_event
+ @event = ActiveModel::StateMachine::Event.new(nil, @name, {:success => @success}) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+ end
+
+ test 'should set the name' do
+ assert_equal @name, new_event.name
+ end
+
+ test 'should set the success option' do
+ assert_equal @success, new_event.success
+ end
+
+ uses_mocha 'StateTransition creation' do
+ test 'should create StateTransitions' do
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :open)
+ ActiveModel::StateMachine::StateTransition.expects(:new).with(:to => :closed, :from => :received)
+ new_event
+ end
+ end
+end
+
+class EventBeingFiredTest < ActiveModel::TestCase
+ test 'should raise an AASM::InvalidTransition error if the transitions are empty' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event)
+
+ assert_raises ActiveModel::StateMachine::InvalidTransition do
+ event.fire(nil)
+ end
+ end
+
+ test 'should return the state of the first matching transition it finds' do
+ event = ActiveModel::StateMachine::Event.new(nil, :event) do
+ transitions :to => :closed, :from => [:open, :received]
+ end
+
+ obj = stub
+ obj.stubs(:current_state).returns(:open)
+
+ assert_equal :closed, event.fire(obj)
+ end
+end
diff --git a/activemodel/test/state_machine/machine_test.rb b/activemodel/test/state_machine/machine_test.rb
new file mode 100644
index 0000000000..2cdfcd9554
--- /dev/null
+++ b/activemodel/test/state_machine/machine_test.rb
@@ -0,0 +1,43 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class MachineTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open
+ state :closed
+ end
+
+ state_machine :initial => :foo do
+ event :shutdown do
+ transitions :from => :open, :to => :closed
+ end
+
+ event :timeout do
+ transitions :from => :open, :to => :closed
+ end
+ end
+
+ state_machine :extra, :initial => :bar do
+ end
+end
+
+class StateMachineMachineTest < ActiveModel::TestCase
+ test "allows reuse of existing machines" do
+ assert_equal 2, MachineTestSubject.state_machines.size
+ end
+
+ test "sets #initial_state from :initial option" do
+ assert_equal :bar, MachineTestSubject.state_machine(:extra).initial_state
+ end
+
+ test "accesses non-default state machine" do
+ assert_kind_of ActiveModel::StateMachine::Machine, MachineTestSubject.state_machine(:extra)
+ end
+
+ test "finds events for given state" do
+ events = MachineTestSubject.state_machine.events_for(:open)
+ assert events.include?(:shutdown)
+ assert events.include?(:timeout)
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/state_machine/state_test.rb b/activemodel/test/state_machine/state_test.rb
new file mode 100644
index 0000000000..22d0d9eb93
--- /dev/null
+++ b/activemodel/test/state_machine/state_test.rb
@@ -0,0 +1,74 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class StateTestSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ end
+end
+
+class StateTest < ActiveModel::TestCase
+ def setup
+ @name = :astate
+ @machine = StateTestSubject.state_machine
+ @options = { :crazy_custom_key => 'key', :machine => @machine }
+ end
+
+ def new_state(options={})
+ ActiveModel::StateMachine::State.new(@name, @options.merge(options))
+ end
+
+ test 'sets the name' do
+ assert_equal :astate, new_state.name
+ end
+
+ test 'sets the display_name from name' do
+ assert_equal "Astate", new_state.display_name
+ end
+
+ test 'sets the display_name from options' do
+ assert_equal "A State", new_state(:display => "A State").display_name
+ end
+
+ test 'sets the options and expose them as options' do
+ @options.delete(:machine)
+ assert_equal @options, new_state.options
+ end
+
+ test 'equals a symbol of the same name' do
+ assert_equal new_state, :astate
+ end
+
+ test 'equals a State of the same name' do
+ assert_equal new_state, new_state
+ end
+
+ uses_mocha 'state actions' do
+ test 'should send a message to the record for an action if the action is present as a symbol' do
+ state = new_state(:entering => :foo)
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should send a message to the record for an action if the action is present as a string' do
+ state = new_state(:entering => 'foo')
+
+ record = stub
+ record.expects(:foo)
+
+ state.call_action(:entering, record)
+ end
+
+ test 'should call a proc, passing in the record for an action if the action is present' do
+ state = new_state(:entering => Proc.new {|r| r.foobar})
+
+ record = stub
+ record.expects(:foobar)
+
+ state.call_action(:entering, record)
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/state_machine/state_transition_test.rb b/activemodel/test/state_machine/state_transition_test.rb
new file mode 100644
index 0000000000..9a9e7f60c5
--- /dev/null
+++ b/activemodel/test/state_machine/state_transition_test.rb
@@ -0,0 +1,88 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
+
+class StateTransitionTest < ActiveModel::TestCase
+ test 'should set from, to, and opts attr readers' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert_equal opts[:from], st.from
+ assert_equal opts[:to], st.to
+ assert_equal opts, st.options
+ end
+
+ uses_mocha 'checking ActiveModel StateMachine transitions' do
+ test 'should pass equality check if from and to are the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_equal st, obj
+ end
+
+ test 'should fail equality check if from are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns('blah')
+ obj.stubs(:to).returns(opts[:to])
+
+ assert_not_equal st, obj
+ end
+
+ test 'should fail equality check if to are not the same' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'g'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.stubs(:from).returns(opts[:from])
+ obj.stubs(:to).returns('blah')
+
+ assert_not_equal st, obj
+ end
+ end
+end
+
+class StateTransitionGuardCheckTest < ActiveModel::TestCase
+ test 'should return true of there is no guard' do
+ opts = {:from => 'foo', :to => 'bar'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ assert st.perform(nil)
+ end
+
+ uses_mocha 'checking ActiveModel StateMachine transition guard checks' do
+ test 'should call the method on the object if guard is a symbol' do
+ opts = {:from => 'foo', :to => 'bar', :guard => :test_guard}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the method on the object if guard is a string' do
+ opts = {:from => 'foo', :to => 'bar', :guard => 'test_guard'}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+
+ test 'should call the proc passing the object if the guard is a proc' do
+ opts = {:from => 'foo', :to => 'bar', :guard => Proc.new {|o| o.test_guard}}
+ st = ActiveModel::StateMachine::StateTransition.new(opts)
+
+ obj = stub
+ obj.expects(:test_guard)
+
+ st.perform(obj)
+ end
+ end
+end
diff --git a/activemodel/test/state_machine_test.rb b/activemodel/test/state_machine_test.rb
new file mode 100644
index 0000000000..b2f0fc4ec0
--- /dev/null
+++ b/activemodel/test/state_machine_test.rb
@@ -0,0 +1,324 @@
+require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
+
+class StateMachineSubject
+ include ActiveModel::StateMachine
+
+ state_machine do
+ state :open, :exit => :exit
+ state :closed, :enter => :enter
+
+ event :close, :success => :success_callback do
+ transitions :to => :closed, :from => [:open]
+ end
+
+ event :null do
+ transitions :to => :closed, :from => [:open], :guard => :always_false
+ end
+ end
+
+ state_machine :bar do
+ state :read
+ state :ended
+
+ event :foo do
+ transitions :to => :ended, :from => [:read]
+ end
+ end
+
+ def always_false
+ false
+ end
+
+ def success_callback
+ end
+
+ def enter
+ end
+ def exit
+ end
+end
+
+class StateMachineSubjectSubclass < StateMachineSubject
+end
+
+class StateMachineClassLevelTest < ActiveModel::TestCase
+ test 'defines a class level #state_machine method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machine)
+ end
+
+ test 'defines a class level #state_machines method on its including class' do
+ assert StateMachineSubject.respond_to?(:state_machines)
+ end
+
+ test 'class level #state_machine returns machine instance' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine
+ end
+
+ test 'class level #state_machine returns machine instance with given name' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machine(:default)
+ end
+
+ test 'class level #state_machines returns hash of machine instances' do
+ assert_kind_of ActiveModel::StateMachine::Machine, StateMachineSubject.state_machines[:default]
+ end
+
+ test "should return a select friendly array of states in the form of [['Friendly name', 'state_name']]" do
+ assert_equal [['Open', 'open'], ['Closed', 'closed']], StateMachineSubject.state_machine.states_for_select
+ end
+end
+
+class StateMachineInstanceLevelTest < ActiveModel::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'defines an accessor for the current state' do
+ assert @foo.respond_to?(:current_state)
+ end
+
+ test 'defines a state querying instance method on including class' do
+ assert @foo.respond_to?(:open?)
+ end
+
+ test 'defines an event! instance method' do
+ assert @foo.respond_to?(:close!)
+ end
+
+ test 'defines an event instance method' do
+ assert @foo.respond_to?(:close)
+ end
+end
+
+class StateMachineInitialStatesTest < ActiveModel::TestCase
+ def setup
+ @foo = StateMachineSubject.new
+ end
+
+ test 'sets the initial state' do
+ assert_equal :open, @foo.current_state
+ end
+
+ test '#open? should be initially true' do
+ assert @foo.open?
+ end
+
+ test '#closed? should be initially false' do
+ assert !@foo.closed?
+ end
+
+ test 'uses the first state defined if no initial state is given' do
+ assert_equal :read, @foo.current_state(:bar)
+ end
+end
+
+class StateMachineEventFiringWithPersistenceTest < ActiveModel::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'updates the current state' do
+ @subj.close!
+
+ assert_equal :closed, @subj.current_state
+ end
+
+ uses_mocha "StateMachineEventFiringWithPersistenceTest with callbacks" do
+ test 'fires the Event' do
+ @subj.class.state_machine.events[:close].expects(:fire).with(@subj)
+ @subj.close!
+ end
+
+ test 'calls the success callback if one was provided' do
+ @subj.expects(:success_callback)
+ @subj.close!
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ def @subj.write_state
+ end
+
+ @subj.expects(:write_state)
+ @subj.close!
+ end
+ end
+end
+
+class StateMachineEventFiringWithoutPersistence < ActiveModel::TestCase
+ test 'updates the current state' do
+ subj = StateMachineSubject.new
+ assert_equal :open, subj.current_state
+ subj.close
+ assert_equal :closed, subj.current_state
+ end
+
+ uses_mocha 'StateMachineEventFiringWithoutPersistence' do
+ test 'fires the Event' do
+ subj = StateMachineSubject.new
+
+ StateMachineSubject.state_machine.events[:close].expects(:fire).with(subj)
+ subj.close
+ end
+
+ test 'attempts to persist if write_state is defined' do
+ subj = StateMachineSubject.new
+
+ def subj.write_state
+ end
+
+ subj.expects(:write_state_without_persistence)
+
+ subj.close
+ end
+ end
+end
+
+uses_mocha 'StateMachinePersistenceTest' do
+ class StateMachinePersistenceTest < ActiveModel::TestCase
+ test 'reads the state if it has not been set and read_state is defined' do
+ subj = StateMachineSubject.new
+ def subj.read_state
+ end
+
+ subj.expects(:read_state).with(StateMachineSubject.state_machine)
+
+ subj.current_state
+ end
+ end
+end
+
+uses_mocha 'StateMachineEventCallbacksTest' do
+ class StateMachineEventCallbacksTest < ActiveModel::TestCase
+ test 'should call aasm_event_fired if defined and successful for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close!
+ end
+
+ test 'should call aasm_event_fired if defined and successful for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_fired(from, to)
+ end
+
+ subj.expects(:event_fired)
+
+ subj.close
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for bang fire' do
+ subj = StateMachineSubject.new
+ def subj.event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null!
+ end
+
+ test 'should call aasm_event_failed if defined and transition failed for non-bang fire' do
+ subj = StateMachineSubject.new
+ def subj.aasm_event_failed(event)
+ end
+
+ subj.expects(:event_failed)
+
+ subj.null
+ end
+ end
+end
+
+uses_mocha 'StateMachineStateActionsTest' do
+ class StateMachineStateActionsTest < ActiveModel::TestCase
+ test "calls enter when entering state" do
+ subj = StateMachineSubject.new
+ subj.expects(:enter)
+ subj.close
+ end
+
+ test "calls exit when exiting state" do
+ subj = StateMachineSubject.new
+ subj.expects(:exit)
+ subj.close
+ end
+ end
+end
+
+class StateMachineInheritanceTest < ActiveModel::TestCase
+ test "has the same states as its parent" do
+ assert_equal StateMachineSubject.state_machine.states, StateMachineSubjectSubclass.state_machine.states
+ end
+
+ test "has the same events as its parent" do
+ assert_equal StateMachineSubject.state_machine.events, StateMachineSubjectSubclass.state_machine.events
+ end
+end
+
+class StateMachineSubject
+ state_machine :chetan_patil, :initial => :sleeping do
+ state :sleeping
+ state :showering
+ state :working
+ state :dating
+
+ event :wakeup do
+ transitions :from => :sleeping, :to => [:showering, :working]
+ end
+
+ event :dress do
+ transitions :from => :sleeping, :to => :working, :on_transition => :wear_clothes
+ transitions :from => :showering, :to => [:working, :dating], :on_transition => Proc.new { |obj, *args| obj.wear_clothes(*args) }
+ end
+ end
+
+ def wear_clothes(shirt_color, trouser_type)
+ end
+end
+
+class StateMachineWithComplexTransitionsTest < ActiveModel::TestCase
+ def setup
+ @subj = StateMachineSubject.new
+ end
+
+ test 'transitions to specified next state (sleeping to showering)' do
+ @subj.wakeup! :showering
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to specified next state (sleeping to working)' do
+ @subj.wakeup! :working
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default (first or showering) state' do
+ @subj.wakeup!
+
+ assert_equal :showering, @subj.current_state(:chetan_patil)
+ end
+
+ test 'transitions to default state when on_transition invoked' do
+ @subj.dress!(nil, 'purple', 'dressy')
+
+ assert_equal :working, @subj.current_state(:chetan_patil)
+ end
+
+ uses_mocha "StateMachineWithComplexTransitionsTest on_transition tests" do
+ test 'calls on_transition method with args' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('blue', 'jeans')
+ @subj.dress! :working, 'blue', 'jeans'
+ end
+
+ test 'calls on_transition proc' do
+ @subj.wakeup! :showering
+
+ @subj.expects(:wear_clothes).with('purple', 'slacks')
+ @subj.dress!(:dating, 'purple', 'slacks')
+ end
+ end
+end \ No newline at end of file
diff --git a/activemodel/test/test_helper.rb b/activemodel/test/test_helper.rb
new file mode 100644
index 0000000000..ccf93280ec
--- /dev/null
+++ b/activemodel/test/test_helper.rb
@@ -0,0 +1,39 @@
+$:.unshift "#{File.dirname(__FILE__)}/../lib"
+$:.unshift File.dirname(__FILE__)
+
+require 'test/unit'
+require 'active_model'
+require 'active_model/state_machine'
+require 'active_support/callbacks' # needed by ActiveModel::TestCase
+require 'active_support/test_case'
+
+def uses_gem(gem_name, test_name, version = '> 0')
+ require 'rubygems'
+ gem gem_name.to_s, version
+ require gem_name.to_s
+ yield
+rescue LoadError
+ $stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
+end
+
+# Wrap tests that use Mocha and skip if unavailable.
+unless defined? uses_mocha
+ def uses_mocha(test_name, &block)
+ uses_gem('mocha', test_name, '>= 0.5.5', &block)
+ end
+end
+
+begin
+ require 'rubygems'
+ require 'ruby-debug'
+ Debugger.start
+rescue LoadError
+end
+
+ActiveSupport::TestCase.send :include, ActiveSupport::Testing::Default
+
+module ActiveModel
+ class TestCase < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Default
+ end
+end \ No newline at end of file
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a1d82fb45d..95fdd93f65 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,49 @@
*Edge*
+* change_column_default preserves the not-null constraint. #617 [Tarmo Tänav]
+
+* Fixed that create database statements would always include "DEFAULT NULL" (Nick Sieger) [#334]
+
+* Add :accessible option to associations for allowing (opt-in) mass assignment. #474. [David Dollar] Example :
+
+ class Post < ActiveRecord::Base
+ belongs_to :author, :accessible => true
+ has_many :comments, :accessible => true
+ end
+
+ post = Post.create({
+ :title => 'Accessible Attributes',
+ :author => { :name => 'David Dollar' },
+ :comments => [
+ { :body => 'First Post!' },
+ { :body => 'Nested Hashes are great!' }
+ ]
+ })
+
+ post.comments << { :body => 'Another Comment' }
+
+* Add :tokenizer option to validates_length_of to specify how to split up the attribute string. #507. [David Lowenfels] Example :
+
+ # Ensure essay contains at least 100 words.
+ validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
+
+* Allow conditions on multiple tables to be specified using hash. [Pratik Naik]. Example:
+
+ User.all :joins => :items, :conditions => { :age => 10, :items => { :color => 'black' } }
+ Item.first :conditions => { :items => { :color => 'red' } }
+
+* Always treat integer :limit as byte length. #420 [Tarmo Tänav]
+
+* Partial updates don't update lock_version if nothing changed. #426 [Daniel Morrison]
+
+* Fix column collision with named_scope and :joins. #46 [Duncan Beevers, Mark Catley]
+
+* db:migrate:down and :up update schema_migrations. #369 [Michael Raidel, RaceCondition]
+
+* PostgreSQL: support :conditions => [':foo::integer', { :foo => 1 }] without treating the ::integer typecast as a bind variable. [Tarmo Tänav]
+
+* MySQL: rename_column preserves column defaults. #466 [Diego Algorta]
+
* Add :from option to calculations. #397 [Ben Munat]
* Add :validate option to associations to enable/disable the automatic validation of associated models. Resolves #301. [Jan De Poorter]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index fc068b16c3..60b17e02b9 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -141,7 +141,7 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Active Record -- Object-relation mapping put on rails"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
@@ -225,13 +225,13 @@ end
desc "Publish the beta gem"
task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
desc "Publish the release files to RubyForge."
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index 49f5270396..174ec95de2 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -188,7 +188,6 @@ module ActiveRecord
through_records
end
- # FIXME: quoting
def preload_belongs_to_association(records, reflection, preload_options={})
options = reflection.options
primary_key_name = reflection.primary_key_name
@@ -227,9 +226,19 @@ module ActiveRecord
table_name = klass.quoted_table_name
primary_key = klass.primary_key
- conditions = "#{table_name}.#{primary_key} IN (?)"
+ conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} IN (?)"
conditions << append_conditions(options, preload_options)
- associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
+ column_type = klass.columns.detect{|c| c.name == primary_key}.type
+ ids = id_map.keys.uniq.map do |id|
+ if column_type == :integer
+ id.to_i
+ elsif column_type == :float
+ id.to_f
+ else
+ id
+ end
+ end
+ associated_records = klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
:joins => options[:joins],
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5f42b5a459..7ad7802cbc 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -663,6 +663,7 @@ module ActiveRecord
# * <tt>:foreign_key</tt> - 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>.
+ # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
# * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
@@ -678,7 +679,7 @@ module ActiveRecord
# * <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 the first 4 rows.
# * <tt>:select</tt> - 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 rise an error.
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:through</tt> - 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>
@@ -691,6 +692,7 @@ module ActiveRecord
# * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_many :comments, :order => "posted_on"
@@ -711,7 +713,8 @@ module ActiveRecord
configure_dependency_for_has_many(reflection)
- add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
+ add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
+ add_multiple_associated_save_callbacks(reflection.name)
add_association_callbacks(reflection.name, reflection.options)
if options[:through]
@@ -757,6 +760,7 @@ module ActiveRecord
# * <tt>:foreign_key</tt> - 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>.
+ # * <tt>:primary_key</tt> - Specify the method that returns the primary key used for the association. By default this is +id+.
# * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
# * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
# * <tt>:select</tt> - 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
@@ -771,6 +775,7 @@ module ActiveRecord
# association is a polymorphic +belongs_to+.
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
@@ -801,7 +806,7 @@ module ActiveRecord
end
after_save method_name
- add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
association_accessor_methods(reflection, HasOneAssociation)
association_constructor_method(:build, reflection, HasOneAssociation)
association_constructor_method(:create, reflection, HasOneAssociation)
@@ -860,6 +865,7 @@ module ActiveRecord
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# belongs_to :firm, :foreign_key => "client_of"
@@ -940,7 +946,7 @@ module ActiveRecord
)
end
- add_single_associated_save_callbacks(reflection.name) if options[:validate] == true
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
configure_dependency_for_belongs_to(reflection)
end
@@ -1031,6 +1037,7 @@ module ActiveRecord
# but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
# * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
# * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +true+ by default.
+ # * <tt>:accessible</tt> - Mass assignment is allowed for this assocation (similar to <tt>ActiveRecord::Base#attr_accessible</tt>).
#
# Option examples:
# has_and_belongs_to_many :projects
@@ -1043,7 +1050,8 @@ module ActiveRecord
def has_and_belongs_to_many(association_id, options = {}, &extension)
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
- add_multiple_associated_save_callbacks(reflection.name) unless options[:validate] == false
+ add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
+ add_multiple_associated_save_callbacks(reflection.name)
collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
# Don't use a before_destroy callback since users' before_destroy
@@ -1105,6 +1113,8 @@ module ActiveRecord
association = association_proxy_class.new(self, reflection)
end
+ new_value = reflection.klass.new(new_value) if reflection.options[:accessible] && new_value.is_a?(Hash)
+
if association_proxy_class == HasOneThroughAssociation
association.create_through_record(new_value)
self.send(reflection.name, new_value)
@@ -1141,7 +1151,7 @@ module ActiveRecord
end
define_method("#{reflection.name.to_s.singularize}_ids") do
- send(reflection.name).map(&:id)
+ send(reflection.name).map { |record| record.id }
end
end
@@ -1163,7 +1173,7 @@ module ActiveRecord
end
end
- def add_single_associated_save_callbacks(association_name)
+ def add_single_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
association = instance_variable_get("@#{association_name}")
@@ -1175,7 +1185,7 @@ module ActiveRecord
validate method_name
end
- def add_multiple_associated_save_callbacks(association_name)
+ def add_multiple_associated_validation_callbacks(association_name)
method_name = "validate_associated_records_for_#{association_name}".to_sym
ivar = "@#{association_name}"
@@ -1196,6 +1206,10 @@ module ActiveRecord
end
validate method_name
+ end
+
+ def add_multiple_associated_save_callbacks(association_name)
+ ivar = "@#{association_name}"
method_name = "before_save_associated_records_for_#{association_name}".to_sym
define_method(method_name) do
@@ -1217,7 +1231,6 @@ module ActiveRecord
else
[]
end
-
records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
# reconstruct the SQL queries now that we know the owner's id
@@ -1342,7 +1355,7 @@ module ActiveRecord
def create_has_many_reflection(association_id, options, &extension)
options.assert_valid_keys(
- :class_name, :table_name, :foreign_key,
+ :class_name, :table_name, :foreign_key, :primary_key,
:dependent,
:select, :conditions, :include, :order, :group, :limit, :offset,
:as, :through, :source, :source_type,
@@ -1350,7 +1363,7 @@ module ActiveRecord
:finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :accessible
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1360,7 +1373,7 @@ module ActiveRecord
def create_has_one_reflection(association_id, options)
options.assert_valid_keys(
- :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key, :accessible
)
create_reflection(:has_one, association_id, options, self)
@@ -1376,7 +1389,7 @@ module ActiveRecord
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(
:class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
- :counter_cache, :extend, :polymorphic, :readonly, :validate
+ :counter_cache, :extend, :polymorphic, :readonly, :validate, :accessible
)
reflection = create_reflection(:belongs_to, association_id, options, self)
@@ -1396,7 +1409,7 @@ module ActiveRecord
:finder_sql, :delete_sql, :insert_sql,
:before_add, :after_add, :before_remove, :after_remove,
:extend, :readonly,
- :validate
+ :validate, :accessible
)
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
@@ -1473,25 +1486,30 @@ module ActiveRecord
join_dependency.joins_for_table_name(table)
}.flatten.compact.uniq
+ order = options[:order]
+ if scoped_order = (scope && scope[:order])
+ order = order ? "#{order}, #{scoped_order}" : scoped_order
+ end
+
is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
sql = "SELECT "
if is_distinct
- sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order)
else
sql << primary_key
end
sql << " FROM #{connection.quote_table_name table_name} "
if is_distinct
- sql << distinct_join_associations.collect(&:association_join).join
+ sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
add_joins!(sql, options, scope)
end
add_conditions!(sql, options[:conditions], scope)
add_group!(sql, options[:group], scope)
- if options[:order] && is_distinct
- connection.add_order_by_for_association_limiting!(sql, options)
+ if order && is_distinct
+ connection.add_order_by_for_association_limiting!(sql, :order => order)
else
add_order!(sql, options[:order], scope)
end
@@ -1514,7 +1532,7 @@ module ActiveRecord
end
def order_tables(options)
- order = options[:order]
+ order = [options[:order], scope(:find, :order) ].join(", ")
return [] unless order && order.is_a?(String)
order.scan(/([\.\w]+).?\./).flatten
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 52d2a9864e..a28be9eed1 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -14,7 +14,7 @@ module ActiveRecord
# If using a custom finder_sql, scan the entire collection.
if @reflection.options[:finder_sql]
expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.uniq.map(&:to_i)
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
if ids.size == 1
id = ids.first
@@ -78,11 +78,14 @@ module ActiveRecord
@loaded = false
end
- def build(attributes = {})
+ def build(attributes = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| build(attr) }
+ attributes.collect { |attr| build(attr, &block) }
else
- build_record(attributes) { |record| set_belongs_to_association_for(record) }
+ build_record(attributes) do |record|
+ block.call(record) if block_given?
+ set_belongs_to_association_for(record)
+ end
end
end
@@ -94,6 +97,8 @@ module ActiveRecord
@owner.transaction do
flatten_deeper(records).each do |record|
+ record = @reflection.klass.new(record) if @reflection.options[:accessible] && record.is_a?(Hash)
+
raise_on_type_mismatch(record)
add_record_to_target_with_callbacks(record) do |r|
result &&= insert_record(record) unless @owner.new_record?
@@ -187,7 +192,7 @@ module ActiveRecord
if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
@target.size
elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
- unsaved_records = Array(@target.detect { |r| r.new_record? })
+ unsaved_records = @target.select { |r| r.new_record? }
unsaved_records.size + count_records
else
count_records
@@ -226,6 +231,10 @@ module ActiveRecord
# Replace this collection with +other_array+
# This will perform a diff and delete/add only records that have changed.
def replace(other_array)
+ other_array.map! do |val|
+ val.is_a?(Hash) ? @reflection.klass.new(val) : val
+ end if @reflection.options[:accessible]
+
other_array.each { |val| raise_on_type_mismatch(val) }
load_target
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 11c64243a2..77fc827e11 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -85,7 +85,7 @@ module ActiveRecord
end
def conditions
- @conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
+ @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
end
alias :sql_conditions :conditions
@@ -219,6 +219,10 @@ module ActiveRecord
def flatten_deeper(array)
array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
end
+
+ def owner_quoted_id
+ @owner.quoted_id
+ end
end
end
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 4fa8e9d0a8..d516d54151 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
@@ -37,7 +37,7 @@ module ActiveRecord
attributes = columns.inject({}) do |attrs, column|
case column.name.to_s
when @reflection.primary_key_name.to_s
- attrs[column.name] = @owner.quoted_id
+ attrs[column.name] = owner_quoted_id
when @reflection.association_foreign_key.to_s
attrs[column.name] = record.quoted_id
else
@@ -64,7 +64,7 @@ module ActiveRecord
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
ids = quoted_record_ids(records)
- sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
@owner.connection.delete(sql)
end
end
@@ -75,7 +75,7 @@ module ActiveRecord
if @reflection.options[:finder_sql]
@finder_sql = @reflection.options[:finder_sql]
else
- @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
@finder_sql << " AND (#{conditions})" if conditions
end
@@ -87,6 +87,7 @@ module ActiveRecord
:joins => @join_sql,
:readonly => false,
:order => @reflection.options[:order],
+ :include => @reflection.options[:include],
:limit => @reflection.options[:limit] } }
end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index f584a97cbb..e6fa15c173 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -19,6 +19,14 @@ module ActiveRecord
end
protected
+ def owner_quoted_id
+ if @reflection.options[:primary_key]
+ quote_value(@owner.send(@reflection.options[:primary_key]))
+ else
+ @owner.quoted_id
+ end
+ end
+
def count_records
count = if has_cached_counter?
@owner.send(:read_attribute, cached_counter_attribute_name)
@@ -53,14 +61,14 @@ module ActiveRecord
def delete_records(records)
case @reflection.options[:dependent]
when :destroy
- records.each(&:destroy)
+ records.each { |r| r.destroy }
when :delete_all
- @reflection.klass.delete(records.map(&:id))
+ @reflection.klass.delete(records.map { |record| record.id })
else
ids = quoted_record_ids(records)
@reflection.klass.update_all(
"#{@reflection.primary_key_name} = NULL",
- "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
+ "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
)
end
end
@@ -76,12 +84,12 @@ module ActiveRecord
when @reflection.options[:as]
@finder_sql =
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
@finder_sql << " AND (#{conditions})" if conditions
else
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
@finder_sql << " AND (#{conditions})" if conditions
end
@@ -100,7 +108,7 @@ module ActiveRecord
create_scoping = {}
set_belongs_to_association_for(create_scoping)
{
- :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
+ :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
:create => create_scoping
}
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 52ced36d16..e1bfff5923 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -107,12 +107,12 @@ module ActiveRecord
# Associate attributes pointing to owner, quoted.
def construct_quoted_owner_attributes(reflection)
if as = reflection.options[:as]
- { "#{as}_id" => @owner.quoted_id,
+ { "#{as}_id" => owner_quoted_id,
"#{as}_type" => reflection.klass.quote_value(
@owner.class.base_class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
else
- { reflection.primary_key_name => @owner.quoted_id }
+ { reflection.primary_key_name => owner_quoted_id }
end
end
@@ -183,7 +183,7 @@ module ActiveRecord
when @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
@finder_sql << " AND (#{conditions})" if conditions
else
@finder_sql = construct_conditions
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index c2b3503e0d..fdc0fa52c9 100755
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -47,7 +47,16 @@ module ActiveRecord
return (obj.nil? ? nil : self)
end
end
-
+
+ protected
+ def owner_quoted_id
+ if @reflection.options[:primary_key]
+ quote_value(@owner.send(@reflection.options[:primary_key]))
+ else
+ @owner.quoted_id
+ end
+ end
+
private
def find_target
@reflection.klass.find(:first,
@@ -63,10 +72,10 @@ module ActiveRecord
case
when @reflection.options[:as]
@finder_sql =
- "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
else
- @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
end
@finder_sql << " AND (#{conditions})" if conditions
end
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 8fca34e524..8ca5a85ad8 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -828,7 +828,7 @@ module ActiveRecord #:nodoc:
def update_counters(id, counters)
updates = counters.inject([]) { |list, (counter_name, increment)|
sign = increment < 0 ? "-" : "+"
- list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
+ list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
}.join(", ")
update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
end
@@ -1479,7 +1479,7 @@ module ActiveRecord #:nodoc:
def construct_finder_sql(options)
scope = scope(:find)
- sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
add_joins!(sql, options, scope)
@@ -1999,24 +1999,28 @@ module ActiveRecord #:nodoc:
# # => "age BETWEEN 13 AND 18"
# { 'other_records.id' => 7 }
# # => "`other_records`.`id` = 7"
+ # { :other_records => { :id => 7 } }
+ # # => "`other_records`.`id` = 7"
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
- def sanitize_sql_hash_for_conditions(attrs)
+ def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
conditions = attrs.map do |attr, value|
- attr = attr.to_s
+ unless value.is_a?(Hash)
+ attr = attr.to_s
+
+ # Extract table name from qualified attribute names.
+ if attr.include?('.')
+ table_name, attr = attr.split('.', 2)
+ table_name = connection.quote_table_name(table_name)
+ end
- # Extract table name from qualified attribute names.
- if attr.include?('.')
- table_name, attr = attr.split('.', 2)
- table_name = connection.quote_table_name(table_name)
+ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
else
- table_name = quoted_table_name
+ sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
end
-
- "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
end.join(' AND ')
replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
@@ -2055,9 +2059,10 @@ module ActiveRecord #:nodoc:
end
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
- statement.gsub(/:([a-zA-Z]\w*)/) do
- match = $1.to_sym
- if bind_vars.include?(match)
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
+ if $1 == ':' # skip postgresql casts
+ $& # return the whole match
+ elsif bind_vars.include?(match = $2.to_sym)
quote_bound_value(bind_vars[match])
else
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
@@ -2069,6 +2074,8 @@ module ActiveRecord #:nodoc:
expanded = []
bind_vars.each do |var|
+ next if var.is_a?(Hash)
+
if var.is_a?(Range)
expanded << var.first
expanded << var.last
@@ -2169,11 +2176,11 @@ module ActiveRecord #:nodoc:
def cache_key
case
when new_record?
- "#{self.class.name.tableize}/new"
- when self[:updated_at]
- "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
+ "#{self.class.model_name.cache_key}/new"
+ when timestamp = self[:updated_at]
+ "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}"
else
- "#{self.class.name.tableize}/#{id}"
+ "#{self.class.model_name.cache_key}/#{id}"
end
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 4edc209c65..1e385fb128 100755
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -262,7 +262,7 @@ module ActiveRecord
def valid_with_callbacks? #:nodoc:
return false if callback(:before_validation) == false
if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
- return false if result == false
+ return false if false == result
result = valid_without_callbacks?
@@ -293,7 +293,7 @@ module ActiveRecord
private
def callback(method)
- result = run_callbacks(method) { |result, object| result == false }
+ result = run_callbacks(method) { |result, object| false == result }
if result != false && respond_to_without_attributes?(method)
result = send(method)
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 f968b9b173..31d6c7942c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -1,4 +1,5 @@
require 'date'
+require 'set'
require 'bigdecimal'
require 'bigdecimal/util'
@@ -6,6 +7,8 @@ module ActiveRecord
module ConnectionAdapters #:nodoc:
# An abstract definition of a column in a table.
class Column
+ TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
+
module Format
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
@@ -30,11 +33,11 @@ module ActiveRecord
end
def text?
- [:string, :text].include? type
+ type == :string || type == :text
end
def number?
- [:float, :integer, :decimal].include? type
+ type == :integer || type == :float || type == :decimal
end
# Returns the Ruby class that corresponds to the abstract data type.
@@ -135,11 +138,7 @@ module ActiveRecord
# convert something to a boolean
def value_to_boolean(value)
- if value == true || value == false
- value
- else
- %w(true t 1).include?(value.to_s.downcase)
- end
+ TRUE_VALUES.include?(value)
end
# convert something to a BigDecimal
@@ -257,7 +256,10 @@ module ActiveRecord
def to_sql
column_sql = "#{base.quote_column_name(name)} #{sql_type}"
- add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
+ column_options = {}
+ column_options[:null] = null unless null.nil?
+ column_options[:default] = default unless default.nil?
+ add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key
column_sql
end
alias to_s :to_sql
@@ -304,8 +306,7 @@ module ActiveRecord
#
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
- # Requests a maximum column length (<tt>:string</tt>, <tt>:text</tt>,
- # <tt>:binary</tt> or <tt>:integer</tt> columns only)
+ # 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> -
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index f48b107a2a..47dbf5a5f3 100755
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -118,6 +118,19 @@ module ActiveRecord
@connection
end
+ def open_transactions
+ @open_transactions ||= 0
+ end
+
+ def increment_open_transactions
+ @open_transactions ||= 0
+ @open_transactions += 1
+ end
+
+ def decrement_open_transactions
+ @open_transactions -= 1
+ end
+
def log_info(sql, name, runtime)
if @logger && @logger.debug?
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 93aafaaad1..35b9ed4746 100755
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -69,7 +69,7 @@ module ActiveRecord
MysqlCompat.define_all_hashes_method!
mysql = Mysql.init
- mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey]
ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
end
@@ -99,7 +99,8 @@ module ActiveRecord
end
def extract_limit(sql_type)
- if sql_type =~ /blob|text/i
+ case sql_type
+ when /blob|text/i
case sql_type
when /tiny/i
255
@@ -110,6 +111,11 @@ module ActiveRecord
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
@@ -139,6 +145,7 @@ module ActiveRecord
# * <tt>:password</tt> - Defaults to nothing.
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
@@ -168,7 +175,7 @@ module ActiveRecord
:primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY".freeze,
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
- :integer => { :name => "int"},
+ :integer => { :name => "int", :limit => 4 },
:float => { :name => "float" },
:decimal => { :name => "decimal" },
:datetime => { :name => "datetime" },
@@ -430,18 +437,29 @@ module ActiveRecord
end
def change_column_default(table_name, column_name, default) #:nodoc:
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ 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
- execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
+ change_column table_name, column_name, column.sql_type, :null => null
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ column = column_for(table_name, column_name)
+
unless options_include_default?(options)
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- else
- raise "No such column: #{table_name}.#{column_name}"
- end
+ 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])}"
@@ -450,8 +468,17 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ 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"]
- execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_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.
@@ -459,14 +486,12 @@ module ActiveRecord
return super unless type.to_s == 'integer'
case limit
- when 0..3
- "smallint(#{limit})"
- when 4..8
- "int(#{limit})"
- when 9..20
- "bigint(#{limit})"
- else
- 'int(11)'
+ 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
@@ -495,7 +520,9 @@ module ActiveRecord
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
end
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
+ if @config[:sslca] || @config[:sslkey]
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
+ end
@connection.real_connect(*@connection_options)
@@ -521,6 +548,13 @@ module ActiveRecord
def version
@version ||= @connection.server_info.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/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 294f4c1929..6d16d72dea 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -47,6 +47,14 @@ module ActiveRecord
end
private
+ def extract_limit(sql_type)
+ case sql_type
+ when /^bigint/i; 8
+ when /^smallint/i; 2
+ else super
+ end
+ end
+
# Extracts the scale from PostgreSQL-specific data types.
def extract_scale(sql_type)
# Money type has a fixed scale of 2.
@@ -324,12 +332,7 @@ module ActiveRecord
end
def supports_insert_with_returning?
- unless defined? @supports_insert_with_returning
- @supports_insert_with_returning =
- @connection.respond_to?(:server_version) &&
- @connection.server_version >= 80200
- end
- @supports_insert_with_returning
+ postgresql_version >= 80200
end
# Returns the configured supported identifier length supported by PostgreSQL,
@@ -553,7 +556,15 @@ module ActiveRecord
# Example:
# drop_database 'matt_development'
def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
+ if postgresql_version >= 80200
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
+ else
+ begin
+ execute "DROP DATABASE #{quote_table_name(name)}"
+ rescue ActiveRecord::StatementInvalid
+ @logger.warn "#{name} database doesn't exist." if @logger
+ end
+ end
end
@@ -611,6 +622,19 @@ module ActiveRecord
end
end
+ # Returns the current database name.
+ def current_database
+ query('select current_database()')[0][0]
+ end
+
+ # Returns the current database encoding format.
+ def encoding
+ query(<<-end_sql)[0][0]
+ SELECT pg_encoding_to_char(pg_database.encoding) FROM pg_database
+ WHERE pg_database.datname LIKE '#{current_database}'
+ end_sql
+ end
+
# Sets the schema search path to a string of comma-separated schema names.
# Names beginning with $ have to be quoted (e.g. $user => '$user').
# See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
@@ -782,15 +806,14 @@ module ActiveRecord
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
return super unless type.to_s == 'integer'
- if limit.nil? || limit == 4
- 'integer'
- elsif limit < 4
- 'smallint'
- else
- 'bigint'
+ case limit
+ when 1..2; 'smallint'
+ when 3..4, nil; 'integer'
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.")
end
end
-
+
# Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
#
# PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 51cfd10e5c..84f8c0284e 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -238,6 +238,15 @@ module ActiveRecord
end
end
+ def change_column_null(table_name, column_name, null, default = nil)
+ 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
+ alter_table(table_name) do |definition|
+ definition[column_name].null = null
+ end
+ end
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
alter_table(table_name) do |definition|
include_default = options_include_default?(options)
@@ -251,6 +260,9 @@ module ActiveRecord
end
def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ unless columns(table_name).detect{|c| c.name == column_name.to_s }
+ raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
+ end
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end
diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb
index b32e17e990..a7d767486c 100644
--- a/activerecord/lib/active_record/dirty.rb
+++ b/activerecord/lib/active_record/dirty.rb
@@ -142,9 +142,11 @@ module ActiveRecord
def field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- if column.type == :integer && column.null && old.nil?
+ if column.type == :integer && column.null && (old.nil? || old == 0)
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
# Hence we don't record it as a change if the value changes from nil to ''.
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
+ # be typecast back to 0 (''.to_i => 0)
value = nil if value.blank?
else
value = column.type_cast(value)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index e19614e31f..622cfc3c3f 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -515,7 +515,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
all_loaded_fixtures.update(fixtures_map)
- connection.transaction(Thread.current['open_transactions'].to_i == 0) do
+ connection.transaction(connection.open_transactions.zero?) do
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
fixtures.each { |fixture| fixture.insert_fixtures }
@@ -541,10 +541,11 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
label.to_s.hash.abs
end
- attr_reader :table_name
+ attr_reader :table_name, :name
def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
@connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
+ @name = table_name # preserve fixture base name
@class_name = class_name ||
(ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
@table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
@@ -929,7 +930,7 @@ module Test #:nodoc:
load_fixtures
@@already_loaded_fixtures[self.class] = @loaded_fixtures
end
- ActiveRecord::Base.send :increment_open_transactions
+ ActiveRecord::Base.connection.increment_open_transactions
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
@@ -950,9 +951,9 @@ module Test #:nodoc:
end
# Rollback changes if a transaction is active.
- if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
+ if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
ActiveRecord::Base.connection.rollback_db_transaction
- Thread.current['open_transactions'] = 0
+ ActiveRecord::Base.connection.decrement_open_transactions
end
ActiveRecord::Base.verify_active_connections!
end
@@ -963,9 +964,9 @@ module Test #:nodoc:
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
unless fixtures.nil?
if fixtures.instance_of?(Fixtures)
- @loaded_fixtures[fixtures.table_name] = fixtures
+ @loaded_fixtures[fixtures.name] = fixtures
else
- fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
+ fixtures.each { |f| @loaded_fixtures[f.name] = f }
end
end
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index c66034d18b..ff9899d032 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -68,6 +68,7 @@ module ActiveRecord
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
return update_without_lock(attribute_names) unless locking_enabled?
+ return 0 if attribute_names.empty?
lock_col = self.class.locking_column
previous_value = send(lock_col).to_i
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index b47b01e99a..e095b3c766 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -399,7 +399,10 @@ module ActiveRecord
def run
target = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
- target.migrate(@direction)
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
+ target.migrate(@direction)
+ record_version_state_after_migrating(target.version)
+ end
end
def migrate
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index b0c8a8b815..080e3d0f5e 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -82,6 +82,7 @@ module ActiveRecord
# expected_options = { :conditions => { :colored => 'red' } }
# assert_equal expected_options, Shirt.colored('red').proxy_options
def named_scope(name, options = {}, &block)
+ name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
@@ -149,7 +150,8 @@ module ActiveRecord
if scopes.include?(method)
scopes[method].call(self, *args)
else
- with_scope :find => proxy_options do
+ with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do
+ method = :new if method == :build
proxy_scope.send(method, *args, &block)
end
end
diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb
index 25e0e61c69..c96e5f9d51 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -20,7 +20,7 @@ module ActiveRecord
# ActiveRecord::Base.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
- # called during startup, and before each development request.
+ # called during startup, and before each development request.
def observers=(*observers)
@observers = observers.flatten
end
@@ -130,11 +130,11 @@ module ActiveRecord
# Observers register themselves in the model class they observe, since it is the class that
# notifies them of events when they occur. As a side-effect, when an observer is loaded its
# corresponding model class is loaded.
- #
+ #
# Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
- # application initializers. Now observers are loaded after application initializers,
+ # application initializers. Now observers are loaded after application initializers,
# so observed models can make use of extensions.
- #
+ #
# If by any chance you are using observed models in the initialization you can still
# load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
# singletons and that call instantiates and registers them.
@@ -189,7 +189,6 @@ module ActiveRecord
def add_observer!(klass)
klass.add_observer(self)
- klass.class_eval 'def after_find() end' unless klass.method_defined?(:after_find)
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 8614ef8751..3f74c03714 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -112,6 +112,10 @@ module ActiveRecord
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
end
+ def sanitized_conditions #:nodoc:
+ @sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
+ end
+
private
def derive_class_name
name.to_s.camelize
diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb
index 7dee962c8a..ca5591ae35 100644
--- a/activerecord/lib/active_record/test_case.rb
+++ b/activerecord/lib/active_record/test_case.rb
@@ -22,11 +22,22 @@ module ActiveRecord
end
end
+ def assert_sql(*patterns_to_match)
+ $queries_executed = []
+ yield
+ ensure
+ failed_patterns = []
+ patterns_to_match.each do |pattern|
+ failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
+ end
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map(&:inspect).join(', ')} not found."
+ end
+
def assert_queries(num = 1)
- $query_count = 0
+ $queries_executed = []
yield
ensure
- assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
+ assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed."
end
def assert_no_queries(&block)
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 3b6835762c..354a6c83a2 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -73,25 +73,14 @@ module ActiveRecord
# trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
module ClassMethods
def transaction(&block)
- increment_open_transactions
+ connection.increment_open_transactions
begin
- connection.transaction(Thread.current['start_db_transaction'], &block)
+ connection.transaction(connection.open_transactions == 1, &block)
ensure
- decrement_open_transactions
+ connection.decrement_open_transactions
end
end
-
- private
- def increment_open_transactions #:nodoc:
- open = Thread.current['open_transactions'] ||= 0
- Thread.current['start_db_transaction'] = open.zero?
- Thread.current['open_transactions'] = open + 1
- end
-
- def decrement_open_transactions #:nodoc:
- Thread.current['open_transactions'] -= 1
- end
end
def transaction(&block)
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 83d55f23ea..38b2f38a3d 100755
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -402,7 +402,7 @@ module ActiveRecord
record.errors.add(attr_name, message)
end
end
- end
+ end
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
#
@@ -488,8 +488,9 @@ module ActiveRecord
# validates_length_of :fax, :in => 7..32, :allow_nil => true
# validates_length_of :phone, :in => 7..32, :allow_blank => true
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
- # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
+ # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character"
+ # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me."
+ # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) }
# end
#
# Configuration options:
@@ -500,7 +501,6 @@ module ActiveRecord
# * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
- #
# * <tt>:too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
@@ -512,9 +512,14 @@ module ActiveRecord
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
+ # count words as in above example.)
+ # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
def validates_length_of(*attrs)
# Merge given options with defaults.
- options = {}.merge(DEFAULT_VALIDATION_OPTIONS)
+ options = {
+ :tokenizer => lambda {|value| value.split(//)}
+ }.merge(DEFAULT_VALIDATION_OPTIONS)
options.update(attrs.extract_options!.symbolize_keys)
# Ensure that one and only one range option is specified.
@@ -537,7 +542,7 @@ module ActiveRecord
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
if value.nil? or value.size < option_value.begin
message = record.errors.generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
record.errors.add(attr, message)
@@ -554,7 +559,7 @@ module ActiveRecord
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
validates_each(attrs, options) do |record, attr, value|
- value = value.split(//) if value.kind_of?(String)
+ value = options[:tokenizer].call(value) if value.kind_of?(String)
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
key = message_options[option]
custom_message = options[:message] || options[key]
@@ -728,7 +733,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_inclusion_of(*attr_names)
- configuration = { :on => :save, :with => nil }
+ configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
@@ -763,7 +768,7 @@ module ActiveRecord
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
def validates_exclusion_of(*attr_names)
- configuration = { :on => :save, :with => nil }
+ configuration = { :on => :save }
configuration.update(attr_names.extract_options!)
enum = configuration[:in] || configuration[:within]
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 11f9870534..04770646b2 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -65,6 +65,12 @@ class AdapterTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_encoding
+ assert_not_nil @connection.encoding
+ end
+ end
+
def test_table_alias
def @connection.test_table_alias_length() 10; end
class << @connection
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 294b993c55..b29df68d22 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
@@ -681,4 +681,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal developer, project.developers.find(:first)
assert_equal project, developer.projects.find(:first)
end
+
+ def test_dynamic_find_should_respect_association_include
+ # SQL error in sort clause if :include is not included
+ # due to Unknown column 'authors.id'
+ assert Category.find(1).posts_with_authors_sorted_by_author_id.find_by_title('Welcome to the weblog')
+ end
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index b638143c5a..b9c7ec6377 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -129,6 +129,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name
end
+ def test_finding_using_primary_key
+ assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name
+ end
+
def test_finding_using_sql
firm = Firm.find(:first)
first_client = firm.clients_using_sql.first
@@ -345,7 +349,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_invalid_adding_with_validate_false
firm = Firm.find(:first)
client = Client.new
- firm.unvalidated_clients_of_firm << Client.new
+ firm.unvalidated_clients_of_firm << client
assert firm.valid?
assert !client.valid?
@@ -353,6 +357,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert client.new_record?
end
+ def test_valid_adding_with_validate_false
+ no_of_clients = Client.count
+
+ firm = Firm.find(:first)
+ client = Client.new("name" => "Apple")
+
+ assert firm.valid?
+ assert client.valid?
+ assert client.new_record?
+
+ firm.unvalidated_clients_of_firm << client
+
+ assert firm.save
+ assert !client.new_record?
+ assert_equal no_of_clients+1, Client.count
+ end
+
def test_build
company = companies(:first_firm)
new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
@@ -367,6 +388,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, company.clients_of_firm(true).size
end
+ def test_collection_size_after_building
+ company = companies(:first_firm) # company already has one client
+ company.clients_of_firm.build("name" => "Another Client")
+ company.clients_of_firm.build("name" => "Yet Another Client")
+ assert_equal 3, company.clients_of_firm.size
+ end
+
def test_build_many
company = companies(:first_firm)
new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
@@ -397,6 +425,37 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, first_topic.replies.to_ary.size
end
+ def test_build_via_block
+ company = companies(:first_firm)
+ new_client = assert_no_queries { company.clients_of_firm.build {|client| client.name = "Another Client" } }
+ assert !company.clients_of_firm.loaded?
+
+ assert_equal "Another Client", new_client.name
+ assert new_client.new_record?
+ assert_equal new_client, company.clients_of_firm.last
+ company.name += '-changed'
+ assert_queries(2) { assert company.save }
+ assert !new_client.new_record?
+ assert_equal 2, company.clients_of_firm(true).size
+ end
+
+ def test_build_many_via_block
+ company = companies(:first_firm)
+ new_clients = assert_no_queries do
+ company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) do |client|
+ client.name = "changed"
+ end
+ end
+
+ assert_equal 2, new_clients.size
+ assert_equal "changed", new_clients.first.name
+ assert_equal "changed", new_clients.last.name
+
+ company.name += '-changed'
+ assert_queries(3) { assert company.save }
+ assert_equal 3, company.clients_of_firm(true).size
+ end
+
def test_create_without_loading_association
first_firm = companies(:first_firm)
Firm.column_names
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 05155f6303..be5170f830 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -187,4 +187,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
post.people_with_callbacks.clear
assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort
end
+
+ def test_dynamic_find_should_respect_association_include
+ # SQL error in sort clause if :include is not included
+ # due to Unknown column 'comments.id'
+ assert Person.find(1).posts_with_comments_sorted_by_comment_id.find_by_title('Welcome to the weblog')
+ end
end
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index d3ca0cae41..99639849a5 100755
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -29,6 +29,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
end
+ def test_finding_using_primary_key
+ firm = companies(:first_firm)
+ assert_equal Account.find_by_firm_id(firm.id), firm.account
+ firm.firm_id = companies(:rails_core).id
+ assert_equal accounts(:rails_core_account), firm.account_using_primary_key
+ end
+
def test_can_marshal_has_one_association_with_nil_target
firm = Firm.new
assert_nothing_raised do
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 59349dd7cf..4904feeb7d 100755
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -189,6 +189,114 @@ class AssociationProxyTest < ActiveRecord::TestCase
end
end
+ def test_belongs_to_mass_assignment
+ post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
+ author_attributes = { :name => 'David Dollar' }
+
+ assert_no_difference 'Author.count' do
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ Post.create(post_attributes.merge({:author => author_attributes}))
+ end
+ end
+
+ assert_difference 'Author.count' do
+ post = Post.create(post_attributes.merge({:creatable_author => author_attributes}))
+ assert_equal post.creatable_author.name, author_attributes[:name]
+ end
+ end
+
+ def test_has_one_mass_assignment
+ post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
+ comment_attributes = { :body => 'Setter Takes Hash' }
+
+ assert_no_difference 'Comment.count' do
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ Post.create(post_attributes.merge({:uncreatable_comment => comment_attributes}))
+ end
+ end
+
+ assert_difference 'Comment.count' do
+ post = Post.create(post_attributes.merge({:creatable_comment => comment_attributes}))
+ assert_equal post.creatable_comment.body, comment_attributes[:body]
+ end
+ end
+
+ def test_has_many_mass_assignment
+ post = posts(:welcome)
+ post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
+ comment_attributes = { :body => 'Setter Takes Hash' }
+
+ assert_no_difference 'Comment.count' do
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ Post.create(post_attributes.merge({:comments => [comment_attributes]}))
+ end
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ post.comments << comment_attributes
+ end
+ end
+
+ assert_difference 'Comment.count' do
+ post = Post.create(post_attributes.merge({:creatable_comments => [comment_attributes]}))
+ assert_equal post.creatable_comments.last.body, comment_attributes[:body]
+ end
+
+ assert_difference 'Comment.count' do
+ post.creatable_comments << comment_attributes
+ assert_equal post.comments.last.body, comment_attributes[:body]
+ end
+
+ post.creatable_comments = [comment_attributes, comment_attributes]
+ assert_equal post.creatable_comments.count, 2
+ end
+
+ def test_has_and_belongs_to_many_mass_assignment
+ post = posts(:welcome)
+ post_attributes = { :title => 'Associations', :body => 'Are They Accessible?' }
+ category_attributes = { :name => 'Accessible Association', :type => 'Category' }
+
+ assert_no_difference 'Category.count' do
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ Post.create(post_attributes.merge({:categories => [category_attributes]}))
+ end
+ assert_raise(ActiveRecord::AssociationTypeMismatch) do
+ post.categories << category_attributes
+ end
+ end
+
+ assert_difference 'Category.count' do
+ post = Post.create(post_attributes.merge({:creatable_categories => [category_attributes]}))
+ assert_equal post.creatable_categories.last.name, category_attributes[:name]
+ end
+
+ assert_difference 'Category.count' do
+ post.creatable_categories << category_attributes
+ assert_equal post.creatable_categories.last.name, category_attributes[:name]
+ end
+
+ post.creatable_categories = [category_attributes, category_attributes]
+ assert_equal post.creatable_categories.count, 2
+ end
+
+ def test_association_proxy_setter_can_take_hash
+ special_comment_attributes = { :body => 'Setter Takes Hash' }
+
+ post = posts(:welcome)
+ post.creatable_comment = { :body => 'Setter Takes Hash' }
+
+ assert_equal post.creatable_comment.body, special_comment_attributes[:body]
+ end
+
+ def test_association_collection_can_take_hash
+ post_attributes = { :title => 'Setter Takes', :body => 'Hash' }
+ david = authors(:david)
+
+ post = (david.posts << post_attributes).last
+ assert_equal post.title, post_attributes[:title]
+
+ david.posts = [post_attributes, post_attributes]
+ assert_equal david.posts.count, 2
+ end
+
def setup_dangling_association
josh = Author.create(:name => "Josh")
p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh)
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index a4be629fbd..9e4f268db7 100755
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -19,6 +19,7 @@ require 'models/warehouse_thing'
require 'rexml/document'
class Category < ActiveRecord::Base; end
+class Categorization < ActiveRecord::Base; end
class Smarts < ActiveRecord::Base; end
class CreditCard < ActiveRecord::Base
class PinNumber < ActiveRecord::Base
@@ -75,7 +76,7 @@ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
end
class BasicsTest < ActiveRecord::TestCase
- fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations
def test_table_exists
assert !NonExistentTable.table_exists?
@@ -130,7 +131,7 @@ class BasicsTest < ActiveRecord::TestCase
def test_read_attributes_before_type_cast
category = Category.new({:name=>"Test categoty", :type => nil})
- category_attrs = {"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
@@ -614,6 +615,22 @@ class BasicsTest < ActiveRecord::TestCase
assert_equal -2, Topic.find(2).replies_count
end
+ def test_update_counter
+ category = Category.first
+ assert_nil category.categorizations_count
+ assert_equal 2, category.categorizations.count
+
+ Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
+ category.reload
+ assert_not_nil category.categorizations_count
+ assert_equal 2, category.categorizations_count
+
+ Category.update_counters(category.id, "categorizations_count" => category.categorizations.count)
+ category.reload
+ assert_not_nil category.categorizations_count
+ assert_equal 4, category.categorizations_count
+ end
+
def test_update_all
assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
assert_equal "bulk updated!", Topic.find(1).content
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
new file mode 100644
index 0000000000..540f42f4b6
--- /dev/null
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -0,0 +1,36 @@
+require "cases/helper"
+
+class ColumnDefinitionTest < ActiveRecord::TestCase
+ def setup
+ @adapter = ActiveRecord::ConnectionAdapters::AbstractAdapter.new(nil)
+ def @adapter.native_database_types
+ {:string => "varchar"}
+ end
+ end
+
+ # Avoid column definitions in create table statements like:
+ # `title` varchar(255) DEFAULT NULL NULL
+ def test_should_not_include_default_clause_when_default_is_null
+ column = ActiveRecord::ConnectionAdapters::Column.new("title", nil, "varchar(20)")
+ column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
+ @adapter, column.name, "string",
+ column.limit, column.precision, column.scale, column.default, column.null)
+ assert_equal "title varchar(20) NULL", column_def.to_sql
+ end
+
+ def test_should_include_default_clause_when_default_is_present
+ column = ActiveRecord::ConnectionAdapters::Column.new("title", "Hello", "varchar(20)")
+ column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
+ @adapter, column.name, "string",
+ column.limit, column.precision, column.scale, column.default, column.null)
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello' NULL}, column_def.to_sql
+ end
+
+ def test_should_specify_not_null_if_null_option_is_false
+ column = ActiveRecord::ConnectionAdapters::Column.new("title", "Hello", "varchar(20)", false)
+ column_def = ActiveRecord::ConnectionAdapters::ColumnDefinition.new(
+ @adapter, column.name, "string",
+ column.limit, column.precision, column.scale, column.default, column.null)
+ assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql
+ end
+end \ No newline at end of file
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index c011ffaf57..e5e022050d 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -2,6 +2,7 @@ require 'cases/helper'
require 'models/topic' # For booleans
require 'models/pirate' # For timestamps
require 'models/parrot'
+require 'models/person' # For optimistic locking
class Pirate # Just reopening it, not defining it
attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
@@ -54,6 +55,33 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_zero_to_blank_marked_as_changed
+ pirate = Pirate.new
+ pirate.catchphrase = "Yarrrr, me hearties"
+ pirate.parrot_id = 1
+ pirate.save
+
+ # check the change from 1 to ''
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
+ pirate.parrot_id = ''
+ assert pirate.parrot_id_changed?
+ assert_equal([1, nil], pirate.parrot_id_change)
+ pirate.save
+
+ # check the change from nil to 0
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
+ pirate.parrot_id = 0
+ assert pirate.parrot_id_changed?
+ assert_equal([nil, 0], pirate.parrot_id_change)
+ pirate.save
+
+ # check the change from 0 to ''
+ pirate = Pirate.find_by_catchphrase("Yarrrr, me hearties")
+ pirate.parrot_id = ''
+ assert pirate.parrot_id_changed?
+ assert_equal([0, nil], pirate.parrot_id_change)
+ end
+
def test_object_should_be_changed_if_any_attribute_is_changed
pirate = Pirate.new
assert !pirate.changed?
@@ -125,6 +153,24 @@ class DirtyTest < ActiveRecord::TestCase
end
end
+ def test_partial_update_with_optimistic_locking
+ person = Person.new(:first_name => 'foo')
+ old_lock_version = 1
+
+ with_partial_updates Person, false do
+ assert_queries(2) { 2.times { person.save! } }
+ Person.update_all({ :first_name => 'baz' }, :id => person.id)
+ end
+
+ with_partial_updates Person, true do
+ assert_queries(0) { 2.times { person.save! } }
+ assert_equal old_lock_version, person.reload.lock_version
+
+ assert_queries(1) { person.first_name = 'bar'; person.save! }
+ assert_not_equal old_lock_version, person.reload.lock_version
+ end
+ end
+
def test_changed_attributes_should_be_preserved_if_save_failure
pirate = Pirate.new
pirate.parrot_id = 1
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 80936d51f3..b97db73b68 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -1,5 +1,6 @@
require "cases/helper"
require 'models/author'
+require 'models/categorization'
require 'models/comment'
require 'models/company'
require 'models/topic'
@@ -199,6 +200,23 @@ class FinderTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
end
+ def test_find_on_hash_conditions_with_hashed_table_name
+ assert Topic.find(1, :conditions => {:topics => { :approved => false }})
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => {:topics => { :approved => true }}) }
+ end
+
+ def test_find_with_hash_conditions_on_joined_table
+ firms = Firm.all :joins => :account, :conditions => {:accounts => { :credit_limit => 50 }}
+ assert_equal 1, firms.size
+ assert_equal companies(:first_firm), firms.first
+ end
+
+ def test_find_with_hash_conditions_on_joined_table_and_with_range
+ firms = DependentFirm.all :joins => :account, :conditions => {:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}
+ assert_equal 1, firms.size
+ assert_equal companies(:rails_core), firms.first
+ end
+
def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
david = customers(:david)
assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
@@ -394,6 +412,12 @@ class FinderTest < ActiveRecord::TestCase
assert_equal '1,1,1', bind('?', os)
end
+ def test_named_bind_with_postgresql_type_casts
+ l = Proc.new { bind(":a::integer '2009-01-01'::date", :a => '10') }
+ assert_nothing_raised(&l)
+ assert_equal "#{ActiveRecord::Base.quote_value('10')}::integer '2009-01-01'::date", l.call
+ end
+
def test_string_sanitation
assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index aca7cfb367..6ba7597f56 100755
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -15,6 +15,7 @@ require 'models/pirate'
require 'models/treasure'
require 'models/matey'
require 'models/ship'
+require 'models/book'
class FixturesTest < ActiveRecord::TestCase
self.use_instantiated_fixtures = true
@@ -373,6 +374,34 @@ class CheckSetTableNameFixturesTest < ActiveRecord::TestCase
end
end
+class FixtureNameIsNotTableNameFixturesTest < ActiveRecord::TestCase
+ set_fixture_class :items => Book
+ fixtures :items
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_named_accessor
+ assert_kind_of Book, items(:dvd)
+ end
+end
+
+class FixtureNameIsNotTableNameMultipleFixturesTest < ActiveRecord::TestCase
+ set_fixture_class :items => Book, :funny_jokes => Joke
+ fixtures :items, :funny_jokes
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_named_accessor_of_differently_named_fixture
+ assert_kind_of Book, items(:dvd)
+ end
+
+ def test_named_accessor_of_same_named_fixture
+ assert_kind_of Joke, funny_jokes(:a_joke)
+ end
+end
+
class CustomConnectionFixturesTest < ActiveRecord::TestCase
set_fixture_class :courses => Course
fixtures :courses
@@ -432,11 +461,11 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase
alias_method :teardown, :blank_teardown
def test_no_rollback_in_teardown_unless_transaction_active
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
assert_raise(RuntimeError) { ar_setup_fixtures }
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
assert_nothing_raised { ar_teardown_fixtures }
- assert_equal 0, Thread.current['open_transactions']
+ assert_equal 0, ActiveRecord::Base.connection.open_transactions
end
private
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index dc83300efa..0530ba9bd9 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -32,13 +32,13 @@ end
ActiveRecord::Base.connection.class.class_eval do
IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
- def execute_with_counting(sql, name = nil, &block)
- $query_count ||= 0
- $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
- execute_without_counting(sql, name, &block)
+ def execute_with_query_record(sql, name = nil, &block)
+ $queries_executed ||= []
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
+ execute_without_query_record(sql, name, &block)
end
- alias_method_chain :execute, :counting
+ alias_method_chain :execute, :query_record
end
# Make with_scope public for tests
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 47fb5c608f..4fd38bfbc9 100755
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -191,6 +191,13 @@ class InheritanceTest < ActiveRecord::TestCase
assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed"
end
+ def test_eager_load_belongs_to_primary_key_quoting
+ con = Account.connection
+ assert_sql(/\(#{con.quote_table_name('companies')}.#{con.quote_column_name('id')} IN \(1\)\)/) do
+ Account.find(1, :include => :firm)
+ end
+ end
+
def test_alt_eager_loading
switch_to_alt_inheritance_column
test_eager_load_belongs_to_something_inherited
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index 258f7c7a0f..3432abee31 100755
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -143,12 +143,20 @@ class LifecycleTest < ActiveRecord::TestCase
assert_equal developer.name, multi_observer.record.name
end
- def test_observing_after_find_when_not_defined_on_the_model
+ def test_after_find_cannot_be_observed_when_its_not_defined_on_the_model
observer = MinimalisticObserver.instance
assert_equal Minimalistic, MinimalisticObserver.observed_class
minimalistic = Minimalistic.find(1)
- assert_equal minimalistic, observer.minimalistic
+ assert_nil observer.minimalistic
+ end
+
+ def test_after_find_can_be_observed_when_its_defined_on_the_model
+ observer = TopicObserver.instance
+ assert_equal Topic, TopicObserver.observed_class
+
+ topic = Topic.find(1)
+ assert_equal topic, observer.topic
end
def test_invalid_observer
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index 7db6c570b5..701187223f 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -29,10 +29,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
assert_equal 0, p2.lock_version
+ p1.first_name = 'stu'
p1.save!
assert_equal 1, p1.lock_version
assert_equal 0, p2.lock_version
+ p2.first_name = 'sue'
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
end
@@ -42,11 +44,14 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
assert_equal 0, p2.lock_version
+ p1.first_name = 'stu'
p1.save!
assert_equal 1, p1.lock_version
assert_equal 0, p2.lock_version
+ p2.first_name = 'sue'
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
+ p2.first_name = 'sue2'
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
end
@@ -54,15 +59,18 @@ class OptimisticLockingTest < ActiveRecord::TestCase
p1 = Person.new(:first_name => 'anika')
assert_equal 0, p1.lock_version
+ p1.first_name = 'anika2'
p1.save!
p2 = Person.find(p1.id)
assert_equal 0, p1.lock_version
assert_equal 0, p2.lock_version
+ p1.first_name = 'anika3'
p1.save!
assert_equal 1, p1.lock_version
assert_equal 0, p2.lock_version
+ p2.first_name = 'sue'
assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
end
@@ -81,10 +89,12 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, t1.version
assert_equal 0, t2.version
+ t1.tps_report_number = 700
t1.save!
assert_equal 1, t1.version
assert_equal 0, t2.version
+ t2.tps_report_number = 800
assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
end
@@ -93,6 +103,7 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal 0, p1.lock_version
assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
+ p1.first_name = 'bianca2'
p1.save!
assert_equal 1, p1.lock_version
assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
@@ -146,6 +157,15 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert ref.save
end
+ # Useful for partial updates, don't only update the lock_version if there
+ # is nothing else being updated.
+ def test_update_without_attributes_does_not_only_update_lock_version
+ assert_nothing_raised do
+ p1 = Person.new(:first_name => 'anika')
+ p1.send(:update_with_lock, [])
+ end
+ end
+
private
def add_counter_column_to(model)
diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb
index 1a9a875730..d6b3e341df 100644
--- a/activerecord/test/cases/method_scoping_test.rb
+++ b/activerecord/test/cases/method_scoping_test.rb
@@ -87,6 +87,16 @@ class MethodScopingTest < ActiveRecord::TestCase
assert_equal 1, scoped_developers.size
end
+ def test_scoped_find_joins
+ scoped_developers = Developer.with_scope(:find => { :joins => 'JOIN developers_projects ON id = developer_id' } ) do
+ Developer.find(:all, :conditions => 'developers_projects.project_id = 2')
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ assert_equal developers(:david).attributes, scoped_developers.first.attributes
+ end
+
def test_scoped_count_include
# with the include, will retrieve only developers for the given project
Developer.with_scope(:find => { :include => :projects }) do
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index f36255e209..7ecf755ef8 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -3,6 +3,7 @@ require 'bigdecimal/util'
require 'models/person'
require 'models/topic'
+require 'models/developer'
require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
@@ -153,9 +154,10 @@ if ActiveRecord::Base.connection.supports_migrations?
t.column :default_int, :integer
- t.column :one_int, :integer, :limit => 1
- t.column :four_int, :integer, :limit => 4
- t.column :eight_int, :integer, :limit => 8
+ t.column :one_int, :integer, :limit => 1
+ t.column :four_int, :integer, :limit => 4
+ t.column :eight_int, :integer, :limit => 8
+ t.column :eleven_int, :integer, :limit => 11
end
end
@@ -167,12 +169,20 @@ if ActiveRecord::Base.connection.supports_migrations?
one = columns.detect { |c| c.name == "one_int" }
four = columns.detect { |c| c.name == "four_int" }
eight = columns.detect { |c| c.name == "eight_int" }
+ eleven = columns.detect { |c| c.name == "eleven_int" }
if current_adapter?(:PostgreSQLAdapter)
assert_equal 'integer', default.sql_type
assert_equal 'smallint', one.sql_type
assert_equal 'integer', four.sql_type
assert_equal 'bigint', eight.sql_type
+ assert_equal 'integer', eleven.sql_type
+ elsif current_adapter?(:MysqlAdapter)
+ assert_match 'int(11)', default.sql_type
+ assert_match 'tinyint', one.sql_type
+ assert_match 'int', four.sql_type
+ assert_match 'bigint', eight.sql_type
+ assert_match 'int(11)', eleven.sql_type
elsif current_adapter?(:OracleAdapter)
assert_equal 'NUMBER(38)', default.sql_type
assert_equal 'NUMBER(1)', one.sql_type
@@ -483,6 +493,37 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
+ def test_rename_column_preserves_default_value_not_null
+ begin
+ default_before = Developer.connection.columns("developers").find { |c| c.name == "salary" }.default
+ assert_equal 70000, default_before
+ Developer.connection.rename_column "developers", "salary", "anual_salary"
+ Developer.reset_column_information
+ assert Developer.column_names.include?("anual_salary")
+ default_after = Developer.connection.columns("developers").find { |c| c.name == "anual_salary" }.default
+ assert_equal 70000, default_after
+ ensure
+ Developer.connection.rename_column "developers", "anual_salary", "salary"
+ Developer.reset_column_information
+ end
+ end
+
+ def test_rename_nonexistent_column
+ ActiveRecord::Base.connection.create_table(:hats) do |table|
+ table.column :hat_name, :string, :default => nil
+ end
+ exception = if current_adapter?(:PostgreSQLAdapter)
+ ActiveRecord::StatementInvalid
+ else
+ ActiveRecord::ActiveRecordError
+ end
+ assert_raises(exception) do
+ Person.connection.rename_column "hats", "nonexistent", "should_fail"
+ end
+ ensure
+ ActiveRecord::Base.connection.drop_table(:hats)
+ end
+
def test_rename_column_with_sql_reserved_word
begin
assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
@@ -662,6 +703,55 @@ if ActiveRecord::Base.connection.supports_migrations?
Person.connection.drop_table :testings rescue nil
end
+ def test_keeping_default_and_notnull_constaint_on_change
+ Person.connection.create_table :testings do |t|
+ t.column :title, :string
+ end
+ person_klass = Class.new(Person)
+ person_klass.set_table_name 'testings'
+
+ person_klass.connection.add_column "testings", "wealth", :integer, :null => false, :default => 99
+ person_klass.reset_column_information
+ assert_equal 99, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+ assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")}
+
+ # change column default to see that column doesn't lose its not null definition
+ person_klass.connection.change_column_default "testings", "wealth", 100
+ person_klass.reset_column_information
+ assert_equal 100, person_klass.columns_hash["wealth"].default
+ assert_equal false, person_klass.columns_hash["wealth"].null
+
+ # rename column to see that column doesn't lose its not null and/or default definition
+ person_klass.connection.rename_column "testings", "wealth", "money"
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["wealth"]
+ assert_equal 100, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column
+ person_klass.connection.change_column "testings", "money", :integer, :null => false, :default => 1000
+ person_klass.reset_column_information
+ assert_equal 1000, person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+
+ # change column, make it nullable and clear default
+ person_klass.connection.change_column "testings", "money", :integer, :null => true, :default => nil
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal true, person_klass.columns_hash["money"].null
+
+ # change_column_null, make it not nullable and set null values to a default value
+ person_klass.connection.execute('UPDATE testings SET money = NULL')
+ person_klass.connection.change_column_null "testings", "money", false, 2000
+ person_klass.reset_column_information
+ assert_nil person_klass.columns_hash["money"].default
+ assert_equal false, person_klass.columns_hash["money"].null
+ assert_equal [2000], Person.connection.select_values("SELECT money FROM testings").map { |s| s.to_i }.sort
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
def test_change_column_default_to_null
Person.connection.change_column_default "people", "first_name", nil
Person.reset_column_information
@@ -799,6 +889,21 @@ if ActiveRecord::Base.connection.supports_migrations?
assert !Reminder.table_exists?
end
+ def test_migrator_double_up
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
+ assert_nothing_raised { ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1) }
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_double_down
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 1)
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1)
+ assert_nothing_raised { ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 1) }
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
def test_finds_migrations
migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
[['1', 'people_have_last_names'],
@@ -888,16 +993,6 @@ if ActiveRecord::Base.connection.supports_migrations?
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
assert_equal(0, ActiveRecord::Migrator.current_version)
end
-
- def test_migrator_run
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
- assert_equal(0, ActiveRecord::Migrator.current_version)
-
- assert_equal(0, ActiveRecord::Migrator.current_version)
- ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
- assert_equal(0, ActiveRecord::Migrator.current_version)
- end
def test_schema_migrations_table_name
ActiveRecord::Base.table_name_prefix = "prefix_"
@@ -1206,10 +1301,10 @@ if ActiveRecord::Base.connection.supports_migrations?
end
def integer_column
- if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
- "integer"
- else
+ if current_adapter?(:MysqlAdapter)
'int(11)'
+ else
+ 'integer'
end
end
diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb
index eb3e43c8ac..7c3e0f2ca6 100644
--- a/activerecord/test/cases/multiple_db_test.rb
+++ b/activerecord/test/cases/multiple_db_test.rb
@@ -57,4 +57,29 @@ class MultipleDbTest < ActiveRecord::TestCase
assert Course.connection
end
+
+ def test_transactions_across_databases
+ c1 = Course.find(1)
+ e1 = Entrant.find(1)
+
+ begin
+ Course.transaction do
+ Entrant.transaction do
+ c1.name = "Typo"
+ e1.name = "Typo"
+ c1.save
+ e1.save
+ raise "No I messed up."
+ end
+ end
+ rescue
+ # Yup caught it
+ end
+
+ assert_equal "Typo", c1.name
+ assert_equal "Typo", e1.name
+
+ assert_equal "Ruby Development", Course.find(1).name
+ assert_equal "Ruby Developer", Entrant.find(1).name
+ end
end
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index d890cf7936..0c1eb23428 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -6,7 +6,7 @@ require 'models/reply'
require 'models/author'
class NamedScopeTest < ActiveRecord::TestCase
- fixtures :posts, :authors, :topics, :comments
+ fixtures :posts, :authors, :topics, :comments, :author_addresses
def test_implements_enumerable
assert !Topic.find(:all).empty?
@@ -59,6 +59,16 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count
end
+ def test_scopes_with_string_name_can_be_composed
+ # NOTE that scopes defined with a string as a name worked on their own
+ # but when called on another scope the other scope was completely replaced
+ assert_equal Topic.replied.approved, Topic.replied.approved_as_string
+ end
+
+ def test_scopes_can_be_specified_with_deep_hash_conditions
+ assert_equal Topic.replied.approved, Topic.replied.approved_as_hash_condition
+ end
+
def test_scopes_are_composable
assert_equal (approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved
assert_equal (replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied
@@ -77,6 +87,25 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on)
end
+ def test_scopes_with_joins
+ address = author_addresses(:david_address)
+ posts_with_authors_at_address = Post.find(
+ :all, :joins => 'JOIN authors ON authors.id = posts.author_id',
+ :conditions => [ 'authors.author_address_id = ?', address.id ]
+ )
+ assert_equal posts_with_authors_at_address, Post.with_authors_at_address(address)
+ end
+
+ def test_scopes_with_joins_respects_custom_select
+ address = author_addresses(:david_address)
+ posts_with_authors_at_address_titles = Post.find(:all,
+ :select => 'title',
+ :joins => 'JOIN authors ON authors.id = posts.author_id',
+ :conditions => [ 'authors.author_address_id = ?', address.id ]
+ )
+ assert_equal posts_with_authors_at_address_titles, Post.with_authors_at_address(address).find(:all, :select => 'title')
+ end
+
def test_extensions
assert_equal 1, Topic.anonymous_extension.one
assert_equal 2, Topic.named_extension.two
@@ -154,4 +183,30 @@ class NamedScopeTest < ActiveRecord::TestCase
topics.empty? # use loaded (no query)
end
end
+
+ def test_should_build_with_proxy_options
+ topic = Topic.approved.build({})
+ assert topic.approved
+ end
+
+ def test_should_build_new_with_proxy_options
+ topic = Topic.approved.new
+ assert topic.approved
+ end
+
+ def test_should_create_with_proxy_options
+ topic = Topic.approved.create({})
+ assert topic.approved
+ end
+
+ def test_should_create_with_bang_with_proxy_options
+ topic = Topic.approved.create!({})
+ assert topic.approved
+ end
+
+ def test_should_build_with_proxy_options_chained
+ topic = Topic.approved.by_lifo.build({})
+ assert topic.approved
+ assert_equal 'lifo', topic.author_name
+ end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 0c57b79401..723062e3b8 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 22, Firm.reflect_on_all_associations.size
- assert_equal 17, Firm.reflect_on_all_associations(:has_many).size
- assert_equal 5, Firm.reflect_on_all_associations(:has_one).size
+ assert_equal 24, Firm.reflect_on_all_associations.size
+ assert_equal 18, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 6, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index c42b0efba0..ee7e285a73 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -72,6 +72,52 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{:null => false}, output
end
+ def test_schema_dump_includes_limit_constraint_for_integer_columns
+ stream = StringIO.new
+
+ ActiveRecord::SchemaDumper.ignore_tables = [/^(?!integer_limits)/]
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_match %r{c_int_1.*:limit => 2}, output
+ assert_match %r{c_int_2.*:limit => 2}, output
+
+ # int 3 is 4 bytes in postgresql
+ assert_match %r{c_int_3.*}, output
+ assert_no_match %r{c_int_3.*:limit}, output
+
+ assert_match %r{c_int_4.*}, output
+ assert_no_match %r{c_int_4.*:limit}, output
+ elsif current_adapter?(:MysqlAdapter)
+ 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
+
+ assert_match %r{c_int_4.*}, output
+ assert_no_match %r{c_int_4.*:limit}, output
+ elsif current_adapter?(:SQLiteAdapter)
+ 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
+ assert_match %r{c_int_4.*:limit => 4}, output
+ end
+ assert_match %r{c_int_without_limit.*}, output
+ assert_no_match %r{c_int_without_limit.*:limit}, output
+
+ if current_adapter?(:SQLiteAdapter)
+ assert_match %r{c_int_5.*:limit => 5}, output
+ assert_match %r{c_int_6.*:limit => 6}, output
+ assert_match %r{c_int_7.*:limit => 7}, output
+ assert_match %r{c_int_8.*:limit => 8}, output
+ else
+ assert_match %r{c_int_5.*:limit => 8}, output
+ assert_match %r{c_int_6.*:limit => 8}, output
+ assert_match %r{c_int_7.*:limit => 8}, output
+ assert_match %r{c_int_8.*:limit => 8}, output
+ end
+ end
+
def test_schema_dump_with_string_ignored_table
stream = StringIO.new
diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb
index ad27ac951c..60b00b3e8f 100755
--- a/activerecord/test/cases/validations_test.rb
+++ b/activerecord/test/cases/validations_test.rb
@@ -1061,6 +1061,18 @@ class ValidationsTest < ActiveRecord::TestCase
end
end
+ def test_validates_length_of_with_block
+ Topic.validates_length_of :content, :minimum => 5, :too_short=>"Your essay must be at least %d words.",
+ :tokenizer => lambda {|str| str.scan(/\w+/) }
+ t = Topic.create!(:content => "this content should be long enough")
+ assert t.valid?
+
+ t.content = "not long enough"
+ assert !t.valid?
+ assert t.errors.on(:content)
+ assert_equal "Your essay must be at least 5 words.", t.errors[:content]
+ end
+
def test_validates_size_of_association_utf8
with_kcode('UTF8') do
assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
@@ -1381,6 +1393,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
INTEGERS = [0, 10, -10] + INTEGER_STRINGS
BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
+ INFINITY = [1.0/0.0]
def setup
Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
@@ -1392,27 +1405,27 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
Topic.validates_numericality_of :approved
invalid!(NIL + BLANK + JUNK)
- valid!(FLOATS + INTEGERS + BIGDECIMAL)
+ valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
def test_validates_numericality_of_with_nil_allowed
Topic.validates_numericality_of :approved, :allow_nil => true
invalid!(BLANK + JUNK)
- valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY)
end
def test_validates_numericality_of_with_integer_only
Topic.validates_numericality_of :approved, :only_integer => true
- invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
valid!(INTEGERS)
end
def test_validates_numericality_of_with_integer_only_and_nil_allowed
Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
- invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
+ invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY)
valid!(NIL + INTEGERS)
end
@@ -1433,7 +1446,7 @@ class ValidatesNumericalityTest < ActiveRecord::TestCase
def test_validates_numericality_with_equal_to
Topic.validates_numericality_of :approved, :equal_to => 10
- invalid!([-10, 11], 'must be equal to 10')
+ invalid!([-10, 11] + INFINITY, 'must be equal to 10')
valid!([10])
end
diff --git a/activerecord/test/fixtures/companies.yml b/activerecord/test/fixtures/companies.yml
index c61128c09b..e7691fde46 100644
--- a/activerecord/test/fixtures/companies.yml
+++ b/activerecord/test/fixtures/companies.yml
@@ -5,6 +5,7 @@ first_client:
client_of: 2
name: Summit
ruby_type: Client
+ firm_name: 37signals
first_firm:
id: 1
diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb
index f63af27403..136dc39cf3 100644
--- a/activerecord/test/models/author.rb
+++ b/activerecord/test/models/author.rb
@@ -1,6 +1,7 @@
class Author < ActiveRecord::Base
- has_many :posts
+ has_many :posts, :accessible => true
has_many :posts_with_comments, :include => :comments, :class_name => "Post"
+ has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
has_many :posts_with_categories, :include => :categories, :class_name => "Post"
has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
has_many :posts_containing_the_letter_a, :class_name => "Post"
diff --git a/activerecord/test/models/category.rb b/activerecord/test/models/category.rb
index f1d2e4805a..1660c61682 100644
--- a/activerecord/test/models/category.rb
+++ b/activerecord/test/models/category.rb
@@ -2,6 +2,7 @@ class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
has_and_belongs_to_many :special_posts, :class_name => "Post"
has_and_belongs_to_many :other_posts, :class_name => "Post"
+ has_and_belongs_to_many :posts_with_authors_sorted_by_author_id, :class_name => "Post", :include => :authors, :order => "authors.id"
has_and_belongs_to_many(:select_testing_posts,
:class_name => 'Post',
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index 9fa810ac68..e6aa810146 100755
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -46,11 +46,14 @@ class Firm < Company
has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1'
has_many :plain_clients, :class_name => 'Client'
has_many :readonly_clients, :class_name => 'Client', :readonly => true
+ has_many :clients_using_primary_key, :class_name => 'Client',
+ :primary_key => 'name', :foreign_key => 'firm_name'
has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true
has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false
has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account'
has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true
+ has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account"
end
class DependentFirm < Company
diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb
index 4f4d695b24..430d0b38f7 100644
--- a/activerecord/test/models/person.rb
+++ b/activerecord/test/models/person.rb
@@ -6,5 +6,5 @@ class Person < ActiveRecord::Base
has_many :references
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'
end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index d9101706b5..e23818eb33 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -1,6 +1,11 @@
class Post < ActiveRecord::Base
named_scope :containing_the_letter_a, :conditions => "body LIKE '%a%'"
-
+ named_scope :with_authors_at_address, lambda { |address| {
+ :conditions => [ 'authors.author_address_id = ?', address.id ],
+ :joins => 'JOIN authors ON authors.id = posts.author_id'
+ }
+ }
+
belongs_to :author do
def greeting
"hello"
@@ -28,6 +33,12 @@ class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id'
+ belongs_to :creatable_author, :class_name => 'Author', :accessible => true
+ has_one :uncreatable_comment, :class_name => 'Comment', :accessible => false, :order => 'id desc'
+ has_one :creatable_comment, :class_name => 'Comment', :accessible => true, :order => 'id desc'
+ has_many :creatable_comments, :class_name => 'Comment', :accessible => true, :dependent => :destroy
+ has_and_belongs_to_many :creatable_categories, :class_name => 'Category', :accessible => true
+
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings do
def add_joins_and_select
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index f63e862cfd..39ca1bf42a 100755
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -4,6 +4,10 @@ class Topic < ActiveRecord::Base
{ :conditions => ['written_on < ?', time] }
}
named_scope :approved, :conditions => {:approved => true}
+ named_scope :by_lifo, :conditions => {:author_name => 'lifo'}
+
+ named_scope :approved_as_hash_condition, :conditions => {:topics => {:approved => true}}
+ named_scope 'approved_as_string', :conditions => {:approved => true}
named_scope :replied, :conditions => ['replies_count > 0']
named_scope :anonymous_extension do
def one
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 423929fd55..487a08f4ab 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -66,6 +66,7 @@ ActiveRecord::Schema.define do
create_table :categories, :force => true do |t|
t.string :name, :null => false
t.string :type
+ t.integer :categorizations_count
end
create_table :categories_posts, :force => true, :id => false do |t|
@@ -102,6 +103,7 @@ ActiveRecord::Schema.define do
t.string :type
t.string :ruby_type
t.integer :firm_id
+ t.string :firm_name
t.string :name
t.integer :client_of
t.integer :rating, :default => 1
@@ -407,6 +409,13 @@ ActiveRecord::Schema.define do
t.column :key, :string
end
+ create_table :integer_limits, :force => true do |t|
+ t.integer :"c_int_without_limit"
+ (1..8).each do |i|
+ t.integer :"c_int_#{i}", :limit => i
+ end
+ 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/Rakefile b/activeresource/Rakefile
index 9fd0ec4921..8c3ad36a02 100644
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -42,9 +42,10 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Active Resource -- Object-oriented REST services"
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/**/*.rb')
+ rdoc.rdoc_files.exclude('lib/activeresource.rb')
}
@@ -114,13 +115,13 @@ end
desc "Publish the beta gem"
task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload
end
desc "Publish the release files to RubyForge."
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index d87558aba9..8d3b136d80 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,12 +1,36 @@
*Edge*
+* Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example:
+
+ a = (1..10).to_a
+ a.in_groups(3) #=> [[1, 2, 3, 4], [5, 6, 7, nil], [8, 9, 10, nil]]
+ a.in_groups(3, false) #=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
+
+* Fix TimeWithZone unmarshaling: coerce unmarshaled Time instances to utc, because Ruby's marshaling of Time instances doesn't respect the zone [Geoff Buesing]
+
+* Added Memoizable mixin for caching simple lazy loaded attributes [Josh Peek]
+
+* Move the test related core_ext stuff out of core_ext so it's only loaded by the test helpers. [Michael Koziarski]
+
+* Add Inflection rules for String#humanize. #535 [dcmanges]
+
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.human(/_cnt$/i, '\1_count')
+ end
+
+ 'jargon_cnt'.humanize # => 'Jargon count'
+
+* TimeWithZone: when crossing DST boundary, treat Durations of days, months or years as variable-length, and all other values as absolute length. A time + 24.hours will advance exactly 24 hours, but a time + 1.day will advance 23-25 hours, depending on the day. Ensure consistent behavior across all advancing methods [Geoff Buesing]
+
+* Added TimeZone #=~, to support matching zones by regex in time_zone_select. #195 [Ernie Miller]
+
* Added Array#second through Array#tenth as aliases for Array#[1] through Array#[9] [DHH]
* Added test/do declaration style testing to ActiveSupport::TestCase [DHH via Jay Fields]
* Added Object#present? which is equivalent to !Object#blank? [DHH]
-* Added Enumberable#many? to encapsulate collection.size > 1 [DHH]
+* Added Enumberable#many? to encapsulate collection.size > 1 [DHH/Damian Janowski]
* Add more standard Hash methods to ActiveSupport::OrderedHash [Steve Purcell]
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index e51bf08f8d..f2885c69a4 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -33,11 +33,11 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Active Support -- Utility classes and standard library extensions from Rails"
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/active_support.rb')
- rdoc.rdoc_files.include('lib/active_support/*.rb')
rdoc.rdoc_files.include('lib/active_support/**/*.rb')
+ rdoc.rdoc_files.exclude('lib/active_support/vendor/*')
}
spec = Gem::Specification.new do |s|
@@ -65,13 +65,13 @@ end
desc "Publish the beta gem"
task :pgem => [:package] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
- Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/as", "doc").upload
+ Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload
end
desc "Publish the release files to RubyForge."
@@ -172,4 +172,4 @@ namespace :tzinfo do
def excluded_classes
%w(country country_index_definition country_info country_timezone timezone_index_definition timezone_proxy tzdataparser)
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index de50aafe16..1df911a3f2 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -43,6 +43,7 @@ require 'active_support/ordered_hash'
require 'active_support/ordered_options'
require 'active_support/option_merger'
+require 'active_support/memoizable'
require 'active_support/string_inquirer'
require 'active_support/values/time_zone'
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 2f1143e610..3e3dc18263 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -19,19 +19,19 @@ module ActiveSupport
def self.expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
-
+
if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
end
expanded_cache_key << case
- when key.respond_to?(:cache_key)
- key.cache_key
- when key.is_a?(Array)
- key.collect { |element| expand_cache_key(element) }.to_param
- when key.respond_to?(:to_param)
- key.to_param
- end
+ when key.respond_to?(:cache_key)
+ key.cache_key
+ when key.is_a?(Array)
+ key.collect { |element| expand_cache_key(element) }.to_param
+ when key
+ key.to_param
+ end.to_s
expanded_cache_key
end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 59dc96754f..e67b719ddb 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -30,7 +30,7 @@ module ActiveSupport #:nodoc:
# Calls <tt>to_param</tt> on all its elements and joins the result with
# slashes. This is used by <tt>url_for</tt> in Action Pack.
def to_param
- map(&:to_param).join '/'
+ collect { |e| e.to_param }.join '/'
end
# Converts an array into a string suitable for use as a URL query string,
@@ -38,7 +38,8 @@ module ActiveSupport #:nodoc:
#
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
def to_query(key)
- collect { |value| value.to_query("#{key}[]") } * '&'
+ prefix = "#{key}[]"
+ collect { |value| value.to_query(prefix) }.join '&'
end
def self.included(base) #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 767acc4e07..df37afb053 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -4,8 +4,8 @@ module ActiveSupport #:nodoc:
module CoreExtensions #:nodoc:
module Array #:nodoc:
module Grouping
- # Iterates over the array in groups of size +number+, padding any remaining
- # slots with +fill_with+ unless it is +false+.
+ # Splits or iterates over the array in groups of size +number+,
+ # padding any remaining slots with +fill_with+ unless it is +false+.
#
# %w(1 2 3 4 5 6 7).in_groups_of(3) {|g| p g}
# ["1", "2", "3"]
@@ -39,6 +39,49 @@ module ActiveSupport #:nodoc:
end
end
+ # Splits or iterates over the array in +number+ of groups, padding any
+ # remaining slots with +fill_with+ unless it is +false+.
+ #
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|g| p g}
+ # ["1", "2", "3", "4"]
+ # ["5", "6", "7", nil]
+ # ["8", "9", "10", nil]
+ #
+ # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|g| p g}
+ # ["1", "2", "3"]
+ # ["4", "5", "&nbsp;"]
+ # ["6", "7", "&nbsp;"]
+ #
+ # %w(1 2 3 4 5 6 7).in_groups(3, false) {|g| p g}
+ # ["1", "2", "3"]
+ # ["4", "5"]
+ # ["6", "7"]
+ def in_groups(number, fill_with = nil)
+ # size / number gives minor group size;
+ # size % number gives how many objects need extra accomodation;
+ # each group hold either division or division + 1 items.
+ division = size / number
+ modulo = size % number
+
+ # create a new array avoiding dup
+ groups = []
+ start = 0
+
+ number.times do |index|
+ length = division + (modulo > 0 && modulo > index ? 1 : 0)
+ padding = fill_with != false &&
+ modulo > 0 && length == division ? 1 : 0
+ groups << slice(start, length).concat([fill_with] * padding)
+ start += length
+ end
+
+ if block_given?
+ groups.each{|g| yield(g) }
+ else
+ groups
+ end
+ end
+
# Divides the array into one or more subarrays based on a delimiting +value+
# or the result of an optional block.
#
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 9647797ec2..e451e9933a 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -79,7 +79,9 @@ module Enumerable
end
# Returns true if the collection has more than 1 element. Functionally equivalent to collection.size > 1.
- def many?
+ # Works with a block too ala any?, so people.many? { |p| p.age > 26 } # => returns true if more than 1 person is over 26.
+ def many?(&block)
+ size = block_given? ? select(&block).size : self.size
size > 1
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index 40bbebb7c4..45f3e4bf5c 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -1,4 +1,14 @@
class Module
+ # Returns the name of the module containing this one.
+ #
+ # p M::N.parent_name # => "M"
+ def parent_name
+ unless defined? @parent_name
+ @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
+ end
+ @parent_name
+ end
+
# Returns the module which contains this one according to its name.
#
# module M
@@ -16,8 +26,7 @@ class Module
# p Module.new.parent # => Object
#
def parent
- parent_name = name.split('::')[0..-2] * '::'
- parent_name.empty? ? Object : parent_name.constantize
+ parent_name ? parent_name.constantize : Object
end
# Returns all the parents of this module according to its name, ordered from
@@ -35,10 +44,12 @@ class Module
#
def parents
parents = []
- parts = name.split('::')[0..-2]
- until parts.empty?
- parents << (parts * '::').constantize
- parts.pop
+ if parent_name
+ parts = parent_name.split('::')
+ until parts.empty?
+ parents << (parts * '::').constantize
+ parts.pop
+ end
end
parents << Object unless parents.include? Object
parents
@@ -70,6 +81,6 @@ class Module
# Returns the names of the constants defined locally rather than the
# constants themselves. See <tt>local_constants</tt>.
def local_constant_names
- local_constants.map(&:to_s)
+ local_constants.map { |c| c.to_s }
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/model_naming.rb b/activesupport/lib/active_support/core_ext/module/model_naming.rb
index 26e76ab556..5518f5417b 100644
--- a/activesupport/lib/active_support/core_ext/module/model_naming.rb
+++ b/activesupport/lib/active_support/core_ext/module/model_naming.rb
@@ -1,12 +1,13 @@
module ActiveSupport
class ModelName < String
- attr_reader :singular, :plural, :partial_path
+ attr_reader :singular, :plural, :cache_key, :partial_path
def initialize(name)
super
@singular = underscore.tr('/', '_').freeze
@plural = @singular.pluralize.freeze
- @partial_path = "#{tableize}/#{demodulize.underscore}".freeze
+ @cache_key = tableize.freeze
+ @partial_path = "#{@cache_key}/#{demodulize.underscore}".freeze
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
index 9f1d4ed2aa..4ecaab3bbb 100644
--- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb
+++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb
@@ -35,7 +35,7 @@ class Object
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
if RUBY_VERSION >= '1.9'
def instance_variable_names
- instance_variables.map(&:to_s)
+ instance_variables.map { |var| var.to_s }
end
else
alias_method :instance_variable_names, :instance_variables
diff --git a/activesupport/lib/active_support/core_ext/test.rb b/activesupport/lib/active_support/core_ext/test.rb
deleted file mode 100644
index c0b19bdc58..0000000000
--- a/activesupport/lib/active_support/core_ext/test.rb
+++ /dev/null
@@ -1 +0,0 @@
-require 'active_support/core_ext/test/unit/assertions'
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 7a8c4d0326..2f3fa72bb4 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -299,7 +299,7 @@ module ActiveSupport #:nodoc:
# Will the provided constant descriptor be unloaded?
def will_unload?(const_desc)
- autoloaded?(desc) ||
+ autoloaded?(const_desc) ||
explicitly_unloadable_constants.include?(to_constant_name(const_desc))
end
@@ -387,7 +387,7 @@ module ActiveSupport #:nodoc:
ensure
# Remove the stack frames that we added.
if defined?(watch_frames) && ! watch_frames.blank?
- frame_ids = watch_frames.collect(&:object_id)
+ frame_ids = watch_frames.collect { |frame| frame.object_id }
constant_watch_stack.delete_if do |watch_frame|
frame_ids.include? watch_frame.object_id
end
@@ -437,7 +437,7 @@ module ActiveSupport #:nodoc:
protected
def log_call(*args)
if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity
- arg_str = args.collect(&:inspect) * ', '
+ arg_str = args.collect { |arg| arg.inspect } * ', '
/in `([a-z_\?\!]+)'/ =~ caller(1).first
selector = $1 || '<unknown>'
log "called #{selector}(#{arg_str})"
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 47bd6e1767..6651569d33 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -30,10 +30,10 @@ module ActiveSupport
class Inflections
include Singleton
- attr_reader :plurals, :singulars, :uncountables
+ attr_reader :plurals, :singulars, :uncountables, :humans
def initialize
- @plurals, @singulars, @uncountables = [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], [], []
end
# Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
@@ -76,9 +76,20 @@ module ActiveSupport
(@uncountables << words).flatten!
end
+ # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
+ # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
+ # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
+ #
+ # Examples:
+ # human /_cnt$/i, '\1_count'
+ # human "legacy_col_person_name", "Name"
+ def human(rule, replacement)
+ @humans.insert(0, [rule, replacement])
+ end
+
# Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
# Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
- # <tt>:singulars</tt>, <tt>:uncountables</tt>.
+ # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
#
# Examples:
# clear :all
@@ -209,7 +220,10 @@ module ActiveSupport
# "employee_salary" # => "Employee salary"
# "author_id" # => "Author"
def humanize(lower_case_and_underscored_word)
- lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
+ result = lower_case_and_underscored_word.to_s.dup
+
+ inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
+ result.gsub(/_id$/, "").gsub(/_/, " ").capitalize
end
# Removes the module part from the expression in the string.
@@ -307,4 +321,9 @@ module ActiveSupport
end
end
+# in case active_support/inflector is required without the rest of active_support
require 'active_support/inflections'
+require 'active_support/core_ext/string/inflections'
+unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
+ String.send :include, ActiveSupport::CoreExtensions::String::Inflections
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/json/encoders/date_time.rb b/activesupport/lib/active_support/json/encoders/date_time.rb
index d41c3e9786..a4a5efbfb1 100644
--- a/activesupport/lib/active_support/json/encoders/date_time.rb
+++ b/activesupport/lib/active_support/json/encoders/date_time.rb
@@ -8,7 +8,7 @@ class DateTime
if ActiveSupport.use_standard_json_time_format
xmlschema.inspect
else
- %("#{strftime("%Y/%m/%d %H:%M:%S %z")}")
+ strftime('"%Y/%m/%d %H:%M:%S %z"')
end
end
end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
new file mode 100644
index 0000000000..d06250171a
--- /dev/null
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -0,0 +1,35 @@
+module ActiveSupport
+ module Memoizable
+ def self.included(base) #:nodoc:
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def memoize(symbol)
+ original_method = "_unmemoized_#{symbol}"
+ memoized_ivar = "@_memoized_#{symbol}"
+ raise "Already memoized #{symbol}" if instance_methods.map(&:to_s).include?(original_method)
+
+ alias_method original_method, symbol
+ class_eval <<-EOS, __FILE__, __LINE__
+ def #{symbol}
+ if defined? #{memoized_ivar}
+ #{memoized_ivar}
+ else
+ #{memoized_ivar} = #{original_method}
+ end
+ end
+ EOS
+ end
+ end
+
+ def freeze
+ methods.each do |method|
+ if m = method.to_s.match(/\A_unmemoized_(.*)/)
+ send(m[1]).freeze
+ end
+ end
+ super
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 2fd02d5313..0f531b0c79 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -1,11 +1,7 @@
require 'test/unit/testcase'
-require 'active_support/testing/setup_and_teardown'
require 'active_support/testing/default'
+require 'active_support/testing/core_ext/test'
-# TODO: move to core_ext
-class Test::Unit::TestCase #:nodoc:
- include ActiveSupport::Testing::SetupAndTeardown
-end
module ActiveSupport
class TestCase < Test::Unit::TestCase
diff --git a/activesupport/lib/active_support/testing/core_ext/test.rb b/activesupport/lib/active_support/testing/core_ext/test.rb
new file mode 100644
index 0000000000..d3f38f0bc7
--- /dev/null
+++ b/activesupport/lib/active_support/testing/core_ext/test.rb
@@ -0,0 +1,6 @@
+require 'active_support/testing/core_ext/test/unit/assertions'
+require 'active_support/testing/setup_and_teardown'
+
+class Test::Unit::TestCase #:nodoc:
+ include ActiveSupport::Testing::SetupAndTeardown
+end \ No newline at end of file
diff --git a/activesupport/lib/active_support/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
index 77fe325fb4..70a44eab8c 100644
--- a/activesupport/lib/active_support/core_ext/test/unit/assertions.rb
+++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb
@@ -1,9 +1,10 @@
-module Test
- module Unit
+require 'test/unit/assertions'
+module Test
+ module Unit
#--
# FIXME: no Proc#binding in Ruby 2, must change this API
#++
- module Assertions
+ module Assertions
# Test numeric difference between the return value of an expression as a result of what is evaluated
# in the yielded block.
#
diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb
index 24936029a2..5f2027eb3b 100644
--- a/activesupport/lib/active_support/testing/performance.rb
+++ b/activesupport/lib/active_support/testing/performance.rb
@@ -12,13 +12,13 @@ module ActiveSupport
if benchmark = ARGV.include?('--benchmark') # HAX for rake test
{ :benchmark => true,
:runs => 10,
- :metrics => [:process_time, :memory, :objects],
+ :metrics => [:process_time, :memory, :objects, :gc_runs, :gc_time],
:output => 'tmp/performance' }
else
{ :benchmark => false,
:runs => 1,
:min_percent => 0.02,
- :metrics => [:wall_time, :memory, :objects],
+ :metrics => [:process_time, :memory, :objects],
:formats => [:flat, :graph_html, :call_tree],
:output => 'tmp/performance' }
end
@@ -44,7 +44,7 @@ module ActiveSupport
run_profile(klass.new)
result.add_run
else
- $stderr.puts "Skipping unknown metric #{metric_name.inspect}. Expected :process_time, :wall_time, :cpu_time, :memory, or :objects."
+ $stderr.puts '%20s: unsupported' % metric_name.to_s
end
end
@@ -72,9 +72,13 @@ module ActiveSupport
protected
def run_warmup
+ 5.times { GC.start }
+
time = Metrics::Time.new
run_test(time, :benchmark)
puts "%s (%s warmup)" % [full_test_name, time.format(time.total)]
+
+ 5.times { GC.start }
end
def run_profile(metric)
@@ -95,8 +99,13 @@ module ActiveSupport
def report
rate = @total / profile_options[:runs]
- '%20s: %s/run' % [@metric.name, @metric.format(rate)]
+ '%20s: %s' % [@metric.name, @metric.format(rate)]
end
+
+ protected
+ def output_filename
+ "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}"
+ end
end
class Benchmarker < Performer
@@ -107,9 +116,9 @@ module ActiveSupport
def record
avg = @metric.total / profile_options[:runs].to_i
- data = [full_test_name, @metric.name, avg, Time.now.utc.xmlschema] * ','
+ now = Time.now.utc.xmlschema
with_output_file do |file|
- file.puts "#{data},#{environment}"
+ file.puts "#{avg},#{now},#{environment}"
end
end
@@ -134,10 +143,10 @@ module ActiveSupport
end
protected
- HEADER = 'test,metric,measurement,created_at,app,rails,ruby,platform'
+ HEADER = 'measurement,created_at,app,rails,ruby,platform'
def with_output_file
- fname = "#{profile_options[:output]}/benchmarks.csv"
+ fname = output_filename
if new = !File.exist?(fname)
FileUtils.mkdir_p(File.dirname(fname))
@@ -148,6 +157,10 @@ module ActiveSupport
yield file
end
end
+
+ def output_filename
+ "#{super}.csv"
+ end
end
class Profiler < Performer
@@ -183,7 +196,7 @@ module ActiveSupport
else printer_class.name.sub(/Printer$/, '').underscore
end
- "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}_#{suffix}"
+ "#{super()}_#{suffix}"
end
end
@@ -210,6 +223,10 @@ module ActiveSupport
self.class::Mode
end
+ def measure
+ 0
+ end
+
def benchmark
with_gc_stats do
before = measure
@@ -271,7 +288,7 @@ module ActiveSupport
end
class CpuTime < Time
- Mode = RubyProf::CPU_TIME
+ Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME)
def initialize(*args)
# FIXME: yeah my CPU is 2.33 GHz
@@ -285,16 +302,27 @@ module ActiveSupport
end
class Memory < Base
- Mode = RubyProf::MEMORY
+ Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY)
+ # ruby-prof wrapper
if RubyProf.respond_to?(:measure_memory)
def measure
RubyProf.measure_memory / 1024.0
end
+
+ # Ruby 1.8 + adymo patch
elsif GC.respond_to?(:allocated_size)
def measure
GC.allocated_size / 1024.0
end
+
+ # Ruby 1.8 + lloyd patch
+ elsif GC.respond_to?(:heap_info)
+ def measure
+ GC.heap_info['heap_current_memory'] / 1024.0
+ end
+
+ # Ruby 1.9 unpatched
elsif GC.respond_to?(:malloc_allocated_size)
def measure
GC.malloc_allocated_size / 1024.0
@@ -307,7 +335,7 @@ module ActiveSupport
end
class Objects < Base
- Mode = RubyProf::ALLOCATIONS
+ Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS)
if RubyProf.respond_to?(:measure_allocations)
def measure
@@ -323,6 +351,46 @@ module ActiveSupport
measurement.to_i.to_s
end
end
+
+ class GcRuns < Base
+ Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS)
+
+ if RubyProf.respond_to?(:measure_gc_runs)
+ def measure
+ RubyProf.measure_gc_runs
+ end
+ elsif GC.respond_to?(:collections)
+ def measure
+ GC.collections
+ end
+ elsif GC.respond_to?(:heap_info)
+ def measure
+ GC.heap_info['num_gc_passes']
+ end
+ end
+
+ def format(measurement)
+ measurement.to_i.to_s
+ end
+ end
+
+ class GcTime < Base
+ Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME)
+
+ if RubyProf.respond_to?(:measure_gc_time)
+ def measure
+ RubyProf.measure_gc_time
+ end
+ elsif GC.respond_to?(:time)
+ def measure
+ GC.time
+ end
+ end
+
+ def format(measurement)
+ '%d ms' % (measurement / 1000)
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index bfc5b16039..e85bfe9b2e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -159,19 +159,24 @@ module ActiveSupport
utc == other
end
- # If wrapped +time+ is a DateTime, use DateTime#since instead of <tt>+</tt>.
- # Otherwise, just pass on to +method_missing+.
def +(other)
- result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
- result.in_time_zone(time_zone)
+ # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
+ # otherwise move forward from #utc, for accuracy when moving across DST boundaries
+ if duration_of_variable_length?(other)
+ method_missing(:+, other)
+ else
+ result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other)
+ result.in_time_zone(time_zone)
+ end
end
-
- # If a time-like object is passed in, compare it with +utc+.
- # Else if wrapped +time+ is a DateTime, use DateTime#ago instead of DateTime#-.
- # Otherwise, just pass on to +method_missing+.
+
def -(other)
+ # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time,
+ # otherwise move backwards #utc, for accuracy when moving across DST boundaries
if other.acts_like?(:time)
utc - other
+ elsif duration_of_variable_length?(other)
+ method_missing(:-, other)
else
result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other)
result.in_time_zone(time_zone)
@@ -179,15 +184,27 @@ module ActiveSupport
end
def since(other)
- utc.since(other).in_time_zone(time_zone)
+ # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time,
+ # otherwise move forward from #utc, for accuracy when moving across DST boundaries
+ if duration_of_variable_length?(other)
+ method_missing(:since, other)
+ else
+ utc.since(other).in_time_zone(time_zone)
+ end
end
def ago(other)
- utc.ago(other).in_time_zone(time_zone)
+ since(-other)
end
-
+
def advance(options)
- utc.advance(options).in_time_zone(time_zone)
+ # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
+ # otherwise advance from #utc, for accuracy when moving across DST boundaries
+ if options.detect {|k,v| [:years, :weeks, :months, :days].include? k}
+ method_missing(:advance, options)
+ else
+ utc.advance(options).in_time_zone(time_zone)
+ end
end
%w(year mon month day mday hour min sec).each do |method_name|
@@ -246,7 +263,7 @@ module ActiveSupport
end
def marshal_load(variables)
- initialize(variables[0], ::Time.send!(:get_zone, variables[1]), variables[2])
+ initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc)
end
# Ensure proxy class responds to all methods that underlying time instance responds to.
@@ -279,5 +296,9 @@ module ActiveSupport
def transfer_time_values_to_utc_constructor(time)
::Time.utc_time(time.year, time.month, time.day, time.hour, time.min, time.sec, time.respond_to?(:usec) ? time.usec : 0)
end
+
+ def duration_of_variable_length?(obj)
+ ActiveSupport::Duration === obj && obj.parts.flatten.detect {|p| [:years, :months, :days].include? p }
+ end
end
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 5b2d42aa3c..788d40bfa8 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -201,6 +201,12 @@ module ActiveSupport
result
end
+ # Compare #name and TZInfo identifier to a supplied regexp, returning true
+ # if a match is found.
+ def =~(re)
+ return true if name =~ re || MAPPING[name] =~ re
+ end
+
# Returns a textual representation of this time zone.
def to_s
"(GMT#{formatted_offset}) #{name}"
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 09b56525e0..f3220d27aa 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1,5 +1,11 @@
require 'abstract_unit'
+class CacheKeyTest < Test::Unit::TestCase
+ def test_expand_cache_key
+ assert_equal 'name/1/2/true', ActiveSupport::Cache.expand_cache_key([1, '2', true], :name)
+ end
+end
+
class CacheStoreSettingTest < Test::Unit::TestCase
def test_file_fragment_cache_store
store = ActiveSupport::Cache.lookup_store :file_store, "/path/to/cache/directory"
diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb
index 7563be44f8..62a1f61d53 100644
--- a/activesupport/test/core_ext/array_ext_test.rb
+++ b/activesupport/test/core_ext/array_ext_test.rb
@@ -99,7 +99,7 @@ class ArrayExtToSTests < Test::Unit::TestCase
end
class ArrayExtGroupingTests < Test::Unit::TestCase
- def test_group_by_with_perfect_fit
+ def test_in_groups_of_with_perfect_fit
groups = []
('a'..'i').to_a.in_groups_of(3) do |group|
groups << group
@@ -109,7 +109,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), %w(g h i)], ('a'..'i').to_a.in_groups_of(3)
end
- def test_group_by_with_padding
+ def test_in_groups_of_with_padding
groups = []
('a'..'g').to_a.in_groups_of(3) do |group|
groups << group
@@ -118,7 +118,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g', nil, nil]], groups
end
- def test_group_by_pads_with_specified_values
+ def test_in_groups_of_pads_with_specified_values
groups = []
('a'..'g').to_a.in_groups_of(3, 'foo') do |group|
@@ -128,7 +128,7 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g', 'foo', 'foo']], groups
end
- def test_group_without_padding
+ def test_in_groups_of_without_padding
groups = []
('a'..'g').to_a.in_groups_of(3, false) do |group|
@@ -137,6 +137,48 @@ class ArrayExtGroupingTests < Test::Unit::TestCase
assert_equal [%w(a b c), %w(d e f), ['g']], groups
end
+
+ def test_in_groups_returned_array_size
+ array = (1..7).to_a
+
+ 1.upto(array.size + 1) do |number|
+ assert_equal number, array.in_groups(number).size
+ end
+ end
+
+ def test_in_groups_with_empty_array
+ assert_equal [[], [], []], [].in_groups(3)
+ end
+
+ def test_in_groups_with_block
+ array = (1..9).to_a
+ groups = []
+
+ array.in_groups(3) do |group|
+ groups << group
+ end
+
+ assert_equal array.in_groups(3), groups
+ end
+
+ def test_in_groups_with_perfect_fit
+ assert_equal [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+ (1..9).to_a.in_groups(3)
+ end
+
+ def test_in_groups_with_padding
+ array = (1..7).to_a
+
+ assert_equal [[1, 2, 3], [4, 5, nil], [6, 7, nil]],
+ array.in_groups(3)
+ assert_equal [[1, 2, 3], [4, 5, 'foo'], [6, 7, 'foo']],
+ array.in_groups(3, 'foo')
+ end
+
+ def test_in_groups_without_padding
+ assert_equal [[1, 2, 3], [4, 5], [6, 7]],
+ (1..7).to_a.in_groups(3, false)
+ end
end
class ArraySplitTests < Test::Unit::TestCase
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index fb9b9b8916..2315d8f3db 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -63,10 +63,15 @@ class EnumerableTests < Test::Unit::TestCase
assert_equal({ 5 => payments[0], 15 => payments[1], 10 => payments[2] },
payments.index_by { |p| p.price })
end
-
- def test_several
+
+ def test_many
assert ![].many?
assert ![ 1 ].many?
assert [ 1, 2 ].many?
+
+ assert ![].many? {|x| x > 1 }
+ assert ![ 2 ].many? {|x| x > 1 }
+ assert ![ 1, 2 ].many? {|x| x > 1 }
+ assert [ 1, 2, 2 ].many? {|x| x > 1 }
end
end
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 0977cd8e50..dfe04485be 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -320,8 +320,11 @@ class TimeWithZoneTest < Test::Unit::TestCase
marshal_str = Marshal.dump(@twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
+ assert mtime.utc.utc?
assert_equal ActiveSupport::TimeZone['Eastern Time (US & Canada)'], mtime.time_zone
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
+ assert mtime.time.utc?
+ assert_equal @twz.inspect, mtime.inspect
end
end
@@ -331,8 +334,11 @@ class TimeWithZoneTest < Test::Unit::TestCase
marshal_str = Marshal.dump(twz)
mtime = Marshal.load(marshal_str)
assert_equal Time.utc(2000, 1, 1, 0), mtime.utc
+ assert mtime.utc.utc?
assert_equal 'America/New_York', mtime.time_zone.name
assert_equal Time.utc(1999, 12, 31, 19), mtime.time
+ assert mtime.time.utc?
+ assert_equal @twz.inspect, mtime.inspect
end
end
@@ -485,6 +491,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2004,2,29))
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:years => 1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.years_since(1).inspect
+ assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.year).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.year).inspect
end
@@ -492,6 +499,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2005,1,31))
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.advance(:months => 1).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.months_since(1).inspect
+ assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", twz.since(1.month).inspect
assert_equal "Mon, 28 Feb 2005 00:00:00 EST -05:00", (twz + 1.month).inspect
end
@@ -499,6 +507,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2000,1,31))
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.advance(:months => 1).inspect
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.months_since(1).inspect
+ assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", twz.since(1.month).inspect
assert_equal "Tue, 29 Feb 2000 00:00:00 EST -05:00", (twz + 1.month).inspect
end
@@ -506,6 +515,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,3,2,2))
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:months => 1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.months_since(1).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.month).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.month).inspect
end
@@ -513,8 +523,172 @@ class TimeWithZoneTest < Test::Unit::TestCase
twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,1,59,59))
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.advance(:seconds => 1).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1).inspect
+ assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", twz.since(1.second).inspect
assert_equal "Sun, 02 Apr 2006 03:00:00 EDT -04:00", (twz + 1.second).inspect
end
+
+ def test_advance_1_day_across_spring_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
+ # When we advance 1 day, we want to end up at the same time on the next day
+ assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.advance(:days => 1).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", twz.since(1.days).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:00 EDT -04:00", (twz + 1.days).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", twz.since(1.days + 1.second).inspect
+ assert_equal "Sun, 02 Apr 2006 10:30:01 EDT -04:00", (twz + 1.days + 1.second).inspect
+ end
+
+ def test_advance_1_day_across_spring_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,10,30))
+ # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
+ # When we advance back 1 day, we want to end up at the same time on the previous day
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:days => -1).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.days).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.days).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:01 EST -05:00", twz.ago(1.days - 1.second).inspect
+ end
+
+ def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
+ # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 86400.seconds).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(86400.seconds).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:seconds => 86400).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 1440.minutes).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(1440.minutes).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:minutes => 1440).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", (twz + 24.hours).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.since(24.hours).inspect
+ assert_equal "Sun, 02 Apr 2006 11:30:00 EDT -04:00", twz.advance(:hours => 24).inspect
+ end
+
+ def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_spring_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,2,11,30))
+ # In 2006, spring DST transition occurred Apr 2 at 2AM; this day was only 23 hours long
+ # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 86400.seconds).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(86400.seconds).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:seconds => -86400).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1440.minutes).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1440.minutes).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:minutes => -1440).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 24.hours).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(24.hours).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:hours => -24).inspect
+ end
+
+ def test_advance_1_day_across_fall_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
+ # When we advance 1 day, we want to end up at the same time on the next day
+ assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.advance(:days => 1).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", twz.since(1.days).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:00 EST -05:00", (twz + 1.days).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", twz.since(1.days + 1.second).inspect
+ assert_equal "Sun, 29 Oct 2006 10:30:01 EST -05:00", (twz + 1.days + 1.second).inspect
+ end
+
+ def test_advance_1_day_across_fall_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,10,30))
+ # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
+ # When we advance backwards 1 day, we want to end up at the same time on the previous day
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:days => -1).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.days).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.days).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:01 EDT -04:00", twz.ago(1.days - 1.second).inspect
+ end
+
+ def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
+ # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 86400.seconds).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(86400.seconds).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:seconds => 86400).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 1440.minutes).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(1440.minutes).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:minutes => 1440).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", (twz + 24.hours).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.since(24.hours).inspect
+ assert_equal "Sun, 29 Oct 2006 09:30:00 EST -05:00", twz.advance(:hours => 24).inspect
+ end
+
+ def test_advance_1_day_expressed_as_number_of_seconds_minutes_or_hours_across_fall_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,29,9,30))
+ # In 2006, fall DST transition occurred Oct 29 at 2AM; this day was 25 hours long
+ # When we advance a specific number of hours, minutes or seconds, we want to advance exactly that amount
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 86400.seconds).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(86400.seconds).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:seconds => -86400).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1440.minutes).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1440.minutes).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:minutes => -1440).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 24.hours).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(24.hours).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:hours => -24).inspect
+ end
+
+ def test_advance_1_month_across_spring_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,4,1,10,30))
+ assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.advance(:months => 1).inspect
+ assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.months_since(1).inspect
+ assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", twz.since(1.month).inspect
+ assert_equal "Mon, 01 May 2006 10:30:00 EDT -04:00", (twz + 1.month).inspect
+ end
+
+ def test_advance_1_month_across_spring_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,5,1,10,30))
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.advance(:months => -1).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.months_ago(1).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", twz.ago(1.month).inspect
+ assert_equal "Sat, 01 Apr 2006 10:30:00 EST -05:00", (twz - 1.month).inspect
+ end
+
+ def test_advance_1_month_across_fall_dst_transition
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,10,28,10,30))
+ assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.advance(:months => 1).inspect
+ assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.months_since(1).inspect
+ assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", twz.since(1.month).inspect
+ assert_equal "Tue, 28 Nov 2006 10:30:00 EST -05:00", (twz + 1.month).inspect
+ end
+
+ def test_advance_1_month_across_fall_dst_transition_backwards
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2006,11,28,10,30))
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.advance(:months => -1).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.months_ago(1).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", twz.ago(1.month).inspect
+ assert_equal "Sat, 28 Oct 2006 10:30:00 EDT -04:00", (twz - 1.month).inspect
+ end
+
+ def test_advance_1_year
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,2,15,10,30))
+ assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.advance(:years => 1).inspect
+ assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", twz.years_since(1).inspect
+ assert_equal "Sun, 15 Feb 2009 10:30:00 EST -05:00", (twz + 1.year).inspect
+ assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.advance(:years => -1).inspect
+ assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", twz.years_ago(1).inspect
+ assert_equal "Thu, 15 Feb 2007 10:30:00 EST -05:00", (twz - 1.year).inspect
+ end
+
+ def test_advance_1_year_during_dst
+ twz = ActiveSupport::TimeWithZone.new(nil, @time_zone, Time.utc(2008,7,15,10,30))
+ assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.advance(:years => 1).inspect
+ assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", twz.years_since(1).inspect
+ assert_equal "Wed, 15 Jul 2009 10:30:00 EDT -04:00", (twz + 1.year).inspect
+ assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.advance(:years => -1).inspect
+ assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", twz.years_ago(1).inspect
+ assert_equal "Sun, 15 Jul 2007 10:30:00 EDT -04:00", (twz - 1.year).inspect
+ end
end
class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 4ce9cbb705..6c0c14e866 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -110,6 +110,23 @@ class InflectorTest < Test::Unit::TestCase
end
end
+ def test_humanize_by_rule
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.human(/_cnt$/i, '\1_count')
+ inflect.human(/^prefx_/i, '\1')
+ end
+ assert_equal("Jargon count", ActiveSupport::Inflector.humanize("jargon_cnt"))
+ assert_equal("Request", ActiveSupport::Inflector.humanize("prefx_request"))
+ end
+
+ def test_humanize_by_string
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.human("col_rpted_bugs", "Reported bugs")
+ end
+ assert_equal("Reported bugs", ActiveSupport::Inflector.humanize("col_rpted_bugs"))
+ assert_equal("Col rpted bugs", ActiveSupport::Inflector.humanize("COL_rpted_bugs"))
+ end
+
def test_constantize
assert_nothing_raised { assert_equal Ace::Base::Case, ActiveSupport::Inflector.constantize("Ace::Base::Case") }
assert_nothing_raised { assert_equal Ace::Base::Case, ActiveSupport::Inflector.constantize("::Ace::Base::Case") }
@@ -148,7 +165,7 @@ class InflectorTest < Test::Unit::TestCase
end
end
- %w{plurals singulars uncountables}.each do |inflection_type|
+ %w{plurals singulars uncountables humans}.each do |inflection_type|
class_eval "
def test_clear_#{inflection_type}
cached_values = ActiveSupport::Inflector.inflections.#{inflection_type}
@@ -160,25 +177,29 @@ class InflectorTest < Test::Unit::TestCase
end
def test_clear_all
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables
+ cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
ActiveSupport::Inflector.inflections.clear :all
assert ActiveSupport::Inflector.inflections.plurals.empty?
assert ActiveSupport::Inflector.inflections.singulars.empty?
assert ActiveSupport::Inflector.inflections.uncountables.empty?
+ assert ActiveSupport::Inflector.inflections.humans.empty?
ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
+ ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
def test_clear_with_default
- cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables
+ cached_values = ActiveSupport::Inflector.inflections.plurals, ActiveSupport::Inflector.inflections.singulars, ActiveSupport::Inflector.inflections.uncountables, ActiveSupport::Inflector.inflections.humans
ActiveSupport::Inflector.inflections.clear
assert ActiveSupport::Inflector.inflections.plurals.empty?
assert ActiveSupport::Inflector.inflections.singulars.empty?
assert ActiveSupport::Inflector.inflections.uncountables.empty?
+ assert ActiveSupport::Inflector.inflections.humans.empty?
ActiveSupport::Inflector.inflections.instance_variable_set :@plurals, cached_values[0]
ActiveSupport::Inflector.inflections.instance_variable_set :@singulars, cached_values[1]
ActiveSupport::Inflector.inflections.instance_variable_set :@uncountables, cached_values[2]
+ ActiveSupport::Inflector.inflections.instance_variable_set :@humans, cached_values[3]
end
Irregularities.each do |irregularity|
@@ -217,7 +238,7 @@ class InflectorTest < Test::Unit::TestCase
end
end
- { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable }.each do |scope, method|
+ { :singulars => :singular, :plurals => :plural, :uncountables => :uncountable, :humans => :human }.each do |scope, method|
ActiveSupport::Inflector.inflections do |inflect|
define_method("test_clear_inflections_with_#{scope}") do
# save the inflections
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
new file mode 100644
index 0000000000..fc24a2942d
--- /dev/null
+++ b/activesupport/test/memoizable_test.rb
@@ -0,0 +1,49 @@
+require 'abstract_unit'
+
+uses_mocha 'Memoizable' do
+ class MemoizableTest < Test::Unit::TestCase
+ class Person
+ include ActiveSupport::Memoizable
+
+ def name
+ fetch_name_from_floppy
+ end
+ memoize :name
+
+ def age
+ nil
+ end
+ memoize :age
+
+ private
+ def fetch_name_from_floppy
+ "Josh"
+ end
+ end
+
+ def test_memoization
+ person = Person.new
+ assert_equal "Josh", person.name
+
+ person.expects(:fetch_name_from_floppy).never
+ 2.times { assert_equal "Josh", person.name }
+ end
+
+ def test_memoized_methods_are_frozen
+ person = Person.new
+ person.freeze
+ assert_equal "Josh", person.name
+ assert_equal true, person.name.frozen?
+ end
+
+ def test_memoization_frozen_with_nil_value
+ person = Person.new
+ person.freeze
+ assert_equal nil, person.age
+ end
+
+ def test_double_memoization
+ assert_raise(RuntimeError) { Person.memoize :name }
+ end
+ end
+end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index b42dcd17f8..515ffcf0bf 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -250,6 +250,13 @@ class TimeZoneTest < Test::Unit::TestCase
assert zone1 == zone1
end
+ def test_zone_match
+ zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
+ assert zone =~ /Eastern/
+ assert zone =~ /New_York/
+ assert zone !~ /Nonexistent_Place/
+ end
+
def test_to_s
assert_equal "(GMT+03:00) Moscow", ActiveSupport::TimeZone['Moscow'].to_s
end
diff --git a/cleanlogs.sh b/cleanlogs.sh
deleted file mode 100755
index a4e6baf0df..0000000000
--- a/cleanlogs.sh
+++ /dev/null
@@ -1 +0,0 @@
-rm activerecord/debug.log activerecord/test/debug.log actionpack/debug.log activeresource/test/debug.log
diff --git a/doc/template/horo.rb b/doc/template/horo.rb
new file mode 100644
index 0000000000..e028422a2e
--- /dev/null
+++ b/doc/template/horo.rb
@@ -0,0 +1,613 @@
+# 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
+
+module RDoc
+module Page
+
+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: %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="%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>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%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 />%short_name%</td>
+ <td align="right">
+ <table border='0' cellspacing="0" cellpadding="2">
+ <tr>
+ <td>Path:</td>
+ <td>%full_path%
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%">CVS</a>)
+ENDIF:cvsurl
+ </td>
+ </tr>
+ <tr>
+ <td>Modified:</td>
+ <td>%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">%classmod%</span><br />%full_name%</td>
+ <td align="right">
+ <table cellspacing="0" cellpadding="2">
+ <tr valign="top">
+ <td>In:</td>
+ <td>
+START:infiles
+HREF:full_path_url:full_path:
+IF:cvsurl
+&nbsp;(<a href="%cvsurl%">CVS</a>)
+ENDIF:cvsurl
+END:infiles
+ </td>
+ </tr>
+IF:parent
+ <tr>
+ <td>Parent:</td>
+ <td>
+IF:par_url
+ <a href="%par_url%">
+ENDIF:par_url
+%parent%
+IF:par_url
+ </a>
+ENDIF:par_url
+ </td>
+ </tr>
+ENDIF:parent
+ </table>
+ </td>
+ </tr>
+ </table>
+HTML
+
+###################################################################
+
+METHOD_LIST = <<HTML
+ <div id="content">
+IF:diagram
+ <table cellpadding='0' cellspacing='0' border='0' width="100%"><tr><td align="center">
+ %diagram%
+ </td></tr></table>
+ENDIF:diagram
+
+IF:description
+ <div class="description">%description%</div>
+ENDIF:description
+
+IF:requires
+ <div class="sectiontitle">Required Files</div>
+ <ul>
+START:requires
+ <li>HREF:aref:name:</li>
+END:requires
+ </ul>
+ENDIF:requires
+
+IF:toc
+ <div class="sectiontitle">Contents</div>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+
+IF:methods
+ <div class="sectiontitle">Methods</div>
+ <ul>
+START:methods
+ <li>HREF:aref:name:</li>
+END:methods
+ </ul>
+ENDIF:methods
+
+IF:includes
+<div class="sectiontitle">Included Modules</div>
+<ul>
+START:includes
+ <li>HREF:aref:name:</li>
+END:includes
+</ul>
+ENDIF:includes
+
+START:sections
+IF:sectitle
+<div class="sectiontitle"><a name="%secsequence%">%sectitle%</a></div>
+IF:seccomment
+<div class="description">
+%seccomment%
+</div>
+ENDIF:seccomment
+ENDIF:sectitle
+
+IF:classlist
+ <div class="sectiontitle">Classes and Modules</div>
+ %classlist%
+ENDIF:classlist
+
+IF:constants
+ <div class="sectiontitle">Constants</div>
+ <table border='0' cellpadding='5'>
+START:constants
+ <tr valign='top'>
+ <td class="attr-name">%name%</td>
+ <td>=</td>
+ <td class="attr-value">%value%</td>
+ </tr>
+IF:desc
+ <tr valign='top'>
+ <td>&nbsp;</td>
+ <td colspan="2" class="attr-desc">%desc%</td>
+ </tr>
+ENDIF:desc
+END:constants
+ </table>
+ENDIF:constants
+
+IF:attributes
+ <div class="sectiontitle">Attributes</div>
+ <table border='0' cellpadding='5'>
+START:attributes
+ <tr valign='top'>
+ <td class='attr-rw'>
+IF:rw
+[%rw%]
+ENDIF:rw
+ </td>
+ <td class='attr-name'>%name%</td>
+ <td class='attr-desc'>%a_desc%</td>
+ </tr>
+END:attributes
+ </table>
+ENDIF:attributes
+
+IF:method_list
+START:method_list
+IF:methods
+<div class="sectiontitle">%type% %category% methods</div>
+START:methods
+<div class="method">
+ <div class="title">
+IF:callseq
+ <a name="%aref%"></a><b>%callseq%</b>
+ENDIF:callseq
+IFNOT:callseq
+ <a name="%aref%"></a><b>%name%</b>%params%
+ENDIF:callseq
+IF:codeurl
+[&nbsp;<a href="%codeurl%" target="SOURCE_CODE" onclick="javascript:openCode('%codeurl%'); return false;">source</a>&nbsp;]
+ENDIF:codeurl
+ </div>
+IF:m_desc
+ <div class="description">
+ %m_desc%
+ </div>
+ENDIF:m_desc
+IF:aka
+<div class="aka">
+ This method is also aliased as
+START:aka
+ <a href="%aref%">%name%</a>
+END:aka
+</div>
+ENDIF:aka
+IF:sourcecode
+<div class="sourcecode">
+ <p class="source-link">[ <a href="javascript:toggleSource('%aref%_source')" id="l_%aref%_source">show source</a> ]</p>
+ <div id="%aref%_source" class="dyn-source">
+<pre>
+%sourcecode%
+</pre>
+ </div>
+</div>
+ENDIF:sourcecode
+</div>
+END:methods
+ENDIF:methods
+END:method_list
+ENDIF:method_list
+END:sections
+</div>
+HTML
+
+FOOTER = <<ENDFOOTER
+ </body>
+</html>
+ENDFOOTER
+
+BODY = HEADER + <<ENDBODY
+ !INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+ #{METHOD_LIST}
+ </div>
+
+ #{FOOTER}
+ENDBODY
+
+########################## Source code ##########################
+
+SRC_PAGE = XHTML_PREAMBLE + <<HTML
+<html>
+<head><title>%title%</title>
+<meta http-equiv="Content-Type" content="text/html; charset=%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>%code%</pre>
+</body>
+</html>
+HTML
+
+########################## Index ################################
+
+FR_INDEX_BODY = <<HTML
+!INCLUDE!
+HTML
+
+FILE_INDEX = XHTML_PREAMBLE + <<HTML
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=%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">%list_title%</div>
+<div class="entries">
+START:entries
+<a href="%href%">%name%</a><br />
+END:entries
+</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>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%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="%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
+
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 913bb365ac..b5c5aba460 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,24 @@
*Edge*
+* Make script/plugin install <plugin> -r <revision> option work with git based plugins. #257. [Tim Pope Jakub Kuźma]. Example:
+
+ script/plugin install git://github.com/mislav/will_paginate.git -r agnostic # Installs 'agnostic' branch
+ script/plugin install git://github.com/dchelimsky/rspec.git -r 'tag 1.1.4'
+
+* Added Rails.initialized? flag [Josh Peek]
+
+* Make rake test:uncommitted work with Git. [Tim Pope]
+
+* Added Thin support to script/server. #488 [Bob Klosinski]
+
+* Fix script/about in production mode. #370 [Cheah Chu Yeow, Xavier Noria, David Krmpotic]
+
+* Add the gem load paths before the framework is loaded, so certain gems like RedCloth and BlueCloth can be frozen.
+
+* Fix discrepancies with loading rails/init.rb from gems.
+
+* Plugins check for the gem init path (rails/init.rb) before the standard plugin init path (init.rb) [Jacek Becela]
+
* Changed all generated tests to use the test/do declaration style [DHH]
* Wrapped Rails.env in StringInquirer so you can do Rails.env.development? [DHH]
diff --git a/railties/Rakefile b/railties/Rakefile
index a1d1095c37..f64b60b0dd 100644
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -264,9 +264,10 @@ Rake::RDocTask.new { |rdoc|
rdoc.title = "Railties -- Gluing the Engine to the Rails"
rdoc.options << '--line-numbers' << '--inline-source' << '--accessor' << 'cattr_accessor=object'
rdoc.options << '--charset' << 'utf-8'
- rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo'
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/*.rb')
+ rdoc.rdoc_files.include('lib/rails/*.rb')
rdoc.rdoc_files.include('lib/rails_generator/*.rb')
rdoc.rdoc_files.include('lib/commands/**/*.rb')
}
@@ -331,10 +332,15 @@ end
# Publishing -------------------------------------------------------
-desc "Publish the API documentation"
+desc "Publish the rails gem"
task :pgem => [:gem] do
- Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
- `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+ Rake::SshFilePublisher.new("wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh wrath.rubyonrails.org './gemupdate.sh'`
+end
+
+desc "Publish the API documentation"
+task :pdoc => :rdoc do
+ # railties API isn't separately published
end
desc "Publish the release files to RubyForge."
diff --git a/railties/bin/about b/railties/bin/about
index cd38a32abf..ed8deb0dfc 100644
--- a/railties/bin/about
+++ b/railties/bin/about
@@ -1,3 +1,4 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
-require 'commands/about'
+$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
+require 'commands/about' \ No newline at end of file
diff --git a/railties/configs/routes.rb b/railties/configs/routes.rb
index b579d6c7d1..4f3d9d22dd 100644
--- a/railties/configs/routes.rb
+++ b/railties/configs/routes.rb
@@ -36,6 +36,8 @@ ActionController::Routing::Routes.draw do |map|
# See how all your routes lay out with "rake routes"
# Install the default routes as the lowest priority.
+ # Note: These default routes make all actions in every controller accessible via GET requests. You should
+ # consider removing the them or commenting them out if you're using named routes and resources.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
diff --git a/railties/environments/boot.rb b/railties/environments/boot.rb
index cd21fb9eab..6a30b54973 100644
--- a/railties/environments/boot.rb
+++ b/railties/environments/boot.rb
@@ -82,14 +82,14 @@ module Rails
def load_rubygems
require 'rubygems'
-
- unless rubygems_version >= '0.9.4'
- $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ min_version = '1.1.1'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
exit 1
end
rescue LoadError
- $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
exit 1
end
diff --git a/railties/helpers/performance_test_helper.rb b/railties/helpers/performance_test_helper.rb
index 3c4c7fb740..1aafc7f7e5 100644
--- a/railties/helpers/performance_test_helper.rb
+++ b/railties/helpers/performance_test_helper.rb
@@ -1,6 +1,2 @@
require 'test_helper'
-require 'action_controller/performance_test'
-
-ActionController::Base.perform_caching = true
-ActiveSupport::Dependencies.mechanism = :require
-Rails.logger.level = ActiveSupport::BufferedLogger::INFO
+require 'performance_test_help'
diff --git a/railties/lib/commands/about.rb b/railties/lib/commands/about.rb
index 313bc18c6a..7f53ac8a2e 100644
--- a/railties/lib/commands/about.rb
+++ b/railties/lib/commands/about.rb
@@ -1,2 +1,3 @@
require 'environment'
+require 'rails/info'
puts Rails::Info
diff --git a/railties/lib/commands/plugin.rb b/railties/lib/commands/plugin.rb
index 105819ce90..0256090d16 100644
--- a/railties/lib/commands/plugin.rb
+++ b/railties/lib/commands/plugin.rb
@@ -43,6 +43,16 @@
# plugin is pulled via `svn checkout` or `svn export` but looks
# exactly the same.
#
+# Specifying revisions:
+#
+# * Subversion revision is a single integer.
+#
+# * Git revision format:
+# - full - 'refs/tags/1.8.0' or 'refs/heads/experimental'
+# - short: 'experimental' (equivalent to 'refs/heads/experimental')
+# 'tag 1.8.0' (equivalent to 'refs/tags/1.8.0')
+#
+#
# This is Free Software, copyright 2005 by Ryan Tomayko (rtomayko@gmail.com)
# and is licensed MIT: (http://www.opensource.org/licenses/mit-license.php)
@@ -175,7 +185,7 @@ class Plugin
method ||= rails_env.best_install_method?
if :http == method
method = :export if svn_url?
- method = :clone if git_url?
+ method = :git if git_url?
end
uninstall if installed? and options[:force]
@@ -255,8 +265,25 @@ class Plugin
end
end
- def install_using_clone(options = {})
- git_command :clone, options
+ def install_using_git(options = {})
+ root = rails_env.root
+ install_path = mkdir_p "#{root}/vendor/plugins/#{name}"
+ Dir.chdir install_path do
+ init_cmd = "git init"
+ init_cmd += " -q" if options[:quiet] and not $verbose
+ puts init_cmd if $verbose
+ system(init_cmd)
+ base_cmd = "git pull --depth 1 #{uri}"
+ base_cmd += " -q" if options[:quiet] and not $verbose
+ base_cmd += " #{options[:revision]}" if options[:revision]
+ puts base_cmd if $verbose
+ if system(base_cmd)
+ puts "removing: .git" if $verbose
+ rm_rf ".git"
+ else
+ rm_rf install_path
+ end
+ end
end
def svn_command(cmd, options = {})
@@ -268,16 +295,6 @@ class Plugin
puts base_cmd if $verbose
system(base_cmd)
end
-
- def git_command(cmd, options = {})
- root = rails_env.root
- mkdir_p "#{root}/vendor/plugins"
- base_cmd = "git #{cmd} --depth 1 #{uri} \"#{root}/vendor/plugins/#{name}\""
- puts base_cmd if $verbose
- puts "removing: #{root}/vendor/plugins/#{name}/.git"
- system(base_cmd)
- rm_rf "#{root}/vendor/plugins/#{name}/.git"
- end
def guess_name(url)
@name = File.basename(url)
@@ -632,7 +649,7 @@ module Commands
def options
OptionParser.new do |o|
o.set_summary_indent(' ')
- o.banner = "Usage: #{@base_command.script_name} source URI [URI [URI]...]"
+ o.banner = "Usage: #{@base_command.script_name} unsource URI [URI [URI]...]"
o.define_head "Remove repositories from the default search list."
o.separator ""
o.on_tail("-h", "--help", "Show this help message.") { puts o; exit }
@@ -756,8 +773,8 @@ module Commands
"Suppresses the output from installation.",
"Ignored if -v is passed (./script/plugin -v install ...)") { |v| @options[:quiet] = true }
o.on( "-r REVISION", "--revision REVISION",
- "Checks out the given revision from subversion.",
- "Ignored if subversion is not used.") { |v| @options[:revision] = v }
+ "Checks out the given revision from subversion or git.",
+ "Ignored if subversion/git is not used.") { |v| @options[:revision] = v }
o.on( "-f", "--force",
"Reinstalls a plugin if it's already installed.") { |v| @options[:force] = true }
o.separator ""
diff --git a/railties/lib/commands/server.rb b/railties/lib/commands/server.rb
index 40ffdd1167..7306c248fb 100644
--- a/railties/lib/commands/server.rb
+++ b/railties/lib/commands/server.rb
@@ -13,11 +13,19 @@ rescue Exception
# Mongrel not available
end
+begin
+ require_library_or_gem 'thin'
+rescue Exception
+ # Thin not available
+end
+
server = case ARGV.first
- when "lighttpd", "mongrel", "new_mongrel", "webrick"
+ when "lighttpd", "mongrel", "new_mongrel", "webrick", "thin"
ARGV.shift
else
- if defined?(Mongrel)
+ if defined?(Thin)
+ "thin"
+ elsif defined?(Mongrel)
"mongrel"
elsif RUBY_PLATFORM !~ /(:?mswin|mingw)/ && !silence_stderr { `lighttpd -version` }.blank? && defined?(FCGI)
"lighttpd"
@@ -33,6 +41,8 @@ case server
puts "=> Booting lighttpd (use 'script/server webrick' to force WEBrick)"
when "mongrel", "new_mongrel"
puts "=> Booting Mongrel (use 'script/server webrick' to force WEBrick)"
+ when "thin"
+ puts "=> Booting Thin (use 'script/server webrick' to force WEBrick)"
end
%w(cache pids sessions sockets).each { |dir_to_make| FileUtils.mkdir_p(File.join(RAILS_ROOT, 'tmp', dir_to_make)) }
diff --git a/railties/lib/commands/servers/thin.rb b/railties/lib/commands/servers/thin.rb
new file mode 100644
index 0000000000..833469cab1
--- /dev/null
+++ b/railties/lib/commands/servers/thin.rb
@@ -0,0 +1,25 @@
+require 'rbconfig'
+require 'commands/servers/base'
+require 'thin'
+
+
+options = ARGV.clone
+options.insert(0,'start') unless Thin::Runner.commands.include?(options[0])
+
+thin = Thin::Runner.new(options)
+
+puts "=> Rails #{Rails.version} application starting on http://#{thin.options[:address]}:#{thin.options[:port]}"
+puts "=> Ctrl-C to shutdown server"
+
+log = Pathname.new("#{File.expand_path(RAILS_ROOT)}/log/#{RAILS_ENV}.log").cleanpath
+open(log, (File::WRONLY | File::APPEND | File::CREAT)) unless File.exist? log
+tail_thread = tail(log)
+trap(:INT) { exit }
+
+begin
+ thin.run!
+ensure
+ tail_thread.kill if tail_thread
+ puts 'Exiting'
+end
+
diff --git a/railties/lib/console_with_helpers.rb b/railties/lib/console_with_helpers.rb
index 79018a9f76..be453a6896 100644
--- a/railties/lib/console_with_helpers.rb
+++ b/railties/lib/console_with_helpers.rb
@@ -16,7 +16,7 @@ def helper(*helper_names)
end
end
-require 'application'
+require_dependency 'application'
class << helper
include_all_modules_from ActionView
diff --git a/railties/lib/initializer.rb b/railties/lib/initializer.rb
index f0b5e3f257..7808d88d92 100644
--- a/railties/lib/initializer.rb
+++ b/railties/lib/initializer.rb
@@ -19,15 +19,23 @@ module Rails
def configuration
@@configuration
end
-
+
def configuration=(configuration)
@@configuration = configuration
end
-
+
+ def initialized?
+ @initialized || false
+ end
+
+ def initialized=(initialized)
+ @initialized ||= initialized
+ end
+
def logger
RAILS_DEFAULT_LOGGER
end
-
+
def root
if defined?(RAILS_ROOT)
RAILS_ROOT
@@ -35,11 +43,11 @@ module Rails
nil
end
end
-
+
def env
ActiveSupport::StringInquirer.new(RAILS_ENV)
end
-
+
def cache
RAILS_CACHE
end
@@ -56,7 +64,7 @@ module Rails
@@public_path = path
end
end
-
+
# The Initializer is responsible for processing the Rails configuration, such
# as setting the $LOAD_PATH, requiring the right frameworks, initializing
# logging, and more. It can be run either as a single command that'll just
@@ -113,10 +121,10 @@ module Rails
check_ruby_version
install_gem_spec_stubs
set_load_path
-
+ add_gem_load_paths
+
require_frameworks
set_autoload_paths
- add_gem_load_paths
add_plugin_load_paths
load_environment
@@ -129,12 +137,12 @@ module Rails
initialize_logger
initialize_framework_logging
- initialize_framework_views
initialize_dependency_mechanism
initialize_whiny_nils
initialize_temporary_session_directory
initialize_time_zone
initialize_framework_settings
+ initialize_framework_views
add_support_load_paths
@@ -145,7 +153,7 @@ module Rails
add_gem_load_paths
load_gems
check_gem_dependencies
-
+
load_application_initializers
# the framework is now fully initialized
@@ -158,8 +166,10 @@ module Rails
initialize_routing
# Observers are loaded after plugins in case Observers or observed models are modified by plugins.
-
load_observers
+
+ # Flag initialized
+ Rails.initialized = true
end
# Check for valid Ruby version
@@ -242,12 +252,12 @@ module Rails
def add_gem_load_paths
unless @configuration.gems.empty?
require "rubygems"
- @configuration.gems.each &:add_load_paths
+ @configuration.gems.each { |gem| gem.add_load_paths }
end
end
def load_gems
- @configuration.gems.each(&:load)
+ @configuration.gems.each { |gem| gem.load }
end
def check_gem_dependencies
@@ -256,11 +266,16 @@ module Rails
@gems_dependencies_loaded = false
# don't print if the gems rake tasks are being run
unless $rails_gem_installer
- puts %{These gems that this application depends on are missing:}
- unloaded_gems.each do |gem|
- puts " - #{gem.name}"
- end
- puts %{Run "rake gems:install" to install them.}
+ abort <<-end_error
+Missing these required gems:
+ #{unloaded_gems.map { |gem| "#{gem.name} #{gem.requirement}" } * "\n "}
+
+You're running:
+ ruby #{Gem.ruby_version} at #{Gem.ruby}
+ rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
+
+Run `rake gems:install` to install the missing gems.
+ end_error
end
else
@gems_dependencies_loaded = true
@@ -297,12 +312,12 @@ module Rails
silence_warnings do
return if @environment_loaded
@environment_loaded = true
-
+
config = configuration
constants = self.class.constants
-
+
eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
-
+
(self.class.constants - constants).each do |const|
Object.const_set(const, self.class.const_get(const))
end
@@ -390,7 +405,7 @@ module Rails
for framework in ([ :active_record, :action_controller, :action_mailer ] & configuration.frameworks)
framework.to_s.camelize.constantize.const_get("Base").logger ||= RAILS_DEFAULT_LOGGER
end
-
+
RAILS_CACHE.logger ||= RAILS_DEFAULT_LOGGER
end
@@ -486,7 +501,6 @@ module Rails
Dispatcher.define_dispatcher_callbacks(configuration.cache_classes)
Dispatcher.new(RAILS_DEFAULT_LOGGER).send :run_callbacks, :prepare_dispatch
end
-
end
# The Configuration class holds all the parameters for the Initializer and
@@ -531,7 +545,7 @@ module Rails
# The path to the database configuration file to use. (Defaults to
# <tt>config/database.yml</tt>.)
attr_accessor :database_configuration_file
-
+
# The path to the routes configuration file to use. (Defaults to
# <tt>config/routes.rb</tt>.)
attr_accessor :routes_configuration_file
@@ -597,7 +611,7 @@ module Rails
# a sub class would have access to fine grained modification of the loading behavior. See
# the implementation of Rails::Plugin::Loader for more details.
attr_accessor :plugin_loader
-
+
# Enables or disables plugin reloading. You can get around this setting per plugin.
# If <tt>reload_plugins?</tt> is false, add this to your plugin's <tt>init.rb</tt>
# to make it reloadable:
@@ -634,7 +648,7 @@ module Rails
def gem(name, options = {})
@gems << Rails::GemDependency.new(name, options)
end
-
+
# Deprecated options:
def breakpoint_server(_ = nil)
$stderr.puts %(
@@ -693,7 +707,7 @@ module Rails
else
Pathname.new(::RAILS_ROOT).realpath.to_s
end
-
+
Object.const_set(:RELATIVE_RAILS_ROOT, ::RAILS_ROOT.dup) unless defined?(::RELATIVE_RAILS_ROOT)
::RAILS_ROOT.replace @root_path
end
@@ -734,7 +748,7 @@ module Rails
#
# See Dispatcher#to_prepare.
def to_prepare(&callback)
- after_initialize do
+ after_initialize do
require 'dispatcher' unless defined?(::Dispatcher)
Dispatcher.to_prepare(&callback)
end
@@ -748,11 +762,11 @@ module Rails
def framework_paths
paths = %w(railties railties/lib activesupport/lib)
paths << 'actionpack/lib' if frameworks.include? :action_controller or frameworks.include? :action_view
-
+
[:active_record, :action_mailer, :active_resource, :action_web_service].each do |framework|
paths << "#{framework.to_s.gsub('_', '')}/lib" if frameworks.include? framework
end
-
+
paths.map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) }
end
@@ -767,7 +781,7 @@ module Rails
def default_load_paths
paths = []
-
+
# Add the old mock paths only if the directories exists
paths.concat(Dir["#{root_path}/test/mocks/#{environment}"]) if File.exists?("#{root_path}/test/mocks/#{environment}")
@@ -853,7 +867,7 @@ module Rails
def default_plugin_loader
Plugin::Loader
end
-
+
def default_cache_store
if File.exist?("#{root_path}/tmp/cache/")
[ :file_store, "#{root_path}/tmp/cache/" ]
@@ -861,7 +875,7 @@ module Rails
:memory_store
end
end
-
+
def default_gems
[]
end
diff --git a/railties/lib/performance_test_help.rb b/railties/lib/performance_test_help.rb
new file mode 100644
index 0000000000..a5e52a7acb
--- /dev/null
+++ b/railties/lib/performance_test_help.rb
@@ -0,0 +1,6 @@
+require 'action_controller/performance_test'
+
+ActionController::Base.perform_caching = true
+ActionView::Base.cache_template_loading = true
+ActiveSupport::Dependencies.mechanism = :require
+Rails.logger.level = ActiveSupport::BufferedLogger::INFO
diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb
index 4cac7f725a..f8d97840c1 100644
--- a/railties/lib/rails/gem_dependency.rb
+++ b/railties/lib/rails/gem_dependency.rb
@@ -23,9 +23,13 @@ module Rails
@unpack_directory = nil
end
+ def unpacked_paths
+ Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")]
+ end
+
def add_load_paths
return if @loaded || @load_paths_added
- unpacked_paths = Dir[File.join(self.class.unpacked_path, "#{@name}-#{@version || "*"}")]
+ unpacked_paths = self.unpacked_paths
if unpacked_paths.empty?
args = [@name]
args << @requirement.to_s if @requirement
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index a54ab85dbe..b8b2b57038 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -95,14 +95,14 @@ module Rails
end
def evaluate_init_rb(initializer)
- if has_init_file?
- silence_warnings do
- # Allow plugins to reference the current configuration object
- config = initializer.configuration
-
- eval(IO.read(init_path), binding, init_path)
- end
- end
+ if has_init_file?
+ silence_warnings do
+ # Allow plugins to reference the current configuration object
+ config = initializer.configuration
+
+ eval(IO.read(init_path), binding, init_path)
+ end
+ end
end
end
@@ -111,8 +111,9 @@ module Rails
# to Dependencies.load_paths.
class GemPlugin < Plugin
# Initialize this plugin from a Gem::Specification.
- def initialize(spec)
- super(File.join(spec.full_gem_path))
+ def initialize(spec, gem)
+ directory = (gem.frozen? && gem.unpacked_paths.first) || File.join(spec.full_gem_path)
+ super(directory)
@name = spec.name
end
diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb
index f06a51a572..678b295dc9 100644
--- a/railties/lib/rails/plugin/locator.rb
+++ b/railties/lib/rails/plugin/locator.rb
@@ -63,7 +63,7 @@ module Rails
# => <Rails::Plugin name: 'acts_as_chunky_bacon' ... >
#
def locate_plugins_under(base_path)
- Dir.glob(File.join(base_path, '*')).inject([]) do |plugins, path|
+ Dir.glob(File.join(base_path, '*')).sort.inject([]) do |plugins, path|
if plugin = create_plugin(path)
plugins << plugin
elsif File.directory?(path)
@@ -78,8 +78,9 @@ module Rails
# a <tt>rails/init.rb</tt> file.
class GemLocator < Locator
def plugins
- specs = initializer.configuration.gems.map(&:specification)
- specs += Gem.loaded_specs.values.select do |spec|
+ gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem }
+ specs = gem_index.keys
+ specs += Gem.loaded_specs.values.select do |spec|
spec.loaded_from && # prune stubs
File.exist?(File.join(spec.full_gem_path, "rails", "init.rb"))
end
@@ -91,7 +92,7 @@ module Rails
deps.add(*specs) unless specs.empty?
deps.dependency_order.collect do |spec|
- Rails::GemPlugin.new(spec)
+ Rails::GemPlugin.new(spec, gem_index[spec])
end
end
end
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
index 08ecbfb5cf..d258aeaa0a 100644
--- a/railties/lib/rails_generator/commands.rb
+++ b/railties/lib/rails_generator/commands.rb
@@ -88,7 +88,7 @@ module Rails
Tempfile.open(File.basename(destination), File.dirname(dst)) do |temp|
temp.write render_file(src, file_options, &block)
temp.rewind
- $stdout.puts `#{diff_cmd} #{dst} #{temp.path}`
+ $stdout.puts `#{diff_cmd} "#{dst}" "#{temp.path}"`
end
puts "retrying"
raise 'retry diff'
@@ -154,35 +154,28 @@ HELP
# Ruby or Rails. In the future, expand to check other namespaces
# such as the rest of the user's app.
def class_collisions(*class_names)
-
- # Initialize some check varibles
- last_class = Object
- current_class = nil
- name = nil
-
class_names.flatten.each do |class_name|
# Convert to string to allow symbol arguments.
class_name = class_name.to_s
# Skip empty strings.
- class_name.strip.empty? ? next : current_class = class_name
+ next if class_name.strip.empty?
# Split the class from its module nesting.
nesting = class_name.split('::')
name = nesting.pop
# Extract the last Module in the nesting.
- last = nesting.inject(last_class) { |last, nest|
- break unless last_class.const_defined?(nest)
- last_class = last_class.const_get(nest)
+ last = nesting.inject(Object) { |last, nest|
+ break unless last.const_defined?(nest)
+ last.const_get(nest)
}
- end
- # If the last Module exists, check whether the given
- # class exists and raise a collision if so.
-
- if last_class and last_class.const_defined?(name.camelize)
- raise_class_collision(current_class)
+ # If the last Module exists, check whether the given
+ # class exists and raise a collision if so.
+ if last and last.const_defined?(name.camelize)
+ raise_class_collision(class_name)
+ end
end
end
diff --git a/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb
index cae38e9a2a..03f6d5666e 100644
--- a/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb
+++ b/railties/lib/rails_generator/generators/components/observer/templates/unit_test.rb
@@ -1,6 +1,6 @@
require 'test_helper'
-class <%= class_name %>ObserverTest < Test::Unit::TestCase
+class <%= class_name %>ObserverTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
diff --git a/railties/lib/rails_generator/generators/components/performance_test/USAGE b/railties/lib/rails_generator/generators/components/performance_test/USAGE
new file mode 100644
index 0000000000..d84051eb02
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/performance_test/USAGE
@@ -0,0 +1,8 @@
+Description:
+ Stubs out a new performance test. Pass the name of the test, either
+ CamelCased or under_scored, as an argument. The new test class is
+ generated in test/performance/testname_test.rb
+
+Example:
+ `./script/generate performance_test GeneralStories` creates a GeneralStories
+ performance test in test/performance/general_stories_test.rb
diff --git a/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb
new file mode 100644
index 0000000000..fbcc1cf683
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/performance_test/performance_test_generator.rb
@@ -0,0 +1,16 @@
+class PerformanceTestGenerator < Rails::Generator::NamedBase
+ default_options :skip_migration => false
+
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_path, class_name, "#{class_name}Test"
+
+ # performance test directory
+ m.directory File.join('test/performance', class_path)
+
+ # performance test stub
+ m.template 'performance_test.rb', File.join('test/performance', class_path, "#{file_name}_test.rb")
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb b/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb
new file mode 100644
index 0000000000..352ff48054
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/performance_test/templates/performance_test.rb
@@ -0,0 +1,8 @@
+require 'performance/test_helper'
+
+class <%= class_name %>Test < ActionController::PerformanceTest
+ # Replace this with your real tests.
+ def test_homepage
+ get '/'
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
index e2902bf4b9..5fecfe0167 100644
--- a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
@@ -1,5 +1,5 @@
class ScaffoldGenerator < Rails::Generator::NamedBase
- default_options :skip_timestamps => false, :skip_migration => false
+ default_options :skip_timestamps => false, :skip_migration => false, :force_plural => false
attr_reader :controller_name,
:controller_class_path,
@@ -16,6 +16,11 @@ class ScaffoldGenerator < Rails::Generator::NamedBase
def initialize(runtime_args, runtime_options = {})
super
+ if @name == @name.pluralize && !options[:force_plural]
+ logger.warning "Plural version of the model detected, using singularized version. Override with --force-plural."
+ @name = @name.singularize
+ end
+
@controller_name = @name.pluralize
base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
@@ -81,6 +86,8 @@ class ScaffoldGenerator < Rails::Generator::NamedBase
"Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v }
opt.on("--skip-migration",
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
+ opt.on("--force-plural",
+ "Forces the generation of a plural ModelName") { |v| options[:force_plural] = v }
end
def scaffold_views
diff --git a/railties/lib/rails_generator/lookup.rb b/railties/lib/rails_generator/lookup.rb
index 1f28c39d55..0526d526ad 100644
--- a/railties/lib/rails_generator/lookup.rb
+++ b/railties/lib/rails_generator/lookup.rb
@@ -108,7 +108,7 @@ module Rails
sources << PathSource.new(:vendor, "#{::RAILS_ROOT}/vendor/generators")
Rails.configuration.plugin_paths.each do |path|
relative_path = Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(::RAILS_ROOT))
- sources << PathSource.new(:"plugins (#{relative_path})", "#{path}/**/{,rails_}generators")
+ sources << PathSource.new(:"plugins (#{relative_path})", "#{path}/*/**/{,rails_}generators")
end
end
sources << PathSource.new(:user, "#{Dir.user_home}/.rails/generators")
diff --git a/railties/lib/tasks/annotations.rake b/railties/lib/tasks/annotations.rake
index ea6046670f..48ac40099a 100644
--- a/railties/lib/tasks/annotations.rake
+++ b/railties/lib/tasks/annotations.rake
@@ -6,18 +6,15 @@ task :notes do
end
namespace :notes do
- desc "Enumerate all OPTIMIZE annotations"
- task :optimize do
- SourceAnnotationExtractor.enumerate "OPTIMIZE"
+ ["OPTIMIZE", "FIXME", "TODO"].each do |annotation|
+ desc "Enumerate all #{annotation} annotations"
+ task annotation.downcase.intern do
+ SourceAnnotationExtractor.enumerate annotation
+ end
end
- desc "Enumerate all FIXME annotations"
- task :fixme do
- SourceAnnotationExtractor.enumerate "FIXME"
- end
-
- desc "Enumerate all TODO annotations"
- task :todo do
- SourceAnnotationExtractor.enumerate "TODO"
+ desc "Enumerate a custom annotation, specify with ANNOTATION=WTFHAX"
+ task :custom do
+ SourceAnnotationExtractor.enumerate ENV['ANNOTATION']
end
end \ No newline at end of file
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index 75fba8b45a..5ec712a02d 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -141,6 +141,9 @@ namespace :db do
when 'mysql'
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
+ when 'postgresql'
+ ActiveRecord::Base.establish_connection(config)
+ puts ActiveRecord::Base.connection.encoding
else
puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
end
@@ -179,12 +182,15 @@ namespace :db do
end
namespace :fixtures do
- desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y"
+ desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z."
task :load => :environment do
require 'active_record/fixtures'
- ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
- Fixtures.create_fixtures('test/fixtures', File.basename(fixture_file, '.*'))
+ ActiveRecord::Base.establish_connection(Rails.env)
+ base_dir = File.join(Rails.root, 'test', 'fixtures')
+ fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
+
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/).map {|f| File.join(fixtures_dir, f) } : Dir.glob(File.join(fixtures_dir, '*.{yml,csv}'))).each do |fixture_file|
+ Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
end
end
@@ -215,14 +221,14 @@ namespace :db do
desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
task :dump => :environment do
require 'active_record/schema_dumper'
- File.open(ENV['SCHEMA'] || "db/schema.rb", "w") do |file|
+ File.open(ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb", "w") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
end
desc "Load a schema.rb file into the database"
task :load => :environment do
- file = ENV['SCHEMA'] || "db/schema.rb"
+ file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb"
load(file)
end
end
@@ -234,7 +240,7 @@ namespace :db do
case abcs[RAILS_ENV]["adapter"]
when "mysql", "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
- File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"]
ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"]
@@ -252,13 +258,13 @@ namespace :db do
when "firebird"
set_firebird_env(abcs[RAILS_ENV])
db_string = firebird_db_string(abcs[RAILS_ENV])
- sh "isql -a #{db_string} > db/#{RAILS_ENV}_structure.sql"
+ sh "isql -a #{db_string} > #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
- File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
+ File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
end
end
end
@@ -281,28 +287,28 @@ namespace :db do
when "mysql"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
+ IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
when "postgresql"
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
+ `psql -U "#{abcs["test"]["username"]}" -f #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}`
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < db/#{RAILS_ENV}_structure.sql`
+ `#{abcs["test"]["adapter"]} #{dbfile} < #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql`
when "sqlserver"
`osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
- IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
+ IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when "firebird"
set_firebird_env(abcs["test"])
db_string = firebird_db_string(abcs["test"])
- sh "isql -i db/#{RAILS_ENV}_structure.sql #{db_string}"
+ sh "isql -i #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{db_string}"
else
raise "Task not supported by '#{abcs["test"]["adapter"]}'"
end
diff --git a/railties/lib/tasks/misc.rake b/railties/lib/tasks/misc.rake
index 61042595f9..33bbba1101 100644
--- a/railties/lib/tasks/misc.rake
+++ b/railties/lib/tasks/misc.rake
@@ -44,7 +44,7 @@ namespace :time do
end
end
previous_offset = nil
- TimeZone.__send__(method).each do |zone|
+ ActiveSupport::TimeZone.__send__(method).each do |zone|
if offset.nil? || offset == zone.utc_offset
puts "\n* UTC #{zone.formatted_offset} *" unless zone.utc_offset == previous_offset
puts zone.name
diff --git a/railties/lib/tasks/testing.rake b/railties/lib/tasks/testing.rake
index c8ba6eed94..328bde7442 100644
--- a/railties/lib/tasks/testing.rake
+++ b/railties/lib/tasks/testing.rake
@@ -66,10 +66,16 @@ namespace :test do
Rake::TestTask.new(:uncommitted => "db:test:prepare") do |t|
def t.file_list
- changed_since_checkin = silence_stderr { `svn status` }.map { |path| path.chomp[7 .. -1] }
+ if File.directory?(".svn")
+ changed_since_checkin = silence_stderr { `svn status` }.map { |path| path.chomp[7 .. -1] }
+ elsif File.directory?(".git")
+ changed_since_checkin = silence_stderr { `git ls-files --modified --others` }.map { |path| path.chomp }
+ else
+ abort "Not a Subversion or Git checkout."
+ end
- models = changed_since_checkin.select { |path| path =~ /app[\\\/]models[\\\/].*\.rb/ }
- controllers = changed_since_checkin.select { |path| path =~ /app[\\\/]controllers[\\\/].*\.rb/ }
+ models = changed_since_checkin.select { |path| path =~ /app[\\\/]models[\\\/].*\.rb$/ }
+ controllers = changed_since_checkin.select { |path| path =~ /app[\\\/]controllers[\\\/].*\.rb$/ }
unit_tests = models.map { |model| "test/unit/#{File.basename(model, '.rb')}_test.rb" }
functional_tests = controllers.map { |controller| "test/functional/#{File.basename(controller, '.rb')}_test.rb" }
@@ -80,7 +86,7 @@ namespace :test do
t.libs << 'test'
t.verbose = true
end
- Rake::Task['test:uncommitted'].comment = "Test changes since last checkin (only Subversion)"
+ Rake::Task['test:uncommitted'].comment = "Test changes since last checkin (only Subversion and Git)"
Rake::TestTask.new(:units => "db:test:prepare") do |t|
t.libs << "test"
diff --git a/railties/test/generators/rails_controller_generator_test.rb b/railties/test/generators/rails_controller_generator_test.rb
index 0090d21b85..8304fb5a01 100644
--- a/railties/test/generators/rails_controller_generator_test.rb
+++ b/railties/test/generators/rails_controller_generator_test.rb
@@ -17,4 +17,23 @@ class RailsControllerGeneratorTest < GeneratorTestCase
assert_generated_functional_test_for "admin::products"
assert_generated_helper_for "admin::products"
end
+
+ def test_controller_generates_namespaced_and_not_namespaced_controllers
+ run_generator('controller', %w(products))
+
+ # We have to require the generated helper to show the problem because
+ # the test helpers just check for generated files and contents but
+ # do not actually load them. But they have to be loaded (as in a real environment)
+ # to make the second generator run fail
+ require "#{RAILS_ROOT}/app/helpers/products_helper"
+
+ assert_nothing_raised do
+ begin
+ run_generator('controller', %w(admin::products))
+ ensure
+ # cleanup
+ Object.send(:remove_const, :ProductsHelper)
+ end
+ end
+ end
end
diff --git a/railties/test/generators/rails_scaffold_generator_test.rb b/railties/test/generators/rails_scaffold_generator_test.rb
index 220f9e372e..de6b38dafe 100644
--- a/railties/test/generators/rails_scaffold_generator_test.rb
+++ b/railties/test/generators/rails_scaffold_generator_test.rb
@@ -1,4 +1,5 @@
require 'generators/generator_test_helper'
+require 'abstract_unit'
class RailsScaffoldGeneratorTest < GeneratorTestCase
@@ -104,4 +105,45 @@ class RailsScaffoldGeneratorTest < GeneratorTestCase
assert_added_route_for :products
end
+ uses_mocha("scaffold_force_plural_names") do
+ def test_scaffolded_plural_names
+ Rails::Generator::Base.logger.expects(:warning)
+ g = Rails::Generator::Base.instance('scaffold', %w(ProductLines))
+ assert_equal "ProductLines", g.controller_name
+ assert_equal "ProductLines", g.controller_class_name
+ assert_equal "ProductLine", g.controller_singular_name
+ assert_equal "product_lines", g.controller_plural_name
+ assert_equal "product_lines", g.controller_file_name
+ assert_equal "product_lines", g.controller_table_name
+ end
+ end
+
+ def test_scaffold_plural_model_name_without_force_plural_generates_singular_model
+ run_generator('scaffold', %w(Products name:string))
+
+ assert_generated_model_for :product
+ assert_generated_functional_test_for :products
+ assert_generated_unit_test_for :product
+ assert_generated_fixtures_for :products
+ assert_generated_helper_for :products
+ assert_generated_stylesheet :scaffold
+ assert_generated_views_for :products, "index.html.erb","new.html.erb","edit.html.erb","show.html.erb"
+ assert_skipped_migration :create_products
+ assert_added_route_for :products
+ end
+
+ def test_scaffold_plural_model_name_with_force_plural_forces_plural_model
+ run_generator('scaffold', %w(Products name:string --force-plural))
+
+ assert_generated_model_for :products
+ assert_generated_functional_test_for :products
+ assert_generated_unit_test_for :products
+ assert_generated_fixtures_for :products
+ assert_generated_helper_for :products
+ assert_generated_stylesheet :scaffold
+ assert_generated_views_for :products, "index.html.erb","new.html.erb","edit.html.erb","show.html.erb"
+ assert_skipped_migration :create_products
+ assert_added_route_for :products
+ end
+
end
diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb
index e5fc0926eb..f429bae15c 100644
--- a/railties/test/plugin_loader_test.rb
+++ b/railties/test/plugin_loader_test.rb
@@ -94,7 +94,7 @@ uses_mocha "Plugin Loader Tests" do
def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array
only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon]
- stubbed_application_lib_index_in_LOAD_PATHS = 5
+ stubbed_application_lib_index_in_LOAD_PATHS = 4
@loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS)
@loader.add_plugin_load_paths