From 72759f58674138a89f68a1184bfc2df8327d9d0d Mon Sep 17 00:00:00 2001 From: Pan Thomakos Date: Fri, 11 Mar 2011 17:57:28 -0800 Subject: Fixed special character encoding issue with MemCacheStore in Ruby 1.9.2. --- .../lib/active_support/cache/mem_cache_store.rb | 5 ++- activesupport/test/caching_test.rb | 38 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 45263d482f..a4b20719cd 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -158,7 +158,10 @@ module ActiveSupport private def escape_key(key) - key = key.to_s.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"} + # Fix for EncodedKeyCacheBehavior failing tests in caching_test.rb. + key = key.to_s.dup + key = key.force_encoding(ESCAPE_KEY_CHARS.encoding) if key.respond_to?(:encoding) && key.encoding != ESCAPE_KEY_CHARS.encoding + key = key.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"} key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index e5668e29d7..476d55fffd 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -352,6 +352,43 @@ module CacheStoreBehavior end end +# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters +# The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special +# characters like the umlaut in UTF-8. +module EncodedKeyCacheBehavior + if defined?(Encoding) + Encoding.list.each do |encoding| + define_method "test_#{encoding.name.underscore}_encoded_values" do + key = "foo".force_encoding(encoding) + assert_equal true, @cache.write(key, "1", :raw => true) + assert_equal "1", @cache.read(key) + assert_equal "1", @cache.fetch(key) + assert_equal true, @cache.delete(key) + assert_equal "2", @cache.fetch(key, :raw => true) { "2" } + assert_equal 3, @cache.increment(key) + assert_equal 2, @cache.decrement(key) + end + end + + def test_common_utf8_values + key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) + assert_equal true, @cache.write(key, "1", :raw => true) + assert_equal "1", @cache.read(key) + assert_equal "1", @cache.fetch(key) + assert_equal true, @cache.delete(key) + assert_equal "2", @cache.fetch(key, :raw => true) { "2" } + assert_equal 3, @cache.increment(key) + assert_equal 2, @cache.decrement(key) + end + + def test_retains_encoding + key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8) + assert_equal true, @cache.write(key, "1", :raw => true) + assert_equal Encoding::UTF_8, key.encoding + end + end +end + module CacheDeleteMatchedBehavior def test_delete_matched @cache.write("foo", "bar") @@ -617,6 +654,7 @@ uses_memcached 'memcached backed store' do include CacheStoreBehavior include LocalCacheBehavior include CacheIncrementDecrementBehavior + include EncodedKeyCacheBehavior def test_raw_values cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true) -- cgit v1.2.3 From 36313430fa76fa79fd4da27daf5c018389aaff60 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sun, 13 Mar 2011 16:24:18 +0100 Subject: Missing dependency for duration test. --- activesupport/test/core_ext/duration_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index c0b529d9f8..c8312aa653 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/inflector' require 'active_support/time' require 'active_support/json' -- cgit v1.2.3 From e75774e216a4e1ba875511dc7d182133c36719aa Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sun, 13 Mar 2011 16:22:55 +0100 Subject: Add missing dependency string inflection test. --- activesupport/test/core_ext/string_ext_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index bb865cae91..9371965bbd 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -3,6 +3,7 @@ require 'date' require 'abstract_unit' require 'inflector_test_cases' +require 'active_support/inflector' require 'active_support/core_ext/string' require 'active_support/time' require 'active_support/core_ext/kernel/reporting' -- cgit v1.2.3 From 56a0cba7bedea46bb01f7f9e8db9398aeeac950b Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sun, 13 Mar 2011 16:21:18 +0100 Subject: Missing dependency in MultiByte test. --- activesupport/test/multibyte_chars_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 6ebbfdf334..bfff10fff2 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'abstract_unit' require 'multibyte_test_helpers' +require 'active_support/core_ext/string/multibyte' class String def __method_for_multibyte_testing_with_integer_result; 1; end -- cgit v1.2.3 From 76e4e2fa468593c42623472055d329dfeb577c49 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sun, 13 Mar 2011 16:52:34 +0100 Subject: Refactored AS::DescendantsTracker test cases so they can be tested without AS::Dependencies. --- activesupport/test/descendants_tracker_test.rb | 77 ---------------------- .../test/descendants_tracker_test_cases.rb | 59 +++++++++++++++++ .../descendants_tracker_with_autoloading_test.rb | 35 ++++++++++ ...descendants_tracker_without_autoloading_test.rb | 8 +++ 4 files changed, 102 insertions(+), 77 deletions(-) delete mode 100644 activesupport/test/descendants_tracker_test.rb create mode 100644 activesupport/test/descendants_tracker_test_cases.rb create mode 100644 activesupport/test/descendants_tracker_with_autoloading_test.rb create mode 100644 activesupport/test/descendants_tracker_without_autoloading_test.rb diff --git a/activesupport/test/descendants_tracker_test.rb b/activesupport/test/descendants_tracker_test.rb deleted file mode 100644 index 79fb893592..0000000000 --- a/activesupport/test/descendants_tracker_test.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'abstract_unit' -require 'test/unit' -require 'active_support' -require 'active_support/core_ext/hash/slice' - -class DescendantsTrackerTest < Test::Unit::TestCase - class Parent - extend ActiveSupport::DescendantsTracker - end - - class Child1 < Parent - end - - class Child2 < Parent - end - - class Grandchild1 < Child1 - end - - class Grandchild2 < Child1 - end - - ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2] - - def test_descendants - assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants - assert_equal [Grandchild1, Grandchild2], Child1.descendants - assert_equal [], Child2.descendants - end - - def test_direct_descendants - assert_equal [Child1, Child2], Parent.direct_descendants - assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants - assert_equal [], Child2.direct_descendants - end - - def test_clear_with_autoloaded_parent_children_and_granchildren - mark_as_autoloaded(*ALL) do - ActiveSupport::DescendantsTracker.clear - ALL.each do |k| - assert ActiveSupport::DescendantsTracker.descendants(k).empty? - end - end - end - - def test_clear_with_autoloaded_children_and_granchildren - mark_as_autoloaded Child1, Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal [Child2], Parent.descendants - assert_equal [], Child2.descendants - end - end - - def test_clear_with_autoloaded_granchildren - mark_as_autoloaded Grandchild1, Grandchild2 do - ActiveSupport::DescendantsTracker.clear - assert_equal [Child1, Child2], Parent.descendants - assert_equal [], Child1.descendants - assert_equal [], Child2.descendants - end - end - - protected - - def mark_as_autoloaded(*klasses) - old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup - ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) - - old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup - old_descendants.each { |k, v| old_descendants[k] = v.dup } - - yield - ensure - ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded - ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) - end -end \ No newline at end of file diff --git a/activesupport/test/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb new file mode 100644 index 0000000000..066ec8549b --- /dev/null +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -0,0 +1,59 @@ +module DescendantsTrackerTestCases + class Parent + extend ActiveSupport::DescendantsTracker + end + + class Child1 < Parent + end + + class Child2 < Parent + end + + class Grandchild1 < Child1 + end + + class Grandchild2 < Child1 + end + + ALL = [Parent, Child1, Child2, Grandchild1, Grandchild2] + + def test_descendants + assert_equal [Child1, Grandchild1, Grandchild2, Child2], Parent.descendants + assert_equal [Grandchild1, Grandchild2], Child1.descendants + assert_equal [], Child2.descendants + end + + def test_direct_descendants + assert_equal [Child1, Child2], Parent.direct_descendants + assert_equal [Grandchild1, Grandchild2], Child1.direct_descendants + assert_equal [], Child2.direct_descendants + end + + def test_clear + mark_as_autoloaded(*ALL) do + ActiveSupport::DescendantsTracker.clear + ALL.each do |k| + assert ActiveSupport::DescendantsTracker.descendants(k).empty? + end + end + end + + protected + + def mark_as_autoloaded(*klasses) + # If ActiveSupport::Dependencies is not loaded, forget about autoloading. + # This allows using AS::DescendantsTracker without AS::Dependencies. + if defined? ActiveSupport::Dependencies + old_autoloaded = ActiveSupport::Dependencies.autoloaded_constants.dup + ActiveSupport::Dependencies.autoloaded_constants = klasses.map(&:name) + end + + old_descendants = ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").dup + old_descendants.each { |k, v| old_descendants[k] = v.dup } + + yield + ensure + ActiveSupport::Dependencies.autoloaded_constants = old_autoloaded if defined? ActiveSupport::Dependencies + ActiveSupport::DescendantsTracker.class_eval("@@direct_descendants").replace(old_descendants) + end +end \ No newline at end of file diff --git a/activesupport/test/descendants_tracker_with_autoloading_test.rb b/activesupport/test/descendants_tracker_with_autoloading_test.rb new file mode 100644 index 0000000000..ae18a56f44 --- /dev/null +++ b/activesupport/test/descendants_tracker_with_autoloading_test.rb @@ -0,0 +1,35 @@ +require 'abstract_unit' +require 'test/unit' +require 'active_support/descendants_tracker' +require 'active_support/dependencies' +require 'descendants_tracker_test_cases' + +class DescendantsTrackerWithAutoloadingTest < Test::Unit::TestCase + include DescendantsTrackerTestCases + + def test_clear_with_autoloaded_parent_children_and_granchildren + mark_as_autoloaded(*ALL) do + ActiveSupport::DescendantsTracker.clear + ALL.each do |k| + assert ActiveSupport::DescendantsTracker.descendants(k).empty? + end + end + end + + def test_clear_with_autoloaded_children_and_granchildren + mark_as_autoloaded Child1, Grandchild1, Grandchild2 do + ActiveSupport::DescendantsTracker.clear + assert_equal [Child2], Parent.descendants + assert_equal [], Child2.descendants + end + end + + def test_clear_with_autoloaded_granchildren + mark_as_autoloaded Grandchild1, Grandchild2 do + ActiveSupport::DescendantsTracker.clear + assert_equal [Child1, Child2], Parent.descendants + assert_equal [], Child1.descendants + assert_equal [], Child2.descendants + end + end +end \ No newline at end of file diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb new file mode 100644 index 0000000000..1f0c32dc3f --- /dev/null +++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb @@ -0,0 +1,8 @@ +require 'abstract_unit' +require 'test/unit' +require 'active_support/descendants_tracker' +require 'descendants_tracker_test_cases' + +class DescendantsTrackerWithoutAutoloadingTest < Test::Unit::TestCase + include DescendantsTrackerTestCases +end \ No newline at end of file -- cgit v1.2.3 From d89a7967b5af5c87bbfc268af72287b82541d384 Mon Sep 17 00:00:00 2001 From: Rolf Timmermans Date: Sun, 13 Mar 2011 17:08:33 +0100 Subject: Revert "Revert "It should be possible to use ActiveSupport::DescendantTracker without getting ActiveSupport::Dependencies for free."" This reverts commit 9f5b1e1ed08df9dbedded0a6b7798d919d43b9a6. Tests have been refactored so they pass in isolation. --- activesupport/lib/active_support/descendants_tracker.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb index 4d1cfacc95..e2a8b4d4e3 100644 --- a/activesupport/lib/active_support/descendants_tracker.rb +++ b/activesupport/lib/active_support/descendants_tracker.rb @@ -1,5 +1,3 @@ -require 'active_support/dependencies' - module ActiveSupport # This module provides an internal implementation to track descendants # which is faster than iterating through ObjectSpace. @@ -18,12 +16,16 @@ module ActiveSupport end def self.clear - @@direct_descendants.each do |klass, descendants| - if ActiveSupport::Dependencies.autoloaded?(klass) - @@direct_descendants.delete(klass) - else - descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if ActiveSupport::Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + end end + else + @@direct_descendants.clear end end -- cgit v1.2.3 From 080e2a7abf8314338a41f72821703b23c86b3c45 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Apr 2011 12:14:53 -0700 Subject: Enumerable should pass encoding options to children in #as_json/#to_json. --- activesupport/lib/active_support/json/encoding.rb | 4 ++- activesupport/test/json/encoding_test.rb | 33 ++++++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 82b8a7e148..1fafc36ee8 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -205,7 +205,9 @@ class Regexp end module Enumerable - def as_json(options = nil) to_a end #:nodoc: + def as_json(options = nil) #:nodoc: + to_a.as_json(options) + end end class Array diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index d5fcbf15b7..8cf1a54a99 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -215,6 +215,30 @@ class TestJSONEncoding < Test::Unit::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + def test_enumerable_should_pass_encoding_options_to_children_in_as_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.each.as_json :only => [:address, :city] + expected = [ + { 'address' => { 'city' => 'London' }}, + { 'address' => { 'city' => 'Paris' }} + ] + + assert_equal(expected, json) + end + + def test_enumerable_should_pass_encoding_options_to_children_in_to_json + people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + json = people.each.to_json :only => [:address, :city] + + assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) + end + def test_struct_encoding Struct.new('UserNameAndEmail', :name, :email) Struct.new('UserNameAndDate', :name, :date) @@ -259,12 +283,3 @@ class TestJSONEncoding < Test::Unit::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end - -class JsonOptionsTests < Test::Unit::TestCase - def test_enumerable_should_passthrough_options_to_elements - value, options = Object.new, Object.new - def value.as_json(options) options end - def options.encode_json(encoder) self end - assert_equal options, ActiveSupport::JSON.encode(value, options) - end -end -- cgit v1.2.3 From 4e873ffcdab0c445e2211db1d27ddd5b349f7913 Mon Sep 17 00:00:00 2001 From: Date: Tue, 12 Apr 2011 00:59:55 -0700 Subject: Corrected dbconsole usage message. --- railties/lib/rails/commands/dbconsole.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index f0d6ea1687..b0ba76217a 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -23,7 +23,7 @@ module Rails include_password = false options = {} OptionParser.new do |opt| - opt.banner = "Usage: dbconsole [options] [environment]" + opt.banner = "Usage: dbconsole [environment] [options]" opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v| include_password = true end -- cgit v1.2.3 From dbf32eb6ca1c0666c6826992af1d986d0e81b2c3 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 12 Apr 2011 18:58:41 -0300 Subject: Changed Object#either? to Object#among? on guides --- .../guides/source/active_support_core_extensions.textile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 3ba840c044..6e65771b1d 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -442,9 +442,9 @@ require_library_or_gem('mysql') NOTE: Defined in +active_support/core_ext/kernel/requires.rb+. -h4. +in?+ and +either?+ +h4. +in?+ and +among?+ -The predicate +in?+ tests if an object is included in another object, and the predicate +either?+ tests if an object is included in a list of objects which will be passed as arguments. +The predicate +in?+ tests if an object is included in another object, and the predicate +among?+ tests if an object is included in a list of objects which will be passed as arguments. Examples of +in?+: @@ -454,12 +454,12 @@ Examples of +in?+: 25.in?(30..50) # => false -Examples of +either?+: +Examples of +among?+: - 1.either?(1,2,3) # => true - 5.either?(1,2,3) # => false - [1,2,3].either?([1,2,3], 2, [3,4,5]) # => true + 1.among?(1,2,3) # => true + 5.among?(1,2,3) # => false + [1,2,3].among?([1,2,3], 2, [3,4,5]) # => true NOTE: Defined in +active_support/core_ext/object/inclusion.rb+. -- cgit v1.2.3 From b2cb99125138bcec3206562a1447991bb3fef63d Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Wed, 13 Apr 2011 09:59:01 -0300 Subject: Removed Object#among? from guides --- .../guides/source/active_support_core_extensions.textile | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 6e65771b1d..4cedba1d4d 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -442,9 +442,9 @@ require_library_or_gem('mysql') NOTE: Defined in +active_support/core_ext/kernel/requires.rb+. -h4. +in?+ and +among?+ +h4. +in?+ -The predicate +in?+ tests if an object is included in another object, and the predicate +among?+ tests if an object is included in a list of objects which will be passed as arguments. +The predicate +in?+ tests if an object is included in another object. Examples of +in?+: @@ -454,14 +454,6 @@ Examples of +in?+: 25.in?(30..50) # => false -Examples of +among?+: - - - 1.among?(1,2,3) # => true - 5.among?(1,2,3) # => false - [1,2,3].among?([1,2,3], 2, [3,4,5]) # => true - - NOTE: Defined in +active_support/core_ext/object/inclusion.rb+. h3. Extensions to +Module+ -- cgit v1.2.3 From 21c03a2f48b4ac9acb6814aa3686bc7a6863120d Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Wed, 13 Apr 2011 15:42:44 -0300 Subject: Update guides with new scaffold.css path --- railties/guides/source/generators.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index d32ba48003..44c15be7bf 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -190,7 +190,7 @@ $ rails generate scaffold User name:string invoke test_unit create test/unit/helpers/users_helper_test.rb invoke stylesheets - create public/stylesheets/scaffold.css + create app/assets/stylesheets/scaffold.css Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. -- cgit v1.2.3 From 04a75e02894e15aa3aaf92e3e856b091a2386f14 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Wed, 13 Apr 2011 17:19:47 -0400 Subject: Moving note out of prologue to match other guides and so it'll display correctly --- railties/guides/source/command_line.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index f3e8d880df..ac3a1c0404 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -8,10 +8,10 @@ Rails comes with every command line tool you'll need to * Mess with objects through an interactive shell * Profile and benchmark your new creation -NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html. - endprologue. +NOTE: This tutorial assumes you have basic Rails knowledge from reading the "Getting Started with Rails Guide":getting_started.html. + WARNING. This Guide is based on Rails 3.0. Some of the code shown here will not work in earlier versions of Rails. h3. Command Line Basics -- cgit v1.2.3 From f8ecb46e82154979a548d36183b25375982aca08 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Wed, 13 Apr 2011 19:54:31 -0300 Subject: Documented +ActiveSupport::Notifications+ module. --- railties/guides/source/initialization.textile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 7c01f01b24..33045954ee 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -592,7 +592,11 @@ This file defines the behavior of the +ActiveSupport::Deprecation+ module, setti h4. +activesupport/lib/active_support/notifications.rb+ -TODO: document +ActiveSupport::Notifications+. +This file defines the +ActiveSupport::Notifications+ module. Notifications provides an instrumentation API for Ruby, shipping with a queue implementation that consumes and publish events to log subscribers in a thread. + +The "API documentation":http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html for +ActiveSupport::Notifications+ explains the usage of this module, including the methods that it defines. + +The file required in +active_support/notifications.rb+ is +active_support/core_ext/module/delegation+ which is documented in the "Active Support Core Extensions Guide":http://guides.rubyonrails.org/active_support_core_extensions.html#method-delegation. h4. +activesupport/core_ext/array/wrap+ -- cgit v1.2.3 From 1447ff56545f53101c5fddbdea5667a8ff68cd8e Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Wed, 13 Apr 2011 20:17:22 -0400 Subject: Making identifiers consistent in example --- railties/guides/source/caching_with_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 297ba2d661..995a835ce8 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -322,7 +322,7 @@ You can use Hashes and Arrays of values as cache keys. # This is a legal cache key -Rails.cache.read(:site => "mysite", :owners => [owner_1, owner2]) +Rails.cache.read(:site => "mysite", :owners => [owner_1, owner_2]) The keys you use on +Rails.cache+ will not be the same as those actually used with the storage engine. They may be modified with a namespace or altered to fit technology backend constraints. This means, for instance, that you can't save values with +Rails.cache+ and then try to pull them out with the +memcache-client+ gem. However, you also don't need to worry about exceeding the memcached size limit or violating syntax rules. -- cgit v1.2.3 From e56b19bbed275bd6d2bd0574300e5f0f6b85add0 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Wed, 13 Apr 2011 20:32:36 -0400 Subject: Aligning table cells --- railties/guides/source/testing.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index 2809c6d076..3b4cb3d8a5 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -415,8 +415,8 @@ NOTE: +assert_valid(record)+ has been deprecated. Please use +assert(record.vali |_.Assertion |_.Purpose| |+assert_valid(record)+ |Ensures that the passed record is valid by Active Record standards and returns any error messages if it is not.| -|+assert_difference(expressions, difference = 1, message = nil) {...}+ |Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| -|+assert_no_difference(expressions, message = nil, &block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| +|+assert_difference(expressions, difference = 1, message = nil) {...}+ |Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| +|+assert_no_difference(expressions, message = nil, &block)+ |Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| |+assert_recognizes(expected_options, path, extras={}, message=nil)+ |Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| |+assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)+ |Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| |+assert_response(type, message = nil)+ |Asserts that the response comes with a specific status code. You can specify +:success+ to indicate 200, +:redirect+ to indicate 300-399, +:missing+ to indicate 404, or +:error+ to match the 500-599 range| -- cgit v1.2.3 From 974a6aa176ff5549e00f1a126ba5d46fa175c59e Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Wed, 13 Apr 2011 20:49:14 -0400 Subject: Remove trailing whitespace --- railties/guides/source/initialization.textile | 12 ++++---- railties/guides/source/plugins.textile | 34 +++++++++++----------- .../source/ruby_on_rails_guides_guidelines.textile | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 33045954ee..013eca2a35 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -468,7 +468,7 @@ h4. +config/application.rb+ This file requires +config/boot.rb+, but only if it hasn't been required before, which would be the case in +rails server+ but *wouldn't* be the case with Passenger. -Then the fun begins! +Then the fun begins! h3. Loading Rails @@ -664,7 +664,7 @@ The +active_support/inflector/methods+ file has already been required by +active h4. +activesupport/lib/active_support/inflector/inflections.rb+ -This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". +This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". h4. +activesupport/lib/active_support/inflector/transliterate.rb+ @@ -698,7 +698,7 @@ h4. Back to +railties/lib/rails/plugin.rb+ The next file required in this is a core extension from Active Support called +array/conversions+ which is covered in "this section":http://guides.rubyonrails.org/active_support_core_extensions.html#array-conversions of the Active Support Core Extensions Guide. -Once that file has finished loading, the +Rails::Plugin+ class is defined. +Once that file has finished loading, the +Rails::Plugin+ class is defined. h4. Back to +railties/lib/rails/application.rb+ @@ -708,7 +708,7 @@ Once this file's done then we go back to the +railties/lib/rails.rb+ file, which h4. +railties/lib/rails/version.rb+ -Much like +active_support/version+, this file defines the +VERSION+ constant which has a +STRING+ constant on it which returns the current version of Rails. +Much like +active_support/version+, this file defines the +VERSION+ constant which has a +STRING+ constant on it which returns the current version of Rails. Once this file has finished loading we go back to +railties/lib/rails.rb+ which then requires +active_support/railtie.rb+. @@ -926,7 +926,7 @@ This file defines the +ActionDispatch::Railtie+ class, but not before requiring h4. +activesupport/lib/action_dispatch.rb+ -This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. +This file attempts to locate the +active_support+ and +active_model+ libraries by looking a couple of directories back from the current file and then adds the +active_support+ and +active_model+ +lib+ directories to the load path, but only if they aren't already, which they are. activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) @@ -1112,4 +1112,4 @@ However the require after these is to a file that hasn't yet been loaded, +actio h4. +actionpack/lib/action_view.rb+ -+action_view.rb+ ++action_view.rb+ diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 2d9821e627..31158d8742 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -17,8 +17,8 @@ This guide describes how to build a test-driven plugin that will: * Add methods to ActiveRecord::Base in the tradition of the 'acts_as' plugins * Give you information about where to put generators in your plugin. -For the purpose of this guide pretend for a moment that you are an avid bird watcher. -Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle +For the purpose of this guide pretend for a moment that you are an avid bird watcher. +Your favorite bird is the Yaffle, and you want to create a plugin that allows other developers to share in the Yaffle goodness. endprologue. @@ -27,21 +27,21 @@ h3. Setup h4. Generating the Plugin Skeleton -Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain +Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain how this generator works. $ rails generate plugin --help -This generator places the plugin into the vendor/plugins directory. +This generator places the plugin into the vendor/plugins directory. -Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards +Vendored plugins are useful for quickly prototyping your plugin but current thinking in the Rails community is shifting towards packaging plugins as gems, especially with the inclusion of Bundler as the Rails dependency manager. Packaging a plugin as a gem may be overkill for any plugins that will not be shared across projects but doing so from the start makes it easier to share the plugin going forward without adding too much additional overhead during development. Rails 3.1 will ship with a plugin generator that will default to setting up a plugin -as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the +as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the "Enginex gem":http://www.github.com/josevalim/enginex. @@ -133,7 +133,7 @@ $ rails console h3. Add an "acts_as" Method to Active Record -A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you +A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models. To begin, set up your files so that you have: @@ -169,8 +169,8 @@ end h4. Add a Class Method -This plugin will expect that you've added a method to your model named 'last_squawk'. However, the -plugin users might have already defined a method on their model named 'last_squawk' that they use +This plugin will expect that you've added a method to your model named 'last_squawk'. However, the +plugin users might have already defined a method on their model named 'last_squawk' that they use for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. To start out, write a failing test that shows the behavior you'd like: @@ -210,7 +210,7 @@ When you run +rake+, you should see the following: This tells us that we don't have the necessary models (Hickwall and Wickwall) that we are trying to test. -We can easily generate these models in our "dummy" Rails application by running the following commands from the +We can easily generate these models in our "dummy" Rails application by running the following commands from the test/dummy directory: @@ -220,7 +220,7 @@ $ rails generate model Wickwall last_squak:string last_tweet:string Now you can create the necessary database tables in your testing database by navigating to your dummy app -and migrating the database. First +and migrating the database. First $ cd test/dummy @@ -319,7 +319,7 @@ When you run +rake+ you should see the tests all pass: h4. Add an Instance Method -This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' +This plugin will add a method named 'squawk' to any Active Record objects that call 'acts_as_yaffle'. The 'squawk' method will simply set the value of one of the fields in the database. To start out, write a failing test that shows the behavior you'd like: @@ -352,7 +352,7 @@ class ActsAsYaffleTest < Test::Unit::TestCase end -Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'", +Run the test to make sure the last two tests fail the an error that contains "NoMethodError: undefined method `squawk'", then update 'acts_as_yaffle.rb' to look like this: @@ -387,8 +387,8 @@ Run +rake+ one final time and you should see: 7 tests, 7 assertions, 0 failures, 0 errors, 0 skips -NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can -interact with the model, and will not always be the right method to use. For example, you could also +NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can +interact with the model, and will not always be the right method to use. For example, you could also use +send("#{self.class.yaffle_text_field}=", string.to_squawk)+. h3. Generators @@ -398,7 +398,7 @@ the creation of generators can be found in the "Generators Guide":generators.htm h3. Publishing your Gem -Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply +Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply commit the code to a Git repository (like Github) and add a line to the Gemfile of the any application: @@ -423,7 +423,7 @@ Move the directory that you created for the gem based plugin into the vendor/plu require 'yaffle' -You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the +You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the console we can check to see if the String has an instance method of to_squawk. $ cd my_app diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile index 8e55780dca..26a5a4c3c9 100644 --- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile +++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile @@ -13,7 +13,7 @@ h3. Prologue Each guide should start with motivational text at the top (that's the little introduction in the blue area.) The prologue should tell the reader what the guide is about, and what they will learn. See for example the "Routing Guide":routing.html. h3. Titles - + The title of every guide uses +h2+, guide sections use +h3+, subsections +h4+, etc. Capitalize all words except for internal articles, prepositions, conjunctions, and forms of the verb to be: -- cgit v1.2.3 From 2f242652374d40726a5076a531a667e4482744df Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Wed, 13 Apr 2011 20:58:26 -0400 Subject: Making colon usage consistent --- .../guides/source/active_record_querying.textile | 2 +- .../source/rails_application_templates.textile | 44 +++++++++++----------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index df8e35ed33..b970c55327 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -993,7 +993,7 @@ Client.count # SELECT count(*) AS count_all FROM clients -Or on a relation : +Or on a relation: Client.where(:first_name => 'Ryan').count diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile index 8e51f9e23b..388d8eea3e 100644 --- a/railties/guides/source/rails_application_templates.textile +++ b/railties/guides/source/rails_application_templates.textile @@ -11,19 +11,19 @@ endprologue. h3. Usage -To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option : +To apply a template, you need to provide the Rails generator with the location of the template you wish to apply, using -m option: $ rails new blog -m ~/template.rb -It's also possible to apply a template using a URL : +It's also possible to apply a template using a URL: $ rails new blog -m https://gist.github.com/755496.txt -Alternatively, you can use the rake task +rails:template+ to apply a template to an existing Rails application : +Alternatively, you can use the rake task +rails:template+ to apply a template to an existing Rails application: $ rake rails:template LOCATION=~/template.rb @@ -31,7 +31,7 @@ $ rake rails:template LOCATION=~/template.rb h3. Template API -Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template : +Rails templates API is very self explanatory and easy to understand. Here's an example of a typical Rails template: # template.rb @@ -45,20 +45,20 @@ git :add => "." git :commit => "-a -m 'Initial commit'" -The following sections outlines the primary methods provided by the API : +The following sections outlines the primary methods provided by the API: h4. gem(name, options = {}) Adds a +gem+ entry for the supplied gem to the generated application’s +Gemfile+. -For example, if your application depends on the gems +bj+ and +nokogiri+ : +For example, if your application depends on the gems +bj+ and +nokogiri+: gem "bj" gem "nokogiri" -Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too : +Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too: rake "gems:install" @@ -80,13 +80,13 @@ h4. plugin(name, options = {}) Installs a plugin to the generated application. -Plugin can be installed from Git : +Plugin can be installed from Git: plugin 'authentication', :git => 'git://github.com/foor/bar.git' -You can even install plugins as git submodules : +You can even install plugins as git submodules: plugin 'authentication', :git => 'git://github.com/foor/bar.git', @@ -95,7 +95,7 @@ plugin 'authentication', :git => 'git://github.com/foor/bar.git', Please note that you need to +git :init+ before you can install a plugin as a submodule. -Or use plain old SVN : +Or use plain old SVN: plugin 'usingsvn', :svn => 'svn://example.com/usingsvn/trunk' @@ -105,7 +105,7 @@ h4. vendor/lib/file/initializer(filename, data = nil, &block) Adds an initializer to the generated application’s +config/initializers+ directory. -Lets say you like using +Object#not_nil?+ and +Object#not_blank?+ : +Lets say you like using +Object#not_nil?+ and +Object#not_blank?+: initializer 'bloatlol.rb', <<-CODE @@ -123,7 +123,7 @@ CODE Similarly +lib()+ creates a file in the +lib/+ directory and +vendor()+ creates a file in the +vendor/+ directory. -There is even +file()+, which accepts a relative path from +Rails.root+ and creates all the directories/file needed : +There is even +file()+, which accepts a relative path from +Rails.root+ and creates all the directories/file needed: file 'app/components/foo.rb', <<-CODE @@ -136,7 +136,7 @@ That’ll create +app/components+ directory and put +foo.rb+ in there. h4. rakefile(filename, data = nil, &block) -Creates a new rake file under +lib/tasks+ with the supplied tasks : +Creates a new rake file under +lib/tasks+ with the supplied tasks: rakefile("bootstrap.rake") do @@ -154,7 +154,7 @@ The above creates +lib/tasks/bootstrap.rake+ with a +boot:strap+ rake task. h4. generate(what, args) -Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails : +Runs the supplied rails generator with given arguments. For example, I love to scaffold some whenever I’m playing with Rails: generate(:scaffold, "person", "name:string", "address:text", "age:number") @@ -162,7 +162,7 @@ generate(:scaffold, "person", "name:string", "address:text", "age:number") h4. run(command) -Executes an arbitrary command. Just like the backticks. Let's say you want to remove the +public/index.html+ file : +Executes an arbitrary command. Just like the backticks. Let's say you want to remove the +public/index.html+ file: run "rm public/index.html" @@ -170,19 +170,19 @@ run "rm public/index.html" h4. rake(command, options = {}) -Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database : +Runs the supplied rake tasks in the Rails application. Let's say you want to migrate the database: rake "db:migrate" -You can also run rake tasks with a different Rails environment : +You can also run rake tasks with a different Rails environment: rake "db:migrate", :env => 'production' -Or even use sudo : +Or even use sudo: rake "gems:install", :sudo => true @@ -190,7 +190,7 @@ rake "gems:install", :sudo => true h4. route(routing_code) -This adds a routing entry to the +config/routes.rb+ file. In above steps, we generated a person scaffold and also removed +public/index.html+. Now to make +PeopleController#index+ as the default page for the application : +This adds a routing entry to the +config/routes.rb+ file. In above steps, we generated a person scaffold and also removed +public/index.html+. Now to make +PeopleController#index+ as the default page for the application: route "root :to => 'person#index'" @@ -208,7 +208,7 @@ end h4. ask(question) -+ask()+ gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding : ++ask()+ gives you a chance to get some feedback from the user and use it in your templates. Lets say you want your user to name the new shiny library you’re adding: lib_name = ask("What do you want to call the shiny library ?") @@ -222,7 +222,7 @@ CODE h4. yes?(question) or no?(question) -These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to : +These methods let you ask questions from templates and decide the flow based on the user’s answer. Lets say you want to freeze rails only if the user want to: rake("rails:freeze:gems") if yes?("Freeze rails gems ?") @@ -231,7 +231,7 @@ no?(question) acts just the opposite. h4. git(:must => "-a love") -Rails templates let you run any git command : +Rails templates let you run any git command: git :init -- cgit v1.2.3 From beed866e4335e8418ced59e293b6a71d12214f4f Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Wed, 13 Apr 2011 23:38:05 -0300 Subject: Fix patch file name to make it consistent with the rest of the guide --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 1fcc4fd7e3..9ac8d11c07 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -361,7 +361,7 @@ $ git apply --check my_new_patch.diff Please make sure the patch does not introduce whitespace errors: -$ git apply --whitespace=error-all mynew_patch.diff +$ git apply --whitespace=error-all my_new_patch.diff -- cgit v1.2.3 From 02883a1090e327b115c12c3f1dc97f1474ef3c9f Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 09:36:22 -0300 Subject: Update Contributing to the Rails Code guide for automatically change the status of LH tickets --- .../guides/source/contributing_to_ruby_on_rails.textile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 9ac8d11c07..ded67f0e4f 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -314,14 +314,20 @@ You should not be the only person who looks at the code before you submit it. Yo You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches. +h4. Create a Lighthouse Ticket + +Now create a ticket for your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, as well as tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. Write down your ticket number, for you will need it in the following step. + h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: -$ git commit -a -m "Here is a commit message" +$ git commit -a -m "[#ticket_number state:committed] Here is a commit message" +NOTE: By adding '[#ticket_number state:committed]' on your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository. + h4. Update master It’s pretty likely that other changes to master have happened while you were working. Go get them: @@ -364,10 +370,9 @@ Please make sure the patch does not introduce whitespace errors: $ git apply --whitespace=error-all my_new_patch.diff +h4. Attach your Patch to the Lighthouse Ticket -h4. Create a Lighthouse Ticket - -Now create a ticket with your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, remember to attach your patch file, and tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. +Now you need to update the ticket by attaching the patch file you just created. h4. Get Some Feedback @@ -385,6 +390,7 @@ All contributions, either via master or docrails, get credit in "Rails Contribut h3. Changelog +* April 14, 2001: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com * December 28, 2010: Complete revision by "Xavier Noria":credits.html#fxn * April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy -- cgit v1.2.3 From 9aff7e0898cd9f84fdbd961d895440523c100c82 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 10:04:10 -0300 Subject: Update guides with controller generator producing asset stubs --- railties/guides/source/command_line.textile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index ac3a1c0404..7b2ab92ccc 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -144,10 +144,13 @@ $ rails generate controller Greetings hello create app/helpers/greetings_helper.rb invoke test_unit create test/unit/helpers/greetings_helper_test.rb + invoke assets + create app/assets/javascripts/greetings.js + create app/assets/stylesheets/greetings.css -What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a functional test file, a helper for the view, and a view file. +What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file. Check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+): -- cgit v1.2.3 From 1bcc7055d840b39d1cc7fff934a62be5ea3c9539 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 10:05:08 -0300 Subject: Remove extra whitespace --- railties/guides/source/command_line.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 7b2ab92ccc..a46087c91b 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -150,7 +150,7 @@ $ rails generate controller Greetings hello -What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file. +What all did this generate? It made sure a bunch of directories were in our application, and created a controller file, a view file, a functional test file, a helper for the view, a javascript file and a stylesheet file. Check out the controller and modify it a little (in +app/controllers/greetings_controller.rb+): -- cgit v1.2.3 From 28bf4c67cab5cf266eeccb79bd543202daea3578 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 10:59:48 -0300 Subject: Move [#ticket_number state:commited] to the end of the commit message --- railties/guides/source/contributing_to_ruby_on_rails.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index ded67f0e4f..cbc4acfeca 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -323,10 +323,10 @@ h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: -$ git commit -a -m "[#ticket_number state:committed] Here is a commit message" +$ git commit -a -m "Here is a commit message [#ticket_number state:committed]" -NOTE: By adding '[#ticket_number state:committed]' on your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository. +NOTE: By adding '[#ticket_number state:committed]' at the end of your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository. h4. Update master -- cgit v1.2.3 From 9257106b09714feb0904bc540e8995953d979622 Mon Sep 17 00:00:00 2001 From: "Philipp Kempgen (Amooma)" Date: Thu, 14 Apr 2011 07:51:14 -0700 Subject: properly escape "'" to "'" for XML/HTML (BTW Erubis does that as well) --- activesupport/lib/active_support/core_ext/string/output_safety.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c27cbc37c5..252168dacc 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } # A utility method for escaping HTML tag characters. @@ -20,7 +20,7 @@ class ERB if s.html_safe? s else - s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }.html_safe + s.gsub(/[&"'><]/) { |special| HTML_ESCAPE[special] }.html_safe end end -- cgit v1.2.3 From 328a16b31ef6d8970a1d8fe4243c2973e4fd8b94 Mon Sep 17 00:00:00 2001 From: "Philipp Kempgen (Amooma)" Date: Thu, 14 Apr 2011 07:55:10 -0700 Subject: for escaping HTML can be treated as normal XML --- activesupport/lib/active_support/core_ext/string/output_safety.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 252168dacc..6f410819ba 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } + XML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } # A utility method for escaping HTML tag characters. @@ -20,7 +20,7 @@ class ERB if s.html_safe? s else - s.gsub(/[&"'><]/) { |special| HTML_ESCAPE[special] }.html_safe + s.gsub(/[&"'><]/) { |special| XML_ESCAPE[special] }.html_safe end end -- cgit v1.2.3 From 5be72684f6bbaf83359864b3f46c156de1d9e3ba Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 13:45:00 -0300 Subject: Update guide mentioning that +in?+ may raise an +ArgumentError+ exception --- railties/guides/source/active_support_core_extensions.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 4cedba1d4d..b7f842a0d0 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -444,7 +444,7 @@ NOTE: Defined in +active_support/core_ext/kernel/requires.rb+. h4. +in?+ -The predicate +in?+ tests if an object is included in another object. +The predicate +in?+ tests if an object is included in another object. An +ArgumentError+ exception will be raised if the argument passed does not respond to +include?+. Examples of +in?+: -- cgit v1.2.3 From 53d3bafc8bf3f0cb90a02751aa93b0c30b26ab78 Mon Sep 17 00:00:00 2001 From: Evan Petrie Date: Thu, 14 Apr 2011 12:08:18 -0700 Subject: ruby 1.9.2 and other ruby implementations may not return the same hash value for the same string each time. This can result in your static assets being served from different asset hosts, which makes browser caching less effective. Use md5 or some other digest method instead. --- actionpack/lib/action_view/helpers/asset_tag_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index f6b2d4f3f4..c383e4990c 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -57,7 +57,7 @@ module ActionView # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{source.hash % 2 + 1}.example.com" + # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # } # image_tag("rails.png") # # => Rails -- cgit v1.2.3 From bbfc6cda82b9550bfc955ecf66c0b803e3002e0a Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 20:37:12 -0300 Subject: Remove extra whitespaces from guides --- .../source/action_controller_overview.textile | 2 +- .../guides/source/action_view_overview.textile | 6 ++-- .../guides/source/active_record_querying.textile | 4 +-- railties/guides/source/ajax_on_rails.textile | 8 +++--- railties/guides/source/caching_with_rails.textile | 10 +++---- railties/guides/source/form_helpers.textile | 6 ++-- railties/guides/source/generators.textile | 2 +- railties/guides/source/getting_started.textile | 32 +++++++++++----------- railties/guides/source/i18n.textile | 4 +-- railties/guides/source/initialization.textile | 2 +- .../guides/source/layouts_and_rendering.textile | 8 +++--- railties/guides/source/migrations.textile | 4 +-- railties/guides/source/plugins.textile | 28 +++++++++---------- railties/guides/source/routing.textile | 4 +-- railties/guides/source/security.textile | 10 +++---- 15 files changed, 65 insertions(+), 65 deletions(-) diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 496dc7224b..f8b586c151 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -368,7 +368,7 @@ class UsersController < ApplicationController respond_to do |format| format.html # index.html.erb format.xml { render :xml => @users} - format.json { render :json => @users} + format.json { render :json => @users} end end end diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index d0b3ee6bfc..1a81920249 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -262,9 +262,9 @@ Register one or more stylesheet files to be included when symbol is passed to +s ActionView::Helpers::AssetTagHelper.register_stylesheet_expansion :monkey => ["head", "body", "tail"] stylesheet_link_tag :monkey # => - - - + + + h5. auto_discovery_link_tag diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index b970c55327..7cdffe4c2e 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -546,7 +546,7 @@ Active Record provides two locking mechanisms: h4. Optimistic Locking -Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened. An +ActiveRecord::StaleObjectError+ exception is thrown if that has occurred and the update is ignored. +Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of conflicts with the data. It does this by checking whether another process has made changes to a record since it was opened. An +ActiveRecord::StaleObjectError+ exception is thrown if that has occurred and the update is ignored. Optimistic locking column @@ -1060,7 +1060,7 @@ If you want to find the sum of a field for all records in your table you can cal Client.sum("orders_count") -For options, please see the parent section, "Calculations":#calculations. +For options, please see the parent section, "Calculations":#calculations. h3. Changelog diff --git a/railties/guides/source/ajax_on_rails.textile b/railties/guides/source/ajax_on_rails.textile index b80df4aa58..25c1e01b55 100644 --- a/railties/guides/source/ajax_on_rails.textile +++ b/railties/guides/source/ajax_on_rails.textile @@ -42,7 +42,7 @@ You are ready to add some AJAX love to your Rails app! h4. The Quintessential AJAX Rails Helper: link_to_remote -Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. +Let's start with what is probably the most often used helper: +link_to_remote+. It has an interesting feature from the documentation point of view: the options supplied to +link_to_remote+ are shared by all other AJAX helpers, so learning the mechanics and options of +link_to_remote+ is a great help when using other helpers. The signature of +link_to_remote+ function is the same as that of the standard +link_to+ helper: @@ -72,7 +72,7 @@ link_to_remote "Add to cart", If the server returns 200, the output of the above example is equivalent to our first, simple one. However, in case of error, the element with the DOM id +error+ is updated rather than the +cart+ element. -** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: +** *position* By default (i.e. when not specifying this option, like in the examples before) the repsonse is injected into the element with the specified DOM id, replacing the original content of the element (if there was any). You might want to alter this behavior by keeping the original content - the only question is where to place the new content? This can specified by the +position+ parameter, with four possibilities: *** +:before+ Inserts the response text just before the target element. More precisely, it creates a text node from the response and inserts it as the left sibling of the target element. *** +:after+ Similar behavior to +:before+, but in this case the response is inserted after the target element. *** +:top+ Inserts the text into the target element, before it's original content. If the target element was empty, this is equivalent with not specifying +:position+ at all. @@ -210,7 +210,7 @@ def javascript_test end -The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript". +The above code snippet does exactly the same as the one in the previous section - going about it much more elegantly though. You don't need to worry about headers,write ugly JavaScript code into a string etc. When the first parameter to +render+ is +:update+, Rails expects a block with a single parameter (+page+ in our case, which is the traditional naming convention) which is an instance of the JavaScriptGenerator:"http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper/JavaScriptGenerator/GeneratorMethods.html" object. As it's name suggests, JavaScriptGenerator is responsible for generating JavaScript from your Ruby code. You can execute multiple method calls on the +page+ instance - it's all turned into JavaScript code and sent to the server with the appropriate Content Type, "text/javascript". h4. RJS Templates @@ -307,7 +307,7 @@ So +<<+ is used to execute an arbitrary JavaScript statement, passed as string t page.call :alert, '1+1 equals ' + result -+assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function. ++assign+ simply assigns a value to a variable. +call+ is similar to +<<+ with a slightly different syntax: the first parameter is the name of the function to call, followed by the list of parameters passed to the function. h6. Class Proxies diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 995a835ce8..799339e674 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -25,7 +25,7 @@ h4. Page Caching Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -So, how do you enable this super-fast cache behavior? Simple, let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products +So, how do you enable this super-fast cache behavior? Simple, let's say you have a controller called +ProductsController+ and an +index+ action that lists all the products class ProductsController < ActionController @@ -152,7 +152,7 @@ expire_fragment('all_available_products') h4. Sweepers -Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. +Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment}+ calls in your code. It does this by moving all the work required to expire cached content into an +ActionController::Caching::Sweeper+ subclass. This class is an observer and looks for changes to an object via callbacks, and when a change occurs it expires the caches associated with that object in an around or after filter. Continuing with our Product controller example, we could rewrite it with a sweeper like this: @@ -230,9 +230,9 @@ class ProductsController < ActionController end -The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. +The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory. -However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching. +However, it's important to note that query caches are created at the start of an action and destroyed at the end of that action and thus persist only for the duration of the action. If you'd like to store query results in a more persistent fashion, you can in Rails by using low level caching. h3. Cache Stores @@ -352,7 +352,7 @@ class ProductsController < ApplicationController # If the request is fresh (i.e. it's not modified) then you don't need to do # anything. The default render checks for this using the parameters # used in the previous call to stale? and will automatically send a - # :not_modified. So that's it, you're done. + # :not_modified. So that's it, you're done. end diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 1f21c27ae6..a63245acec 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -258,7 +258,7 @@ The name passed to +form_for+ controls the key used in +params+ to access the fo The helper methods called on the form builder are identical to the model object helpers except that it is not necessary to specify which object is being edited since this is already managed by the form builder. -You can create a similar binding without actually creating +<form>+ tags with the +fields_for+ helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: +You can create a similar binding without actually creating +<form>+ tags with the +fields_for+ helper. This is useful for editing additional model objects with the same form. For example if you had a Person model with an associated ContactDetail model you could create a form for creating both like so: <%= form_for @person, :url => { :action => "create" } do |person_form| %> @@ -438,7 +438,7 @@ As with other helpers, if you were to use the +select+ helper on a form builder <%= f.select(:city_id, ...) %> -WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment. +WARNING: If you are using +select+ (or similar helpers such as +collection_select+, +select_tag+) to set a +belongs_to+ association you must pass the name of the foreign key (in the example above +city_id+), not the name of association itself. If you specify +city+ instead of +city_id+ Active Record will raise an error along the lines of ActiveRecord::AssociationTypeMismatch: City(#17815740) expected, got String(#1138750) when you pass the +params+ hash to +Person.new+ or +update_attributes+. Another way of looking at this is that form helpers only edit attributes. You should also be aware of the potential security ramifications of allowing users to edit foreign keys directly. You may wish to consider the use of +attr_protected+ and +attr_accessible+. For further details on this, see the "Ruby On Rails Security Guide":security.html#_mass_assignment. h4. Option Tags from a Collection of Arbitrary Objects @@ -513,7 +513,7 @@ The +:prefix+ option is the key used to retrieve the hash of date components fro h4(#select-model-object-helpers). Model Object Helpers +select_date+ does not work well with forms that update or create Active Record objects as Active Record expects each element of the +params+ hash to correspond to one attribute. -The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example: +The model object helpers for dates and times submit parameters with special names, when Active Record sees parameters with such names it knows they must be combined with the other parameters and given to a constructor appropriate to the column type. For example: <%= date_select :person, :birth_date %> diff --git a/railties/guides/source/generators.textile b/railties/guides/source/generators.textile index 44c15be7bf..ac709968d9 100644 --- a/railties/guides/source/generators.textile +++ b/railties/guides/source/generators.textile @@ -383,7 +383,7 @@ if yes?("Would you like to install Devise?") end -In the above template we specify that the application relies on the +rspec-rails+ and +cucumber-rails+ gem so these two will be added to the +test+ group in the +Gemfile+. Then we pose a question to the user about whether or not they would like to install Devise. If the user replies "y" or "yes" to this question, then the template will add Devise to the +Gemfile+ outside of any group and then runs the +devise:install+ generator. This template then takes the users input and runs the +devise+ generator, with the user's answer from the last question being passed to this generator. +In the above template we specify that the application relies on the +rspec-rails+ and +cucumber-rails+ gem so these two will be added to the +test+ group in the +Gemfile+. Then we pose a question to the user about whether or not they would like to install Devise. If the user replies "y" or "yes" to this question, then the template will add Devise to the +Gemfile+ outside of any group and then runs the +devise:install+ generator. This template then takes the users input and runs the +devise+ generator, with the user's answer from the last question being passed to this generator. Imagine that this template was in a file called +template.rb+. We can use it to modify the outcome of the +rails new+ command by using the +-m+ option and passing in the filename: diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 5906f953bf..af3525636c 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -22,7 +22,7 @@ TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails 3. * The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system * A working installation of the "SQLite3 Database":http://www.sqlite.org -Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including: +Rails is a web application framework running on the Ruby programming language. If you have no prior experience with Ruby, you will find a very steep learning curve diving straight into Rails. There are some good free resources on the internet for learning Ruby, including: * "Mr. Neighborly's Humble Little Ruby Book":http://www.humblelittlerubybook.com * "Programming Ruby":http://www.ruby-doc.org/docs/ProgrammingRuby/ @@ -78,7 +78,7 @@ Rails ships as many individual components. h5. Action Pack -Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC". +Action Pack is a single gem that contains Action Controller, Action View and Action Dispatch. The "VC" part of "MVC". h5. Action Controller @@ -98,7 +98,7 @@ Action Mailer is a framework for building e-mail services. You can use Action Ma h5. Active Model -Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this. +Active Model provides a defined interface between the Action Pack gem services and Object Relationship Mapping gems such as Active Record. Active Model allows Rails to utilize other ORM frameworks in place of Active Record if your application needs this. h5. Active Record @@ -153,7 +153,7 @@ TIP. If you're working on Windows, you can quickly install Ruby and Rails with " h4. Creating the Blog Application -The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":https://github.com/mikel/getting-started-code. +The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. If you need to see the completed code, you can download it from "Getting Started Code":https://github.com/mikel/getting-started-code. To begin, open a terminal, navigate to a folder where you have rights to create files, and type: @@ -184,7 +184,7 @@ In any case, Rails will create a folder in your working directory called blo |doc/|In-depth documentation for your application.| |lib/|Extended modules for your application (not covered in this guide).| |log/|Application log files.| -|public/|The only folder seen to the world as-is. This is where your images, JavaScript files, stylesheets (CSS), and other static files go.| +|public/|The only folder seen to the world as-is. This is where your images, JavaScript files, stylesheets (CSS), and other static files go.| |script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| @@ -290,7 +290,7 @@ This will fire up an instance of the WEBrick web server by default (Rails can al TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment. h4. Say "Hello", Rails @@ -310,7 +310,7 @@ Rails will create several files for you, including +app/views/home/index.html.er h4. Setting the Application Home Page -Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test. +Now that we have made the controller and view, we need to tell Rails when we want "Hello Rails" to show up. In our case, we want it to show up when we navigate to the root URL of our site, "http://localhost:3000":http://localhost:3000, instead of the "Welcome Aboard" smoke test. The first step to doing this is to delete the default page from your application: @@ -394,7 +394,7 @@ class CreatePosts < ActiveRecord::Migration end -The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide. +The above migration creates two methods, +up+, called when you run this migration into the database, and +down+ in case you need to reverse the changes made by this migration at a later date. The +up+ command in this case creates a +posts+ table with two string columns and a text column. It also creates two timestamp fields to track record creation and updating. More information about Rails migrations can be found in the "Rails Database Migrations":migrations.html guide. At this point, you can use a rake command to run the migration: @@ -548,7 +548,7 @@ This view iterates over the contents of the +@posts+ array to display content an * +link_to+ builds a hyperlink to a particular destination * +edit_post_path+ and +new_post_path+ are helpers that Rails provides as part of RESTful routing. You'll see a variety of these helpers for the different actions that the controller includes. -NOTE. In previous versions of Rails, you had to use +<%=h post.name %>+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +<%= raw post.name %>+. +NOTE. In previous versions of Rails, you had to use +<%=h post.name %>+ so that any HTML would be escaped before being inserted into the page. In Rails 3.0, this is now the default. To get unescaped HTML, you now use +<%= raw post.name %>+. TIP: For more details on the rendering process, see "Layouts and Rendering in Rails":layouts_and_rendering.html. @@ -600,7 +600,7 @@ The +new.html.erb+ view displays this empty Post to the user: <%= link_to 'Back', posts_path %> -The +<%= render 'form' %>+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post. +The +<%= render 'form' %>+ line is our first introduction to _partials_ in Rails. A partial is a snippet of HTML and Ruby code that can be reused in multiple locations. In this case, the form used to make a new post, is basically identical to a form used to edit a post, both have text fields for the name and title and a text area for the content with a button to make a new post or update the existing post. If you take a look at +views/posts/_form.html.erb+ file, you will see the following: @@ -873,7 +873,7 @@ TIP: For more information on Active Record associations, see the "Active Record h4. Adding a Route for Comments -As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows: +As with the +home+ controller, we will need to add a route so that Rails knows where we would like to navigate to see +comments+. Open up the +config/routes.rb+ file again, you will see an entry that was added automatically for +posts+ near the top by the scaffold generator, +resources :posts+, edit it as follows: resources :posts do @@ -901,7 +901,7 @@ This creates four files and one empty directory: * +test/unit/helpers/comments_helper_test.rb+ - The unit tests for the helper * +app/views/comments/+ - Views of the controller are stored here -Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive. +Like with any blog, our readers will create their comments directly after reading the post, and once they have added their comment, will be sent back to the post show page to see their comment now listed. Due to this, our +CommentsController+ is there to provide a method to create comments and delete SPAM comments when they arrive. So first, we'll wire up the Post show template (+/app/views/posts/show.html.erb+) to let us make a new comment: @@ -1078,7 +1078,7 @@ Then in the +app/views/posts/show.html.erb+ you can change it to look like the f <%= link_to 'Back to Posts', posts_path %> | -This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the @post.comments collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show. +This will now render the partial in +app/views/comments/_comment.html.erb+ once for each comment that is in the +@post.comments+ collection. As the +render+ method iterates over the @post.comments collection, it assigns each comment to a local variable named the same as the partial, in this case +comment+ which is then available in the partial for us to show. h4. Rendering a Partial Form @@ -1138,7 +1138,7 @@ The +@post+ object is available to any partials rendered in the view because we h3. Deleting Comments -Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+. +Another important feature on a blog is being able to delete SPAM comments. To do this, we need to implement a link of some sort in the view and a +DELETE+ action in the +CommentsController+. So first, let's add the delete link in the +app/views/comments/_comment.html.erb+ partial: @@ -1312,7 +1312,7 @@ Note that we have changed the +f+ in +form_for(@post) do |f|+ to +post_form+ to This example shows another option of the render helper, being able to pass in local variables, in this case, we want the local variable +form+ in the partial to refer to the +post_form+ object. -We also add a @post.tags.build at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create. +We also add a @post.tags.build at the top of this form, this is to make sure there is a new tag ready to have it's name filled in by the user. If you do not build the new tag, then the form will not appear as there is no new Tag object ready to create. Now create the folder app/views/tags and make a file in there called _form.html.erb which contains the form for the tag: @@ -1373,7 +1373,7 @@ However, that method call @post.tags.map { |t| t.name }.join(", ") is a h3. View Helpers -View Helpers live in app/helpers and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper. +View Helpers live in app/helpers and provide small snippets of reusable code for views. In our case, we want a method that strings a bunch of objects together using their name attribute and joining them with a comma. As this is for the Post show template, we put it in the PostsHelper. Open up app/helpers/posts_helper.rb and add the following: diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 3e7e396e8d..543fe85f70 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -91,7 +91,7 @@ This means, that in the +:en+ locale, the key _hello_ will map to the _Hello wor The I18n library will use *English* as a *default locale*, i.e. if you don't set a different locale, +:en+ will be used for looking up translations. -NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":https://github.com/joshmh/globalize2/tree/master may help you implement it. +NOTE: The i18n library takes a *pragmatic approach* to locale keys (after "some discussion":http://groups.google.com/group/rails-i18n/browse_thread/thread/14dede2c7dbe9470/80eec34395f64f3c?hl=en), including only the _locale_ ("language") part, like +:en+, +:pl+, not the _region_ part, like +:en-US+ or +:en-UK+, which are traditionally used for separating "languages" and "regional setting" or "dialects". Many international applications use only the "language" element of a locale such as +:cz+, +:th+ or +:es+ (for Czech, Thai and Spanish). However, there are also regional differences within different language groups that may be important. For instance, in the +:en-US+ locale you would have $ as a currency symbol, while in +:en-UK+, you would have £. Nothing stops you from separating regional and other settings in this way: you just have to provide full "English - United Kingdom" locale in a +:en-UK+ dictionary. Various "Rails I18n plugins":http://rails-i18n.org/wiki such as "Globalize2":https://github.com/joshmh/globalize2/tree/master may help you implement it. The *translations load path* (+I18n.load_path+) is just a Ruby Array of paths to your translation files that will be loaded automatically and available in your application. You can pick whatever directory and translation file naming scheme makes sense for you. @@ -441,7 +441,7 @@ Do check the "Rails i18n Wiki":http://rails-i18n.org/wiki for list of tools avai h3. Overview of the I18n API Features -You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. +You should have good understanding of using the i18n library now, knowing all necessary aspects of internationalizing a basic Rails application. In the following chapters, we'll cover it's features in more depth. Covered are features like these: diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 013eca2a35..638830cd83 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -1009,7 +1009,7 @@ The loading of this file finishes the loading of +active_model+ and so we go bac h4. Back to +activesupport/lib/action_dispatch.rb+ -The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: +The remainder of this file requires the +rack+ file from the Rack gem which defines the +Rack+ module. After +rack+, there's autoloads defined for the +Rack+, +ActionDispatch+, +ActionDispatch::Http+, +ActionDispatch::Session+. A new method called +autoload_under+ is used here, and this simply prefixes the files where the modules are autoloaded from with the path specified. For example here: autoload_under 'testing' do diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 1548da0eb5..66e8aa67e8 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -406,7 +406,7 @@ end Now, if the current user is a special user, they'll get a special layout when viewing a product. You can even use an inline method to determine the layout: -You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example: +You can also decide the layout by passing a Proc object, the block you give the Proc will be given the +controller+ instance, so you can make decisions based on the current request. For example: class ProductsController < ApplicationController @@ -584,7 +584,7 @@ With this code, the browser will make a fresh request for the index page, the co The only downside to this code, is that it requires a round trip to the browser, the browser requested the show action with +/books/1+ and the controller finds that there are no books, so the controller sends out a 302 redirect response to the browser telling it to go to +/books/+, the browser complies and sends a new request back to the controller asking now for the +index+ action, the controller then gets all the books in the database and renders the index template, sending it back down to the browser which then shows it on your screen. -While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be: +While in a small app, this added latency might not be a problem, it is something to think about when speed of response is of the essence. One way to handle this double request (though a contrived example) could be: def index @@ -862,7 +862,7 @@ Produces -removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing +removes the +description+ and +name+ columns, creates a +part_number+ column and adds an index on it. Finally it renames the +upccode+ column. This is the same as doing remove_column :products, :description @@ -335,7 +335,7 @@ NOTE: The +references+ helper does not actually create foreign key constraints f If the helpers provided by Active Record aren't enough you can use the +execute+ function to execute arbitrary SQL. -For more details and examples of individual methods check the API documentation, in particular the documentation for "ActiveRecord::ConnectionAdapters::SchemaStatements":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "ActiveRecord::ConnectionAdapters::TableDefinition":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "ActiveRecord::ConnectionAdapters::Table":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). +For more details and examples of individual methods check the API documentation, in particular the documentation for "ActiveRecord::ConnectionAdapters::SchemaStatements":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html (which provides the methods available in the +up+ and +down+ methods), "ActiveRecord::ConnectionAdapters::TableDefinition":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html (which provides the methods available on the object yielded by +create_table+) and "ActiveRecord::ConnectionAdapters::Table":http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html (which provides the methods available on the object yielded by +change_table+). h4. Writing Your +down+ Method diff --git a/railties/guides/source/plugins.textile b/railties/guides/source/plugins.textile index 31158d8742..d486e8ade3 100644 --- a/railties/guides/source/plugins.textile +++ b/railties/guides/source/plugins.textile @@ -27,7 +27,7 @@ h3. Setup h4. Generating the Plugin Skeleton -Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain +Rails currently ships with a generator to generate a plugin within a Rails application. Help text is available that will explain how this generator works. @@ -41,7 +41,7 @@ packaging plugins as gems, especially with the inclusion of Bundler as the Rails Packaging a plugin as a gem may be overkill for any plugins that will not be shared across projects but doing so from the start makes it easier to share the plugin going forward without adding too much additional overhead during development. Rails 3.1 will ship with a plugin generator that will default to setting up a plugin -as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the +as a gem. This tutorial will begin to bridge that gap by demonstrating how to create a gem based plugin using the "Enginex gem":http://www.github.com/josevalim/enginex. @@ -69,7 +69,7 @@ h3. Extending Core Classes This section will explain how to add a method to String that will be available anywhere in your rails application. -In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions: +In this example you will add a method to String named +to_squawk+. To begin, create a new test file with a few assertions: # yaffle/test/core_ext_test.rb @@ -83,7 +83,7 @@ class CoreExtTest < Test::Unit::TestCase end -Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method: +Run +rake+ to run the test. This test should fail because we haven't implemented the +to_squak+ method: 1) Error: @@ -133,7 +133,7 @@ $ rails console h3. Add an "acts_as" Method to Active Record -A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you +A common pattern in plugins is to add a method called 'acts_as_something' to models. In this case, you want to write a method called 'acts_as_yaffle' that adds a 'squawk' method to your Active Record models. To begin, set up your files so that you have: @@ -169,9 +169,9 @@ end h4. Add a Class Method -This plugin will expect that you've added a method to your model named 'last_squawk'. However, the +This plugin will expect that you've added a method to your model named 'last_squawk'. However, the plugin users might have already defined a method on their model named 'last_squawk' that they use -for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. +for something else. This plugin will allow the name to be changed by adding a class method called 'yaffle_text_field'. To start out, write a failing test that shows the behavior you'd like: @@ -220,7 +220,7 @@ $ rails generate model Wickwall last_squak:string last_tweet:string Now you can create the necessary database tables in your testing database by navigating to your dummy app -and migrating the database. First +and migrating the database. First $ cd test/dummy @@ -388,17 +388,17 @@ Run +rake+ one final time and you should see: NOTE: The use of +write_attribute+ to write to the field in model is just one example of how a plugin can -interact with the model, and will not always be the right method to use. For example, you could also +interact with the model, and will not always be the right method to use. For example, you could also use +send("#{self.class.yaffle_text_field}=", string.to_squawk)+. h3. Generators -Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about +Generators can be included in your gem simply by creating them in a lib/generators directory of your plugin. More information about the creation of generators can be found in the "Generators Guide":generators.html h3. Publishing your Gem -Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply +Gem plugins in progress can be easily be shared from any Git repository. To share the Yaffle gem with others, simply commit the code to a Git repository (like Github) and add a line to the Gemfile of the any application: @@ -412,7 +412,7 @@ For more information about publishing gems to RubyGems, see: "http://blog.thepet h3. Non-Gem Plugins -Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the +Non-gem plugins are useful for functionality that won't be shared with another project. Keeping your custom functionality in the vendor/plugins directory un-clutters the rest of the application. Move the directory that you created for the gem based plugin into the vendor/plugins directory of a generated Rails application, create a vendor/plugins/yaffle/init.rb file that contains "require 'yaffle'" and everything will still work. @@ -423,7 +423,7 @@ Move the directory that you created for the gem based plugin into the vendor/plu require 'yaffle' -You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the +You can test this by changing to the Rails application that you added the plugin to and starting a rails console. Once in the console we can check to see if the String has an instance method of to_squawk. $ cd my_app @@ -435,7 +435,7 @@ You can also remove the .gemspec, Gemfile and Gemfile.lock files as they will no h3. RDoc Documentation -Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy. +Once your plugin is stable and you are ready to deploy do everyone else a favor and document it! Luckily, writing documentation for your plugin is easy. The first step is to update the README file with detailed information about how to use your plugin. A few key things to include are: diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 58b75b9a1d..95b877aecf 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -321,7 +321,7 @@ resources :photos do end -This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers. +This will recognize +/photos/1/preview+ with GET, and route to the +preview+ action of +PhotosController+. It will also create the +preview_photo_url+ and +preview_photo_path+ helpers. Within the block of member routes, each route name specifies the HTTP verb that it will recognize. You can use +get+, +put+, +post+, or +delete+ here. If you don't have multiple +member+ routes, you can also pass +:on+ to a route, eliminating the block: @@ -421,7 +421,7 @@ You do not need to explicitly use the +:controller+ and +:action+ symbols within match 'photos/:id' => 'photos#show' -With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+. +With this route, Rails will match an incoming path of +/photos/12+ to the +show+ action of +PhotosController+. You can also define other defaults in a route by supplying a hash for the +:defaults+ option. This even applies to parameters that you do not specify as dynamic segments. For example: diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 893f65856c..2bd7ddf428 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -147,7 +147,7 @@ reset_session If you use the popular RestfulAuthentication plugin for user management, add reset_session to the SessionsController#create action. Note that this removes any value from the session, _(highlight)you have to transfer them to the new session_. -Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. +Another countermeasure is to _(highlight)save user-specific properties in the session_, verify them every time a request comes in, and deny access, if the information does not match. Such properties could be the remote IP address or the user agent (the web browser name), though the latter is less user-specific. When saving the IP address, you have to bear in mind that there are Internet service providers or large organizations that put their users behind proxies. _(highlight)These might change over the course of a session_, so these users will not be able to use your application, or only in a limited way. h4. Session Expiry @@ -238,7 +238,7 @@ Or the attacker places the code into the onmouseover event handler of an image: -There are many other possibilities, including Ajax to attack the victim in the background.
The _(highlight)solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller: +There are many other possibilities, including Ajax to attack the victim in the background.
The _(highlight)solution to this is including a security token in non-GET requests_ which check on the server-side. In Rails 2 or higher, this is a one-liner in the application controller: protect_from_forgery :secret => "123456789012345678901234567890..." @@ -394,7 +394,7 @@ params[:user] # => {:name => “ow3ned”, :admin => true} So if you create a new user using mass-assignment, it may be too easy to become an administrator. -Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the attributes= method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: +Note that this vulnerability is not restricted to database columns. Any setter method, unless explicitly protected, is accessible via the attributes= method. In fact, this vulnerability is extended even further with the introduction of nested mass assignment (and nested object forms) in Rails 2.3. The +accepts_nested_attributes_for+ declaration provides us the ability to extend mass assignment to model associations (+has_many+, +has_one+, +has_and_belongs_to_many+). For example: class Person < ActiveRecord::Base @@ -434,13 +434,13 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true -A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: +A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: ActiveRecord::Base.send(:attr_accessible, nil) -This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. +This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. h3. User Management -- cgit v1.2.3 From ba8a71588d8f3206b92c3e176c0b55529904a488 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 14 Apr 2011 21:26:20 -0300 Subject: Added more detailed use of the +list+ command on debugging guide --- .../source/debugging_rails_applications.textile | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/railties/guides/source/debugging_rails_applications.textile b/railties/guides/source/debugging_rails_applications.textile index 045b8823ca..507b0d7ec9 100644 --- a/railties/guides/source/debugging_rails_applications.textile +++ b/railties/guides/source/debugging_rails_applications.textile @@ -328,6 +328,41 @@ If you repeat the +list+ command, this time using just +l+, the next ten lines o And so on until the end of the current file. When the end of file is reached, the +list+ command will start again from the beginning of the file and continue again up to the end, treating the file as a circular buffer. +On the other hand, to see the previous ten lines you should type +list-+ (or +l-+) + + +(rdb:7) l- +[1, 10] in /PathToProject/posts_controller.rb + 1 class PostsController < ApplicationController + 2 # GET /posts + 3 # GET /posts.xml + 4 def index + 5 debugger + 6 @posts = Post.all + 7 + 8 respond_to do |format| + 9 format.html # index.html.erb + 10 format.xml { render :xml => @posts } + + +This way you can move inside the file, being able to see the code above and over the line you added the +debugger+. +Finally, to see where you are in the code again you can type +list=+ + + +(rdb:7) list= +[1, 10] in /PathToProject/posts_controller.rb + 1 class PostsController < ApplicationController + 2 # GET /posts + 3 # GET /posts.xml + 4 def index + 5 debugger +=> 6 @posts = Post.all + 7 + 8 respond_to do |format| + 9 format.html # index.html.erb + 10 format.xml { render :xml => @posts } + + h4. The Context When you start debugging your application, you will be placed in different contexts as you go through the different parts of the stack. -- cgit v1.2.3 From 434d75705f84337c97c2e5360a7586c7373c923b Mon Sep 17 00:00:00 2001 From: Emilio Tagua Date: Thu, 14 Apr 2011 23:00:28 -0300 Subject: Fix console to accept [environment] [options] as specified in docs. --- railties/lib/rails/commands/console.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 2b7faf9715..dfd3c654ff 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -51,6 +51,6 @@ module Rails end # Has to set the RAILS_ENV before config/application is required -if ARGV.first && !ARGV.first.index("-") && env = ARGV.pop # has to pop the env ARGV so IRB doesn't freak +if ARGV.first && !ARGV.first.index("-") && env = ARGV.shift # has to shift the env ARGV so IRB doesn't freak ENV['RAILS_ENV'] = %w(production development test).detect {|e| e =~ /^#{env}/} || env end -- cgit v1.2.3 From c09538941fad7929955ada73cc796e918af415ca Mon Sep 17 00:00:00 2001 From: Joshua Ballanco Date: Thu, 14 Apr 2011 23:18:12 -0400 Subject: Test for stripping tags from a frozen string. This test will pass under Ruby 1.8 but fail under Ruby 1.9 because of the change in behavior of gsub! w.r.t. frozen strings that do not match the pattern used [ruby-core:23664]. --- actionpack/test/template/html-scanner/sanitizer_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index fcc3782f04..678cb9eeeb 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -20,6 +20,7 @@ class SanitizerTest < ActionController::TestCase assert_equal "This has a here.", sanitizer.sanitize("This has a ]]> here.") assert_equal "This has an unclosed ", sanitizer.sanitize("This has an unclosed ]] here...") [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) } + assert_nothing_raised { sanitizer.sanitize("This is a frozen string with no tags".freeze) } end def test_strip_links -- cgit v1.2.3 From 2adeaa9c90b7559387b55e7a24a7eb82671c88cc Mon Sep 17 00:00:00 2001 From: Joshua Ballanco Date: Thu, 14 Apr 2011 23:25:18 -0400 Subject: Fix for stripping tags from frozen strings. This returns behavior under Ruby 1.9 to match Ruby 1.8. --- actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 09dd08898c..91a97c02ff 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -33,7 +33,7 @@ module HTML result = super # strip any comments, and if they have a newline at the end (ie. line with # only a comment) strip that too - result.gsub!(/[\n]?/m, "") if result + result = result.gsub(/[\n]?/m, "") if (result && result =~ /[\n]?/m) # Recurse - handle all dirty nested tags result == text ? result : sanitize(result, options) end -- cgit v1.2.3 From 4dd84c8db06ddb56468401a478399c995c9604c1 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 15 Apr 2011 14:08:37 -0300 Subject: Improved Array#sample documentation --- activesupport/lib/active_support/core_ext/array/random_access.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb index ab1fa7cd5b..0bf510e39a 100644 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ b/activesupport/lib/active_support/core_ext/array/random_access.rb @@ -1,7 +1,9 @@ class Array # Backport of Array#sample based on Marc-Andre Lafortune's https://github.com/marcandre/backports/ # Returns a random element or +n+ random elements from the array. - # If the array is empty and +n+ is nil, returns nil. if +n+ is passed, returns []. + # If the array is empty and +n+ is nil, returns nil. + # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception. + # If the value of +n+ is equal or greater than 0 it returns []. # # [1,2,3,4,5,6].sample # => 4 # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] -- cgit v1.2.3 From 91761b775ce1f028486dc3483904795e9c028ed6 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 15 Apr 2011 14:13:10 -0300 Subject: Added an example of exception situation on Array#sample docs --- activesupport/lib/active_support/core_ext/array/random_access.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb index 0bf510e39a..9eba4642b8 100644 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ b/activesupport/lib/active_support/core_ext/array/random_access.rb @@ -5,10 +5,11 @@ class Array # If +n+ is passed and its value is less than 0, it raises an +ArgumentError+ exception. # If the value of +n+ is equal or greater than 0 it returns []. # - # [1,2,3,4,5,6].sample # => 4 - # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] - # [].sample # => nil - # [].sample(3) # => [] + # [1,2,3,4,5,6].sample # => 4 + # [1,2,3,4,5,6].sample(3) # => [2, 4, 5] + # [1,2,3,4,5,6].sample(-3) # => ArgumentError: negative sample number + # [].sample # => nil + # [].sample(3) # => [] def sample(n=nil) return self[Kernel.rand(size)] if n.nil? n = n.to_int -- cgit v1.2.3 From 3ca6d0e8fe72f6840049f7c2461243cf362b9896 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 19:56:48 -0400 Subject: Including actual usage in example --- actionpack/lib/action_view/helpers/date_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 6cd1565031..72ee31a246 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -115,7 +115,9 @@ module ActionView # time_ago_in_words(Time.now - 15.hours) # => 15 hours # time_ago_in_words(Time.now) # => less than a minute # - # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days + # from_time = Time.now - 3.days - 14.minutes - 25.seconds + # time_ago_in_words(from_time) # => 3 days + # def time_ago_in_words(from_time, include_seconds = false) distance_of_time_in_words(from_time, Time.now, include_seconds) end -- cgit v1.2.3 From 8ac365f47626fd4d9569cf9c378c3a8f4f60eb58 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 20:03:52 -0400 Subject: Making example result match actual result --- actionpack/lib/action_view/helpers/date_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 72ee31a246..4c65ebc1f3 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -112,7 +112,7 @@ module ActionView # # ==== Examples # time_ago_in_words(3.minutes.from_now) # => 3 minutes - # time_ago_in_words(Time.now - 15.hours) # => 15 hours + # time_ago_in_words(Time.now - 15.hours) # => about 15 hours # time_ago_in_words(Time.now) # => less than a minute # # from_time = Time.now - 3.days - 14.minutes - 25.seconds -- cgit v1.2.3 From 6ddd4a3d954cd81f96c747f515406be50041431a Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 20:08:11 -0400 Subject: Days are never approximated using 'about' --- actionpack/lib/action_view/helpers/date_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 4c65ebc1f3..3c0c7c319c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -51,7 +51,7 @@ module ActionView # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years - # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days + # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute -- cgit v1.2.3 From 7f98b544e3b29c7ffdb8cb4a2ad1da5d1a8c611c Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 20:18:30 -0400 Subject: Negative format example should use a negative number --- actionpack/lib/action_view/helpers/number_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 05a9c5b4f1..68ebedd328 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -100,7 +100,7 @@ module ActionView # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506 # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 € # - # number_to_currency(1234567890.50, :negative_format => "(%u%n)") + # number_to_currency(-1234567890.50, :negative_format => "(%u%n)") # # => ($1,234,567,890.51) # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "") # # => £1234567890,50 -- cgit v1.2.3 From 004042c0d94f0294e691de46094a665acd852653 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 20:27:05 -0400 Subject: Fixing missing colon on symbol in example --- actionpack/lib/action_view/helpers/number_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 68ebedd328..a150019e5c 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -238,7 +238,7 @@ module ActionView # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100 # number_with_precision(13, :precision => 5, :significant => true) # => 13.000 # number_with_precision(111.234, :locale => :fr) # => 111,234 - # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true) + # number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true) # # => 13 # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3 # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') -- cgit v1.2.3 From e8afe4e1ea9ea4c59368bb84efde785876f25061 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 20:30:51 -0400 Subject: Making spacing consistent --- actionpack/lib/action_view/helpers/number_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index a150019e5c..b545031fcc 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -318,7 +318,7 @@ module ActionView # Non-significant zeros after the fractional separator are stripped out by default (set # :strip_insignificant_zeros to +false+ to change that): # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" - # number_to_human_size(524288000, :precision=>5) # => "500 MB" + # number_to_human_size(524288000, :precision => 5) # => "500 MB" def number_to_human_size(number, options = {}) options.symbolize_keys! @@ -407,7 +407,7 @@ module ActionView # Unsignificant zeros after the decimal separator are stripped out by default (set # :strip_insignificant_zeros to +false+ to change that): # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" - # number_to_human(500000000, :precision=>5) # => "500 Million" + # number_to_human(500000000, :precision => 5) # => "500 Million" # # ==== Custom Unit Quantifiers # -- cgit v1.2.3 From f274394afbedd2097d5855f8c2d7d384b46977c3 Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 21:34:49 -0400 Subject: Fixing more spacing inconsistencies --- actionpack/lib/action_view/helpers/date_helper.rb | 2 +- actionpack/lib/action_view/helpers/url_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 3c0c7c319c..6dbd2e3e43 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -466,7 +466,7 @@ module ActionView # # # Generates a select field for hours with a custom prompt. Use :prompt => true for a # # generic prompt. - # select_hour(13, :prompt =>'Choose hour') + # select_hour(13, :prompt => 'Choose hour') # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index de75488e72..051d3eb049 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -555,10 +555,10 @@ module ActionView # current_page?(:controller => 'shop', :action => 'checkout') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'1') + # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '1') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'2') + # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '2') # # => false # # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc') -- cgit v1.2.3 From b6bfcc9115df00620e155a84f80d46342c1fe7ee Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 21:50:29 -0400 Subject: Fix syntax error in example --- actionpack/lib/action_view/helpers/text_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index bdda1df437..d1c505d2b5 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -19,7 +19,7 @@ module ActionView # simple_format('Example') # # => "

Example

" # - # simple_format('Example') + # simple_format('Example') # # => "

Example

" # # If you want to escape all content, you should invoke the +h+ method before -- cgit v1.2.3 From 1229904602b8c3b354a144ce33468be408cf9cec Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 21:56:01 -0400 Subject: Removing incorrect example results --- actionpack/lib/action_view/helpers/text_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index d1c505d2b5..06e2b027da 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -295,11 +295,11 @@ module ActionView # +link+ as its optional second parameter and the +html_options+ hash # as its optional third parameter: # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :urls) # => Once upon\na time + # auto_link(post_body, :urls) # # => "Welcome to my new blog at http://www.myblog.com. # Please e-mail me at me@email.com." # - # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time + # auto_link(post_body, :all, :target => "_blank") # # => "Welcome to my new blog at http://www.myblog.com. # Please e-mail me at me@email.com." def auto_link(text, *args, &block)#link = :all, html = {}, &block) -- cgit v1.2.3 From 50b2eb8cbd6cb923026b8fbb98482e06cdbd21ce Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Fri, 15 Apr 2011 22:09:23 -0400 Subject: Fixing another example result --- actionpack/lib/action_view/helpers/sanitize_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 0fee34f8a4..841be0a567 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -94,7 +94,7 @@ module ActionView # # => Please e-mail me at me@email.com. # # strip_links('Blog: Visit.') - # # => Blog: Visit + # # => Blog: Visit. def strip_links(html) self.class.link_sanitizer.sanitize(html) end -- cgit v1.2.3 From 7c9927a4092f18c449538c6656f66ef6c47205f2 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 16:02:05 -0300 Subject: Added Using Action View with Rails guide section. --- .../guides/source/action_view_overview.textile | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 1a81920249..33735b7944 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -20,7 +20,28 @@ Note: Some features of Action View are tied to Active Record, but that doesn't m h3. Using Action View with Rails -TODO... +For each controller there is an associated directory in the app/views directory which holds the template files that make up the views associated with that controller. These files are used to display the view that results from each controller action. + +Let's take a look at what Rails does by default when creating a new resource using the scaffold generator: + + +$ rails generate scaffold post + [...] + invoke scaffold_controller + create app/controllers/posts_controller.rb + invoke erb + create app/views/posts + create app/views/posts/index.html.erb + create app/views/posts/edit.html.erb + create app/views/posts/show.html.erb + create app/views/posts/new.html.erb + create app/views/posts/_form.html.erb + [...] + + +There is a naming convention for views in Rails. Typically, the views share their name with the associated controller action, as you can see above. +For example, the index controller action of the posts_controller.rb will use the index.html.erb view file in the app/views/posts directory. +The complete HTML returned to the client is composed of a combination of this ERB file, a layout template that wraps it, and all the partials that the view may reference. Later on this guide you can find a more detailed documentation of each one of this three components. h3. Using Action View outside of Rails -- cgit v1.2.3 From eecb8385b4901f6c4e9679a5fc9f065a639f9ac7 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 17:14:46 -0300 Subject: Added Partials section to ActionView guide. --- .../guides/source/action_view_overview.textile | 97 +++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 33735b7944..72fcb8c0e6 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -115,9 +115,102 @@ TODO needs a screenshot? I have one - not sure where to put it. h3. Templates, Partials and Layouts -TODO... +As mentioned before, the final HTML output is a composition of three Rails elements: +Templates+, +Partials+ and +Layouts+. +Find below a brief overview of each one of them. + +h4. Partials + +Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With a partial, you can move the code for rendering a particular piece of a response to its own file. + +h5. Naming Partials + +To render a partial as part of a view, you use the +render+ method within the view: + + +<%= render "menu" %> + + +This will render a file named +_menu.html.erb+ at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: + + +<%= render "shared/menu" %> + + +That code will pull in the partial from +app/views/shared/_menu.html.erb+. + +h5. Using Partials to Simplify Views + +One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this: + + +<%= render "shared/ad_banner" %> + +

Products

+ +

Here are a few of our fine products:

+<% @products.each do |product| %> + <%= render :partial => "product", :locals => { :product => product } %> +<% end %> + +<%= render "shared/footer" %> +
+ +Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. + +h5. The :as and :object options + +By default ActionView::Partials::PartialRenderer has its object in a local variable with the same name as the template. So, given + + +<%= render :partial => "product" %> + + +within product we'll get @product in the local variable +product+, as if we had written: + + +<%= render :partial => "product", :locals => { :product => @product } %> + + +With the :as option we can specify a different name for said local variable. For example, if we wanted it to be +item+ instead of product+ we'd do: + + +<%= render :partial => "product", :as => 'item' %> + + +The :object option can be used to directly specify which object is rendered into the partial; useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. + +For example, instead of: + + +<%= render :partial => "product", :locals => { :product => @item } %> + + +you'd do: + + +<%= render :partial => "product", :object => @item %> + + +The :object and :as options can be used together. + +h5. Rendering Collections + +The example of partial use describes a familiar pattern where a template needs to iterate over an array and render a sub template for each of the elements. This pattern has been implemented as a single method that accepts an array and renders a partial by the same name as the elements contained within. +So the three-lined example for rendering all the products can be rewritten with a single line: + + +<%= render :partial => "product", :collection => @products %> + + +When a partial is called with a pluralized collection, then the individual instances of the partial have access to the member of the collection being rendered via a variable named after the partial. In this case, the partial is +_product+ , and within the +_product+ partial, you can refer to +product+ to get the instance that is being rendered. + +You can use a shorthand syntax for rendering collections. Assuming @products is a collection of +Product+ instances, you can simply write the following to produce the same result: + + +<%= render @products %> + -TODO see http://guides.rubyonrails.org/layouts_and_rendering.html +Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. h3. Using Templates, Partials and Layouts in "The Rails Way" -- cgit v1.2.3 From 143ed7edc32f875882f972ce444c9fa8ea028326 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 17:32:04 -0300 Subject: Added Spacer Templates on Partials section of ActionView guide. --- railties/guides/source/action_view_overview.textile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 72fcb8c0e6..ab9b910270 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -212,6 +212,16 @@ You can use a shorthand syntax for rendering collections. Assuming @products is Rails determines the name of the partial to use by looking at the model name in the collection. In fact, you can even create a heterogeneous collection and render it this way, and Rails will choose the proper partial for each member of the collection. +h5. Spacer Templates + +You can also specify a second partial to be rendered between instances of the main partial by using the +:spacer_template+ option: + + +<%= render @products, :spacer_template => "product_ruler" %> + + +Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. + h3. Using Templates, Partials and Layouts in "The Rails Way" TODO... -- cgit v1.2.3 From 8c68bcf829ead305e2459c2b90850b43464ec7d7 Mon Sep 17 00:00:00 2001 From: Michal Papis Date: Sat, 16 Apr 2011 22:56:58 +0200 Subject: Added information about grouping conditional validations --- .../source/active_record_validations_callbacks.textile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 514d0322b9..8882a5705b 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -552,6 +552,21 @@ class Account < ActiveRecord::Base end
+h4. Grouping conditional validations + +Sometimes it is useful to have multiple validations use one condition, it can be easily achieved using +with_options+. + + +class User < ActiveRecord::Base + with_options :if => :is_admin? do |admin| + admin.validates_length_of :password, :minimum => 10 + admin.validates_presence_of :email + end +end + + +All validations inside of +with_options+ block will have automatically passed the condition +:if => :is_admin?+ + h3. Creating Custom Validation Methods When the built-in validation helpers are not enough for your needs, you can write your own validation methods. -- cgit v1.2.3 From 558f096115eb123eafcea6df66762ba1c10150a6 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 18:26:44 -0300 Subject: Added Templates section on ActionView guide. --- .../guides/source/action_view_overview.textile | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index ab9b910270..2e36fd038f 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -118,6 +118,103 @@ h3. Templates, Partials and Layouts As mentioned before, the final HTML output is a composition of three Rails elements: +Templates+, +Partials+ and +Layouts+. Find below a brief overview of each one of them. +h4. Templates + +Action View templates can be written in several ways. If the template file has a .erb extension then it uses a mixture of ERB (included in Ruby) and HTML. If the template file has a .builder extension then a fresh instance of Builder::XmlMarkup library is used. + +Rails supports multiple template systems and uses a file extension to distinguish amongst them. For example, an HTML file using the ERB template system will have .html.erb as a file extension. + +h5. ERB + +Within an ERB template Ruby code can be included using both +<% %>+ and +<%= %>+ tags. The +<% %>+ are used to execute Ruby code that does not return anything, such as conditions, loops or blocks, and the +<%= %>+ tags are used when you want output. + +Consider the following loop for names: + + +Names of all the people +<% @people.each do |person| %> + Name: <%= person.name %>
+<% end %> +
+ +The loop is setup in regular embedding tags +<% %>+ and the name is written using the output embedding tag +<%= %>+. Note that this is not just a usage suggestion, for Regular output functions like print or puts won't work with ERB templates. So this would be wrong: + + +<%# WRONG %> +Hi, Mr. <% puts "Frodo" %> + + +To suppress leading and trailing whitespaces, you can use +<%-+ +-%>+ interchangeably with +<%+ and +%>+. + +h5. Builder + +Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object named +xml+ is automatically made available to templates with a .builder extension. + +Here are some basic examples: + + +xml.em("emphasized") +xml.em { xml.b("emph & bold") } +xml.a("A Link", "href"=>"http://rubyonrails.org") +xml.target("name"=>"compile", "option"=>"fast") + + +will produce + + +emphasized +emph & bold +A link + + + +Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: + + +xml.div { + xml.h1(@person.name) + xml.p(@person.bio) +} + + +would produce something like: + + +
+

David Heinemeier Hansson

+

A product of Danish Design during the Winter of '79...

+
+ + +A full-length RSS example actually used on Basecamp: + + +xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do + xml.channel do + xml.title(@feed_title) + xml.link(@url) + xml.description "Basecamp: Recent items" + xml.language "en-us" + xml.ttl "40" + + for item in @recent_items + xml.item do + xml.title(item_title(item)) + xml.description(item_description(item)) if item_description(item) + xml.pubDate(item_pubDate(item)) + xml.guid(@person.firm.account.url + @recent_items.url(item)) + xml.link(@person.firm.account.url + @recent_items.url(item)) + xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) + end + end + end +end + + +h5. Template caching + +By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode. + h4. Partials Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With a partial, you can move the code for rendering a particular piece of a response to its own file. @@ -222,6 +319,10 @@ You can also specify a second partial to be rendered between instances of the ma Rails will render the +_product_ruler+ partial (with no data passed in to it) between each pair of +_product+ partials. +h4. Layouts + +TODO... + h3. Using Templates, Partials and Layouts in "The Rails Way" TODO... -- cgit v1.2.3 From 9c861e8a0e2f21959ede5f71b56f8a50996d6416 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 18:29:55 -0300 Subject: Better formatting --- railties/guides/source/action_view_overview.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 2e36fd038f..ff581217d4 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -227,13 +227,13 @@ To render a partial as part of a view, you use the +render+ method within the vi <%= render "menu" %>
-This will render a file named +_menu.html.erb+ at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: +This will render a file named _menu.html.erb at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: <%= render "shared/menu" %> -That code will pull in the partial from +app/views/shared/_menu.html.erb+. +That code will pull in the partial from app/views/shared/_menu.html.erb. h5. Using Partials to Simplify Views @@ -252,7 +252,7 @@ One way to use partials is to treat them as the equivalent of subroutines: as a <%= render "shared/footer" %> -Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. +Here, the _ad_banner.html.erb and _footer.html.erb partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. h5. The :as and :object options -- cgit v1.2.3 From 8606fff6f125822c5c27c053ab13b1c3b26838c8 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 16 Apr 2011 20:09:54 -0300 Subject: Added CHANGELOG entry for new section additions on ActionView guide --- railties/guides/source/action_view_overview.textile | 1 + 1 file changed, 1 insertion(+) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index ff581217d4..e11cb00d9f 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -1697,5 +1697,6 @@ You can read more about the Rails Internationalization (I18n) API "here":i18n.ht h3. Changelog +* April 16, 2011: Added 'Using Action View with Rails', 'Templates' and 'Partials' sections. "Sebastian Martinez":http://wyeworks.com * September 3, 2009: Continuing work by Trevor Turk, leveraging the Action Pack docs and "What's new in Edge Rails":http://ryandaigle.com/articles/2007/8/3/what-s-new-in-edge-rails-partials-get-layouts * April 5, 2009: Starting work by Trevor Turk, leveraging Mike Gunderloy's docs -- cgit v1.2.3 From 96546bb63bd5f1b24f5126e7a3314580bc59584f Mon Sep 17 00:00:00 2001 From: Jason Weathered Date: Sun, 17 Apr 2011 21:10:02 +1000 Subject: Fix marshal round-tripping of fractional seconds (Time#subsec). --- activesupport/lib/active_support/core_ext/time/marshal.rb | 1 + activesupport/test/core_ext/time_ext_test.rb | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 1a4d918ce7..457d3f5b62 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -37,6 +37,7 @@ if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone time.instance_eval do if zone = defined?(@_zone) && remove_instance_variable('@_zone') ary = to_a + ary[0] += subsec if ary[0] == sec ary[-1] = zone utc? ? Time.utc(*ary) : Time.local(*ary) else diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 53d497013a..44e02109b1 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -808,4 +808,11 @@ class TimeExtMarshalingTest < Test::Unit::TestCase assert_equal t.zone, unmarshaled.zone assert_equal t, unmarshaled end + + def test_marshalling_preserves_fractional_seconds + t = Time.parse('00:00:00.500') + unmarshaled = Marshal.load(Marshal.dump(t)) + assert_equal t.to_f, unmarshaled.to_f + assert_equal t, unmarshaled + end end -- cgit v1.2.3 From 0fd0d68962bfde407b6a35116efe277293e43962 Mon Sep 17 00:00:00 2001 From: Jason Weathered Date: Sun, 17 Apr 2011 23:51:20 +1000 Subject: Bypass IdentityMap in PostgreSQL geometric tests. The identity map cache prevents us from seeing the DB formatted strings. --- activerecord/test/cases/base_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e57c5b3b87..815ff7b825 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -895,7 +895,7 @@ class BasicsTest < ActiveRecord::TestCase assert g.save # Reload and check that we have all the geometric attributes. - h = Geometric.find(g.id) + h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) } assert_equal '(5,6.1)', h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment @@ -923,7 +923,7 @@ class BasicsTest < ActiveRecord::TestCase assert g.save # Reload and check that we have all the geometric attributes. - h = Geometric.find(g.id) + h = ActiveRecord::IdentityMap.without { Geometric.find(g.id) } assert_equal '(5,6.1)', h.a_point assert_equal '[(2,3),(5.5,7)]', h.a_line_segment -- cgit v1.2.3 From 38e77879e0c4d908c8f74c2828bbce7c6c543934 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 13:06:48 -0300 Subject: Remove 2.3 since guides are supposed to be for Rails 3 --- railties/guides/source/i18n.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 543fe85f70..24ad74485e 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -520,7 +520,7 @@ I18n.t 'activerecord.errors.messages' h5. "Lazy" Lookup -Rails 2.3 implements a convenient way to look up the locale inside _views_. When you have the following dictionary: +Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary: es: -- cgit v1.2.3 From 3dc4d543f177e57cb08ac8fd16a5fff6f635822f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 15 Apr 2011 14:34:14 -0700 Subject: make our constructor signature match the superclass --- actionpack/lib/action_controller/metal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 585bd5e5ab..0133b2ecbc 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -131,7 +131,7 @@ module ActionController attr_internal :headers, :response, :request delegate :session, :to => "@_request" - def initialize(*) + def initialize @_headers = {"Content-Type" => "text/html"} @_status = 200 @_request = nil -- cgit v1.2.3 From d1f10e74caaedbc853d0f875d8e2a2ac16899fa0 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 16 Apr 2011 16:28:53 +0530 Subject: Test failing with Jruby "uninitialized constant TestJSONEncoding::JSON" activesupport/test/json/encoding_test.rb need Json to pass against with Jruby --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index a94f3689bc..c720b09a2c 100644 --- a/Gemfile +++ b/Gemfile @@ -60,7 +60,7 @@ end platforms :jruby do gem "ruby-debug", ">= 0.10.3" - + gem "json" gem "activerecord-jdbcsqlite3-adapter" # This is needed by now to let tests work on JRuby -- cgit v1.2.3 From 256b363eeecf6d0fb896aabd3fc619e200a5062c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 17 Apr 2011 20:47:52 +0100 Subject: Revert "Deprecate defining scopes with a callable (lambda, proc, etc) via the scope class method. Just define a class method yourself instead." This reverts commit f0e198bfa1e3f9689e0cde1d194a44027fc90b3c. Conflicts: activerecord/test/models/post.rb --- activerecord/CHANGELOG | 18 -------- activerecord/lib/active_record/named_scope.rb | 62 +-------------------------- activerecord/test/cases/named_scope_test.rb | 6 --- activerecord/test/models/comment.rb | 5 +-- activerecord/test/models/post.rb | 20 ++++----- activerecord/test/models/topic.rb | 26 +++++------ 6 files changed, 23 insertions(+), 114 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 93eb42a52c..6b3d408720 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,23 +1,5 @@ *Rails 3.1.0 (unreleased)* -* Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your - scope to be lazily evaluated, or takes parameters, please define it as a normal class method - instead. For example, change this: - - class Post < ActiveRecord::Base - scope :unpublished, lambda { where('published_at > ?', Time.now) } - end - - To this: - - class Post < ActiveRecord::Base - def self.unpublished - where('published_at > ?', Time.now) - end - end - - [Jon Leighton] - * Default scopes are now evaluated at the latest possible moment, to avoid problems where scopes would be created which would implicitly contain the default scope, which would then be impossible to get rid of via Model.unscoped. diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index f1df04950b..60840e6958 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -51,14 +51,6 @@ module ActiveRecord # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.where(:color => 'red'). # - # Note that this is simply 'syntactic sugar' for defining an actual class method: - # - # class Shirt < ActiveRecord::Base - # def self.red - # where(:color => 'red') - # end - # end - # # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it # resembles the association object constructed by a has_many declaration. For instance, # you can invoke Shirt.red.first, Shirt.red.count, Shirt.red.where(:size => 'small'). @@ -82,34 +74,14 @@ module ActiveRecord # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean # only shirts. # - # If you need to pass parameters to a scope, define it as a normal method: + # Named \scopes can also be procedural: # # class Shirt < ActiveRecord::Base - # def self.colored(color) - # where(:color => color) - # end + # scope :colored, lambda {|color| where(:color => color) } # end # # In this example, Shirt.colored('puce') finds all puce shirts. # - # Note that scopes defined with \scope will be evaluated when they are defined, rather than - # when they are used. For example, the following would be incorrect: - # - # class Post < ActiveRecord::Base - # scope :recent, where('published_at >= ?', Time.now - 1.week) - # end - # - # The example above would be 'frozen' to the Time.now value when the Post - # class was defined, and so the resultant SQL query would always be the same. The correct - # way to do this would be via a class method, which will re-evaluate the scope each time - # it is called: - # - # class Post < ActiveRecord::Base - # def self.recent - # where('published_at >= ?', Time.now - 1.week) - # end - # end - # # Named \scopes can also have extensions, just as with has_many declarations: # # class Shirt < ActiveRecord::Base @@ -120,18 +92,6 @@ module ActiveRecord # end # end # - # The above could also be written as a class method like so: - # - # class Shirt < ActiveRecord::Base - # def self.red - # where(:color => 'red').extending do - # def dom_id - # 'red_shirts' - # end - # end - # end - # end - # # Scopes can also be used while creating/building a record. # # class Article < ActiveRecord::Base @@ -168,24 +128,6 @@ module ActiveRecord valid_scope_name?(name) extension = Module.new(&Proc.new) if block_given? - if !scope_options.is_a?(Relation) && scope_options.respond_to?(:call) - ActiveSupport::Deprecation.warn <<-WARN -Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your scope to be lazily evaluated, or takes parameters, please define it as a normal class method instead. For example, change this: - -class Post < ActiveRecord::Base - scope :unpublished, lambda { where('published_at > ?', Time.now) } -end - -To this: - -class Post < ActiveRecord::Base - def self.unpublished - where('published_at > ?', Time.now) - end -end - WARN - end - scope_proc = lambda do |*args| options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options options = scoped.apply_finder_options(options) if options.is_a?(Hash) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 2880fdc651..8fd1fc2577 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -471,12 +471,6 @@ class NamedScopeTest < ActiveRecord::TestCase require "models/without_table" end end - - def test_scopes_with_callables_are_deprecated - assert_deprecated do - Post.scope :WE_SO_EXCITED, lambda { |partyingpartyingpartying, yeah| fun!.fun!.fun! } - end - end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 3bd7db7834..2a4c37089a 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,8 +1,5 @@ class Comment < ActiveRecord::Base - def self.limit_by(l) - limit(l) - end - + scope :limit_by, lambda {|l| limit(l) } scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" scope :not_again, where("comments.body NOT LIKE '%again%'") scope :for_first_post, :conditions => { :post_id => 1 } diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index a91c10276b..34cea60053 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -8,13 +8,12 @@ class Post < ActiveRecord::Base scope :containing_the_letter_a, where("body LIKE '%a%'") scope :ranked_by_comments, order("comments_count DESC") - def self.limit_by(l) - limit(l) - end - - def self.with_authors_at_address(address) - where('authors.author_address_id = ?', address.id).joins('JOIN authors ON authors.id = posts.author_id') - end + scope :limit_by, lambda {|l| limit(l) } + 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 @@ -29,10 +28,9 @@ class Post < ActiveRecord::Base scope :with_special_comments, :joins => :comments, :conditions => {:comments => {:type => 'SpecialComment'} } scope :with_very_special_comments, joins(:comments).where(:comments => {:type => 'VerySpecialComment'}) - - def self.with_post(post_id) - joins(:comments).where(:comments => { :post_id => post_id }) - end + scope :with_post, lambda {|post_id| + { :joins => :comments, :conditions => {:comments => {:post_id => post_id} } } + } has_many :comments do def find_most_recent diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 60e750e6c4..6440dbe8ab 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -1,20 +1,10 @@ class Topic < ActiveRecord::Base scope :base - - ActiveSupport::Deprecation.silence do - scope :written_before, lambda { |time| - if time - { :conditions => ['written_on < ?', time] } - end - } - - scope :with_object, Class.new(Struct.new(:klass)) { - def call - klass.where(:approved => true) - end - }.new(self) - end - + scope :written_before, lambda { |time| + if time + { :conditions => ['written_on < ?', time] } + end + } scope :approved, :conditions => {:approved => true} scope :rejected, :conditions => {:approved => false} @@ -29,6 +19,12 @@ class Topic < ActiveRecord::Base end end + scope :with_object, Class.new(Struct.new(:klass)) { + def call + klass.where(:approved => true) + end + }.new(self) + module NamedExtension def two 2 -- cgit v1.2.3 From 28146378d3c83ac8c0ea3427b6152ea61976d642 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 17 Apr 2011 20:55:24 +0100 Subject: Bring back some bits of documentation for scopes which were removed as part of the reversion in 256b363 --- activerecord/lib/active_record/named_scope.rb | 30 ++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 60840e6958..588f52be44 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -51,6 +51,14 @@ module ActiveRecord # The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, # in effect, represents the query Shirt.where(:color => 'red'). # + # Note that this is simply 'syntactic sugar' for defining an actual class method: + # + # class Shirt < ActiveRecord::Base + # def self.red + # where(:color => 'red') + # end + # end + # # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it # resembles the association object constructed by a has_many declaration. For instance, # you can invoke Shirt.red.first, Shirt.red.count, Shirt.red.where(:size => 'small'). @@ -77,11 +85,31 @@ module ActiveRecord # Named \scopes can also be procedural: # # class Shirt < ActiveRecord::Base - # scope :colored, lambda {|color| where(:color => color) } + # scope :colored, lambda { |color| where(:color => color) } # end # # In this example, Shirt.colored('puce') finds all puce shirts. # + # On Ruby 1.9 you can use the 'stabby lambda' syntax: + # + # scope :colored, ->(color) { where(:color => color) } + # + # Note that scopes defined with \scope will be evaluated when they are defined, rather than + # when they are used. For example, the following would be incorrect: + # + # class Post < ActiveRecord::Base + # scope :recent, where('published_at >= ?', Time.now - 1.week) + # end + # + # The example above would be 'frozen' to the Time.now value when the Post + # class was defined, and so the resultant SQL query would always be the same. The correct + # way to do this would be via a lambda, which will re-evaluate the scope each time + # it is called: + # + # class Post < ActiveRecord::Base + # scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) } + # end + # # Named \scopes can also have extensions, just as with has_many declarations: # # class Shirt < ActiveRecord::Base -- cgit v1.2.3 From 60547391c91a1968fa75d673f707a4b50d3d61c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Apr 2011 22:08:21 +0200 Subject: Fix more generator tests. --- railties/test/generators/namespaced_generators_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index d61a02d32f..eb56e8d1a4 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -248,7 +248,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase assert_file "test/unit/helpers/test_app/product_lines_helper_test.rb" # Stylesheets - assert_file "public/stylesheets/scaffold.css" + assert_file "app/assets/stylesheets/scaffold.css.scss" end def test_scaffold_on_revoke @@ -279,7 +279,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase assert_no_file "test/unit/helpers/test_app/product_lines_helper_test.rb" # Stylesheets (should not be removed) - assert_file "public/stylesheets/scaffold.css" + assert_file "app/assets/stylesheets/scaffold.css.scss" end def test_scaffold_with_namespace_on_invoke @@ -320,7 +320,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase assert_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" # Stylesheets - assert_file "public/stylesheets/scaffold.css" + assert_file "app/assets/stylesheets/scaffold.css.scss" end def test_scaffold_with_namespace_on_revoke @@ -352,6 +352,6 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase assert_no_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" # Stylesheets (should not be removed) - assert_file "public/stylesheets/scaffold.css" + assert_file "app/assets/stylesheets/scaffold.css.scss" end end -- cgit v1.2.3 From daf7a8c0db8ad23866e954cd925853ea5ce90fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 17 Apr 2011 22:10:08 +0200 Subject: What a lovely surprise, controller_generator_test was also failing. --- railties/test/generators/controller_generator_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 655d8ad450..2dfc91c683 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -39,8 +39,8 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_invokes_assets run_generator - assert_file "app/assets/javascripts/account.js" - assert_file "app/assets/stylesheets/account.css" + assert_file "app/assets/javascripts/account.js.coffee" + assert_file "app/assets/stylesheets/account.css.scss" end def test_invokes_default_test_framework -- cgit v1.2.3 From 2a5e317951fa28c570dc03c562d506ab366050ae Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 17 Apr 2011 21:33:08 +0100 Subject: Fix test_associate_existing in has_many_through_associations_test on mysql and postgresql --- .../test/cases/associations/has_many_through_associations_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1efe3420a0..70a4e06dbe 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -44,7 +44,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end def test_associate_existing - assert_queries(2) { posts(:thinking); people(:david) } + posts(:thinking); people(:david) # Warm cache assert_queries(1) do posts(:thinking).people << people(:david) -- cgit v1.2.3 From b839d4093356914b305b6fd0140782e8e198402e Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 17 Apr 2011 22:30:27 +0100 Subject: Fix test_load_save in test/cases/binary_test.rb (thanks @tenderlove for actually working out how to fix it) --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e74ec84e81..acc3e9c5e3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -366,7 +366,7 @@ module ActiveRecord case value when String return super unless 'bytea' == column.sql_type - escape_bytea(value) + { :value => value, :format => 1 } else super end @@ -1093,4 +1093,3 @@ module ActiveRecord end end end - -- cgit v1.2.3 From bd302542d0993a4965c870de607051748b6de0be Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:36:56 -0300 Subject: Documented NilClass#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index d0c1ea8326..76450a9238 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -37,7 +37,11 @@ class Object end end -class NilClass #:nodoc: +class NilClass + # Instances of NilClass are always blank + # Example: + # + # nil.blank? => true def blank? true end -- cgit v1.2.3 From a48d2a7a070540b60d50b14a201ffeba02e390f8 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:38:32 -0300 Subject: Documented FalseClass#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 76450a9238..57944bd271 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -47,7 +47,11 @@ class NilClass end end -class FalseClass #:nodoc: +class FalseClass + # Instances of FalseClass are always blank + # Example: + # + # false.blank? => true def blank? true end -- cgit v1.2.3 From c49799923098b63e906af955dc077cb24caeb659 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:39:40 -0300 Subject: Documented TrueClass#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 57944bd271..764d0666fd 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -57,7 +57,11 @@ class FalseClass end end -class TrueClass #:nodoc: +class TrueClass + # Instances of TrueClass are never blank + # Example: + # + # true.blank? => false def blank? false end -- cgit v1.2.3 From ceeed213a96d2f893838f9304b3953a1fc727cfc Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:42:53 -0300 Subject: Documented Array#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 764d0666fd..d6f0056ba4 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -67,7 +67,12 @@ class TrueClass end end -class Array #:nodoc: +class Array + # An array is blank if it's empty + # For example: + # + # [].blank? => true + # [1,2,3].blank? => false alias_method :blank?, :empty? end -- cgit v1.2.3 From b14b058be863e5a557191c9b403cc7ebc77951c9 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:44:34 -0300 Subject: Documented Hash#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index d6f0056ba4..8cef3e87ea 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -76,7 +76,12 @@ class Array alias_method :blank?, :empty? end -class Hash #:nodoc: +class Hash + # A hash is blank if it's empty + # For example: + # + # {}.blank? => true + # {:key => 'value'}.blank? => false alias_method :blank?, :empty? end -- cgit v1.2.3 From d0635b6ad43dd08fe2fc505388b4cd229e25845f Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 19:47:46 -0300 Subject: Documented String#blank? --- activesupport/lib/active_support/core_ext/object/blank.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 8cef3e87ea..5cf49e2cbf 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -85,7 +85,13 @@ class Hash alias_method :blank?, :empty? end -class String #:nodoc: +class String + # A string is blank if it's empty or contains whitespaces only + # For example: + # + # "".blank? => true + # " ".blank? => true + # " something here ".blank? => false def blank? self !~ /\S/ end -- cgit v1.2.3 From 0675047d79912b11a387941e7968d4a651f0cd48 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 20:12:39 -0300 Subject: Formatting examples --- .../lib/active_support/core_ext/object/blank.rb | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 5cf49e2cbf..be22d7534e 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -39,7 +39,8 @@ end class NilClass # Instances of NilClass are always blank - # Example: + # + # === Example # # nil.blank? => true def blank? @@ -49,7 +50,8 @@ end class FalseClass # Instances of FalseClass are always blank - # Example: + # + # === Example # # false.blank? => true def blank? @@ -59,7 +61,8 @@ end class TrueClass # Instances of TrueClass are never blank - # Example: + # + # === Example # # true.blank? => false def blank? @@ -69,7 +72,8 @@ end class Array # An array is blank if it's empty - # For example: + # + # === Examples # # [].blank? => true # [1,2,3].blank? => false @@ -78,7 +82,8 @@ end class Hash # A hash is blank if it's empty - # For example: + # + # === Examples # # {}.blank? => true # {:key => 'value'}.blank? => false @@ -87,7 +92,8 @@ end class String # A string is blank if it's empty or contains whitespaces only - # For example: + # + # === Examples # # "".blank? => true # " ".blank? => true -- cgit v1.2.3 From bb626e785a9ad32d025e429af24654f2af662d09 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 20:22:53 -0300 Subject: Docs for NilClass#try --- activesupport/lib/active_support/core_ext/object/try.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 04619124a1..341a6237cb 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -34,7 +34,19 @@ class Object end end -class NilClass #:nodoc: +class NilClass + # Instances of NilClass return always +nil+ + # It becomes specially helpful when navigating through associations that may return nil + # + # === Examples + # + # nil.try(:name) => nil + # + # Without try + # @person && @person.children.first && @person.children.first.name + # + # With try + # @person.try(:children).try(:first).try(:name) def try(*args) nil end -- cgit v1.2.3 From 49e7555a5ed5463fdf13474259e285d41f171da8 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 20:38:50 -0300 Subject: Docs for +duplicable?+ --- .../active_support/core_ext/object/duplicable.rb | 32 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index b05325790c..0f9129d0b6 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -22,25 +22,49 @@ class Object end end -class NilClass #:nodoc: +class NilClass + # Instances of NilClass are not duplicable + # + # === Example + # + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass def duplicable? false end end -class FalseClass #:nodoc: +class FalseClass + # Instances of FalseClass are not duplicable + # + # === Example + # + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass def duplicable? false end end -class TrueClass #:nodoc: +class TrueClass + # Instances of TrueClass are not duplicable + # + # === Example + # + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass def duplicable? false end end -class Symbol #:nodoc: +class Symbol + # Symbols are not duplicable + # + # === Example + # + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol def duplicable? false end -- cgit v1.2.3 From 9dfc2153270dcaa2f978ef52fbece99efc150bbc Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 17 Apr 2011 20:40:33 -0300 Subject: Formatting docs --- .../lib/active_support/core_ext/object/blank.rb | 20 ++++++++++---------- .../lib/active_support/core_ext/object/try.rb | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index be22d7534e..b92277f427 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -42,7 +42,7 @@ class NilClass # # === Example # - # nil.blank? => true + # nil.blank? # => true def blank? true end @@ -53,7 +53,7 @@ class FalseClass # # === Example # - # false.blank? => true + # false.blank? # => true def blank? true end @@ -64,7 +64,7 @@ class TrueClass # # === Example # - # true.blank? => false + # true.blank? # => false def blank? false end @@ -75,8 +75,8 @@ class Array # # === Examples # - # [].blank? => true - # [1,2,3].blank? => false + # [].blank? # => true + # [1,2,3].blank? # => false alias_method :blank?, :empty? end @@ -85,8 +85,8 @@ class Hash # # === Examples # - # {}.blank? => true - # {:key => 'value'}.blank? => false + # {}.blank? # => true + # {:key => 'value'}.blank? # => false alias_method :blank?, :empty? end @@ -95,9 +95,9 @@ class String # # === Examples # - # "".blank? => true - # " ".blank? => true - # " something here ".blank? => false + # "".blank? # => true + # " ".blank? # => true + # " something here ".blank? # => false def blank? self !~ /\S/ end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 341a6237cb..0977ac51e8 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -40,7 +40,7 @@ class NilClass # # === Examples # - # nil.try(:name) => nil + # nil.try(:name) # => nil # # Without try # @person && @person.children.first && @person.children.first.name -- cgit v1.2.3 From 9556e9c9236eee70c7c69d83893024ccec7587a5 Mon Sep 17 00:00:00 2001 From: Malcolm Locke Date: Mon, 18 Apr 2011 16:02:30 +1200 Subject: best-of-bread -> best-of-breed --- railties/guides/source/i18n.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 24ad74485e..5478a80567 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -863,7 +863,7 @@ If you find anything missing or wrong in this guide, please file a ticket on our h3. Contributing to Rails I18n -I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-bread of most widely useful features for inclusion in the core. +I18n support in Ruby on Rails was introduced in the release 2.2 and is still evolving. The project follows the good Ruby on Rails development tradition of evolving solutions in plugins and real applications first, and only then cherry-picking the best-of-breed of most widely useful features for inclusion in the core. Thus we encourage everybody to experiment with new ideas and features in plugins or other libraries and make them available to the community. (Don't forget to announce your work on our "mailing list":http://groups.google.com/group/rails-i18n!) -- cgit v1.2.3 From 4f044528c0373b0a581a12b1311a7a544ded6c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 08:12:51 +0200 Subject: Slightly reorganize rendering stack. --- actionpack/lib/abstract_controller/rendering.rb | 31 +++++++++++++++++----- .../lib/action_controller/metal/renderers.rb | 6 ++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 66f6d0eebb..d0dd730b06 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -105,16 +105,22 @@ module AbstractController # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. def render(*args, &block) - self.response_body = render_to_string(*args, &block) + options = _normalize_render(*args, &block) + self.response_body = render_to_body(options) end # Raw rendering of a template to a string. Just convert the results of - # render_to_body into a String. + # render_response into a String. # :api: plugin def render_to_string(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - render_to_body(options).tap { self.response_body = nil } + options = _normalize_render(*args, &block) + if self.response_body = render_to_body(options) + string = "" + response_body.each { |r| string << r } + string + end + ensure + self.response_body = nil end # Raw rendering of a template to a Rack-compatible body. @@ -151,8 +157,17 @@ module AbstractController hash end - # Normalize options by converting render "foo" to render :action => "foo" and + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) + options + end + + # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". + # :api: plugin def _normalize_args(action=nil, options={}) case action when NilClass @@ -169,6 +184,8 @@ module AbstractController options end + # Normalize options. + # :api: plugin def _normalize_options(options) if options[:partial] == true options[:partial] = action_name @@ -182,6 +199,8 @@ module AbstractController options end + # Process extra options. + # :api: plugin def _process_options(options) end end diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index dfda6618e7..0ad9dbeda9 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -95,17 +95,17 @@ module ActionController json = json.to_json(options) unless json.kind_of?(String) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON - self.response_body = json + json end add :js do |js, options| self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js(options) : js + js.respond_to?(:to_js) ? js.to_js(options) : js end add :xml do |xml, options| self.content_type ||= Mime::XML - self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml + xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml end end end -- cgit v1.2.3 From 944b4d57960569d5c9a08783044677721a6de4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 08:13:28 +0200 Subject: Add missing dependency. --- actionpack/lib/action_controller/metal/mime_responds.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 16d48e4677..f10287afb4 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -6,6 +6,8 @@ module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern + include ActionController::ImplicitRender + included do class_attribute :responder, :mimes_for_respond_to self.responder = ActionController::Responder -- cgit v1.2.3 From 7a152ab0127877eea6f2cef8ff6d1975a3fc16d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 08:17:47 +0200 Subject: Rename it to DataStreaming. --- actionpack/lib/action_controller.rb | 2 +- actionpack/lib/action_controller/base.rb | 2 +- .../lib/action_controller/metal/data_streaming.rb | 145 +++++++++++++++++++++ .../lib/action_controller/metal/streaming.rb | 145 --------------------- 4 files changed, 147 insertions(+), 147 deletions(-) create mode 100644 actionpack/lib/action_controller/metal/data_streaming.rb delete mode 100644 actionpack/lib/action_controller/metal/streaming.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 62cc18b253..26e6ac770f 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,6 +13,7 @@ module ActionController autoload :Compatibility autoload :ConditionalGet autoload :Cookies + autoload :DataStreaming autoload :Flash autoload :ForceSSL autoload :Head @@ -30,7 +31,6 @@ module ActionController autoload :Rescue autoload :Responder autoload :SessionManagement - autoload :Streaming autoload :Testing autoload :UrlFor end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5f9e082cd3..53e0a4b9d1 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -199,7 +199,7 @@ module ActionController Flash, RequestForgeryProtection, ForceSSL, - Streaming, + DataStreaming, RecordIdentifier, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb new file mode 100644 index 0000000000..997bc6e958 --- /dev/null +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -0,0 +1,145 @@ +require 'active_support/core_ext/file/path' + +module ActionController #:nodoc: + # Methods for sending arbitrary data and for streaming files to the browser, + # instead of rendering. + module DataStreaming + extend ActiveSupport::Concern + + include ActionController::Rendering + + DEFAULT_SEND_FILE_OPTIONS = { + :type => 'application/octet-stream'.freeze, + :disposition => 'attachment'.freeze, + }.freeze + + protected + # Sends the file. This uses a server-appropriate method (such as X-Sendfile) + # via the Rack::Sendfile middleware. The header to use is set via + # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". + # Your server can also configure this for you by setting the X-Sendfile-Type header. + # + # Be careful to sanitize the path parameter if it is coming from a web + # page. send_file(params[:path]) allows a malicious user to + # download any file on your server. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # Defaults to File.basename(path). + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # * :url_based_filename - set to +true+ if you want the browser guess the filename from + # the URL, which is necessary for i18n filenames on certain browsers + # (setting :filename overrides this option). + # + # The default Content-Type and Content-Disposition headers are + # set to download arbitrary binary files in as many browsers as + # possible. IE versions 4, 5, 5.5, and 6 are all known to have + # a variety of quirks (especially when downloading over SSL). + # + # Simple download: + # + # send_file '/path/to.zip' + # + # Show a JPEG in the browser: + # + # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' + # + # Show a 404 page in the browser: + # + # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 + # + # Read about the other Content-* HTTP headers if you'd like to + # provide the user with more information (such as Content-Description) in + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. + # + # Also be aware that the document may be cached by proxies and browsers. + # The Pragma and Cache-Control headers declare how the file may be cached + # by intermediaries. They default to require clients to validate with + # the server before releasing cached responses. See + # http://www.mnot.net/cache_docs/ for an overview of web caching and + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # for the Cache-Control header spec. + def send_file(path, options = {}) #:doc: + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) + + options[:filename] ||= File.basename(path) unless options[:url_based_filename] + send_file_headers! options + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + self.response_body = File.open(path, "rb") + end + + # Sends the given binary data to the browser. This method is similar to + # render :text => data, but also allows you to specify whether + # the browser should display the response as a file attachment (i.e. in a + # download dialog) or as inline data. You may also set the content type, + # the apparent file name, and other things. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with Mime::Type.register, for example :json + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :status - specifies the status code to send with the response. Defaults to '200 OK'. + # + # Generic data download: + # + # send_data buffer + # + # Download a dynamically-generated tarball: + # + # send_data generate_tgz('dir'), :filename => 'dir.tgz' + # + # Display an image Active Record in the browser: + # + # send_data image.data, :type => image.content_type, :disposition => 'inline' + # + # See +send_file+ for more information on HTTP Content-* headers and caching. + def send_data(data, options = {}) #:doc: + send_file_headers! options.dup + render options.slice(:status, :content_type).merge(:text => data) + end + + private + def send_file_headers!(options) + options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) + [:type, :disposition].each do |arg| + raise ArgumentError, ":#{arg} option required" if options[arg].nil? + end + + disposition = options[:disposition] + disposition += %(; filename="#{options[:filename]}") if options[:filename] + + content_type = options[:type] + + if content_type.is_a?(Symbol) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension + else + self.content_type = content_type + end + + headers.merge!( + 'Content-Disposition' => disposition, + 'Content-Transfer-Encoding' => 'binary' + ) + + response.sending_file = true + + # Fix a problem with IE 6.0 on opening downloaded files: + # If Cache-Control: no-cache is set (which Rails does by default), + # IE removes the file it just downloaded from its cache immediately + # after it displays the "open/save" dialog, which means that if you + # hit "open" the file isn't there anymore when the application that + # is called for handling the download is run, so let's workaround that + response.cache_control[:public] ||= false + end + end +end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb deleted file mode 100644 index 312dc8eb3e..0000000000 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ /dev/null @@ -1,145 +0,0 @@ -require 'active_support/core_ext/file/path' - -module ActionController #:nodoc: - # Methods for sending arbitrary data and for streaming files to the browser, - # instead of rendering. - module Streaming - extend ActiveSupport::Concern - - include ActionController::Rendering - - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - }.freeze - - protected - # Sends the file. This uses a server-appropriate method (such as X-Sendfile) - # via the Rack::Sendfile middleware. The header to use is set via - # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". - # Your server can also configure this for you by setting the X-Sendfile-Type header. - # - # Be careful to sanitize the path parameter if it is coming from a web - # page. send_file(params[:path]) allows a malicious user to - # download any file on your server. - # - # Options: - # * :filename - suggests a filename for the browser to use. - # Defaults to File.basename(path). - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with Mime::Type.register, for example :json - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # * :url_based_filename - set to +true+ if you want the browser guess the filename from - # the URL, which is necessary for i18n filenames on certain browsers - # (setting :filename overrides this option). - # - # The default Content-Type and Content-Disposition headers are - # set to download arbitrary binary files in as many browsers as - # possible. IE versions 4, 5, 5.5, and 6 are all known to have - # a variety of quirks (especially when downloading over SSL). - # - # Simple download: - # - # send_file '/path/to.zip' - # - # Show a JPEG in the browser: - # - # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' - # - # Show a 404 page in the browser: - # - # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 - # - # Read about the other Content-* HTTP headers if you'd like to - # provide the user with more information (such as Content-Description) in - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. - # - # Also be aware that the document may be cached by proxies and browsers. - # The Pragma and Cache-Control headers declare how the file may be cached - # by intermediaries. They default to require clients to validate with - # the server before releasing cached responses. See - # http://www.mnot.net/cache_docs/ for an overview of web caching and - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 - # for the Cache-Control header spec. - def send_file(path, options = {}) #:doc: - raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path) - - options[:filename] ||= File.basename(path) unless options[:url_based_filename] - send_file_headers! options - - self.status = options[:status] || 200 - self.content_type = options[:content_type] if options.key?(:content_type) - self.response_body = File.open(path, "rb") - end - - # Sends the given binary data to the browser. This method is similar to - # render :text => data, but also allows you to specify whether - # the browser should display the response as a file attachment (i.e. in a - # download dialog) or as inline data. You may also set the content type, - # the apparent file name, and other things. - # - # Options: - # * :filename - suggests a filename for the browser to use. - # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with Mime::Type.register, for example :json - # * :disposition - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * :status - specifies the status code to send with the response. Defaults to '200 OK'. - # - # Generic data download: - # - # send_data buffer - # - # Download a dynamically-generated tarball: - # - # send_data generate_tgz('dir'), :filename => 'dir.tgz' - # - # Display an image Active Record in the browser: - # - # send_data image.data, :type => image.content_type, :disposition => 'inline' - # - # See +send_file+ for more information on HTTP Content-* headers and caching. - def send_data(data, options = {}) #:doc: - send_file_headers! options.dup - render options.slice(:status, :content_type).merge(:text => data) - end - - private - def send_file_headers!(options) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? - end - - disposition = options[:disposition] - disposition += %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] - - if content_type.is_a?(Symbol) - extension = Mime[content_type] - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension - self.content_type = extension - else - self.content_type = content_type - end - - headers.merge!( - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) - - response.sending_file = true - - # Fix a problem with IE 6.0 on opening downloaded files: - # If Cache-Control: no-cache is set (which Rails does by default), - # IE removes the file it just downloaded from its cache immediately - # after it displays the "open/save" dialog, which means that if you - # hit "open" the file isn't there anymore when the application that - # is called for handling the download is run, so let's workaround that - response.cache_control[:public] ||= false - end - end -end -- cgit v1.2.3 From 389d15ef139e50696b274f2d61dd309ba2632877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 08:52:29 +0200 Subject: Body... wanna *stream* my body? Body... such a thrill my body! Added stream as class level method to make it explicit when to stream. Render also accepts :stream as option. --- actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/base.rb | 1 + .../lib/action_controller/metal/streaming.rb | 57 ++++++++++++++++++++ actionpack/lib/action_view/context.rb | 1 + .../controller/new_base/render_streaming_test.rb | 62 ++++++++++++++++++++++ 5 files changed, 122 insertions(+) create mode 100644 actionpack/lib/action_controller/metal/streaming.rb create mode 100644 actionpack/test/controller/new_base/render_streaming_test.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 26e6ac770f..aab2b9dc25 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -31,6 +31,7 @@ module ActionController autoload :Rescue autoload :Responder autoload :SessionManagement + autoload :Streaming autoload :Testing autoload :UrlFor end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 53e0a4b9d1..ca0dccf575 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -199,6 +199,7 @@ module ActionController Flash, RequestForgeryProtection, ForceSSL, + Streaming, DataStreaming, RecordIdentifier, HttpAuthentication::Basic::ControllerMethods, diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb new file mode 100644 index 0000000000..adb3e94134 --- /dev/null +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -0,0 +1,57 @@ +require 'active_support/core_ext/file/path' +require 'rack/chunked' + +module ActionController #:nodoc: + # Methods for sending streaming templates back to the client. + module Streaming + extend ActiveSupport::Concern + + include AbstractController::Rendering + attr_internal :stream + + module ClassMethods + # Render streaming templates. It accepts :only, :except, :if and :unless as options + # to specify when to stream, as in ActionController filters. + def stream(options={}) + if defined?(Fiber) + before_filter :_stream_filter, options + else + raise "You cannot use streaming if Fiber is not available." + end + end + end + + protected + + # Mark following render calls as streaming. + def _stream_filter #:nodoc: + self.stream = true + end + + # Consider the stream option when normalazing options. + def _normalize_options(options) #:nodoc: + super + options[:stream] = self.stream unless options.key?(:stream) + end + + # Set proper cache control and transfer encoding when streaming + def _process_options(options) #:nodoc: + super + if options[:stream] + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") + end + end + + # Call render_to_body if we are streaming instead of usual +render+. + def _render_template(options) #:nodoc: + if options.delete(:stream) + Rack::Chunked::Body.new view_context.render_body(options) + else + super + end + end + end +end + \ No newline at end of file diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 39d88333e8..a2a64de206 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -2,6 +2,7 @@ module ActionView module CompiledTemplates #:nodoc: # holds compiled template code end + # = Action View Context # # Action View contexts are supplied to Action Controller to render template. diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb new file mode 100644 index 0000000000..27ba4b1a29 --- /dev/null +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -0,0 +1,62 @@ +require 'abstract_unit' + +module RenderStreaming + class BasicController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "render_streaming/basic/hello_world.html.erb" => "Hello world", + "layouts/application.html.erb" => "<%= yield %>, I'm here!" + )] + + layout "application" + stream :only => :hello_world + + def hello_world + end + + def explicit + render :action => "hello_world", :stream => true + end + + def no_layout + render :action => "hello_world", :stream => true, :layout => false + end + + def explicit_cache + headers["Cache-Control"] = "private" + render :action => "hello_world", :stream => true + end + end + + class StreamingTest < Rack::TestCase + test "rendering with streaming enabled at the class level" do + get "/render_streaming/basic/hello_world" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with streaming given to render" do + get "/render_streaming/basic/explicit" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with streaming do not override explicit cache control given to render" do + get "/render_streaming/basic/explicit_cache" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! "private" + end + + test "rendering with streaming no layout" do + get "/render_streaming/basic/no_layout" + assert_body "b\r\nHello world\r\n0\r\n\r\n" + assert_streaming! + end + + def assert_streaming!(cache="no-cache") + assert_status 200 + assert_equal nil, headers["Content-Length"] + assert_equal "chunked", headers["Transfer-Encoding"] + assert_equal cache, headers["Cache-Control"] + end + end if defined?(Fiber) +end -- cgit v1.2.3 From 39372964d245ba7077515a698eaf71cb534800fe Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 18 Apr 2011 10:56:37 +0200 Subject: Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH] --- activesupport/CHANGELOG | 2 ++ activesupport/lib/active_support/core_ext/string.rb | 1 + activesupport/lib/active_support/core_ext/string/inquiry.rb | 13 +++++++++++++ activesupport/test/core_ext/string_ext_test.rb | 5 +++++ 4 files changed, 21 insertions(+) create mode 100644 activesupport/lib/active_support/core_ext/string/inquiry.rb diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 163f1c932c..69e9cbfd42 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.1.0 (unreleased)* +* Add String#inquiry as a convenience method for turning a string into a StringInquirer object [DHH] + * Add Object#in? to test if an object is included in another object [Prem Sichanugrist, Brian Morearty, John Reitano] * LocalCache strategy is now a real middleware class, not an anonymous class diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 8fb8c31ade..72522d395c 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -11,3 +11,4 @@ require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/exclude' require 'active_support/core_ext/string/encoding' require 'active_support/core_ext/string/strip' +require 'active_support/core_ext/string/inquiry' diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb new file mode 100644 index 0000000000..604f3bf4dc --- /dev/null +++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb @@ -0,0 +1,13 @@ +require 'active_support/string_inquirer' + +class String + # Wraps the current string in the ActiveSupport::StringInquirer class, + # which gives you a prettier way to test for equality. Example: + # + # env = "production".inquiry + # env.production? # => true + # env.development? # => false + def inquiry + ActiveSupport::StringInquirer.new(self) + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index bb865cae91..f0c289a418 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -250,6 +250,11 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end + + def test_string_inquiry + assert "production".inquiry.production? + assert !"production".inquiry.development? + end def test_truncate assert_equal "Hello World!", "Hello World!".truncate(12) -- cgit v1.2.3 From 3e335923161b4074cb048b2a828b0588fcf98b16 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 18 Apr 2011 11:18:37 +0200 Subject: Add description to assets:precompile such that it shows up in rake -T --- railties/lib/rails/tasks/assets.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake index 396ce728a1..9ed697f142 100644 --- a/railties/lib/rails/tasks/assets.rake +++ b/railties/lib/rails/tasks/assets.rake @@ -1,5 +1,6 @@ namespace :assets do - task :compile => :environment do + desc "Compile all the assets named in config.assets.precompile" + task :precompile => :environment do assets = Rails.application.config.assets.precompile Rails.application.assets.precompile(*assets) end -- cgit v1.2.3 From 07fde1a3d36d0622d5fa9c040bfca70db675926a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 14:25:51 +0200 Subject: Just define the controller if fibers are defined. --- actionpack/test/controller/new_base/render_streaming_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 27ba4b1a29..dbd6c66d1f 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -58,5 +58,5 @@ module RenderStreaming assert_equal "chunked", headers["Transfer-Encoding"] assert_equal cache, headers["Cache-Control"] end - end if defined?(Fiber) -end + end +end if defined?(Fiber) -- cgit v1.2.3 From 3b0f917b1dfabce6a6b338e4a7cb02995c055596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 18 Apr 2011 14:27:30 +0200 Subject: Test explicit skip. --- actionpack/test/controller/new_base/render_streaming_test.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index dbd6c66d1f..ffc4b331ec 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -8,11 +8,15 @@ module RenderStreaming )] layout "application" - stream :only => :hello_world + stream :only => [:hello_world, :skip] def hello_world end + def skip + render :action => "hello_world", :stream => false + end + def explicit render :action => "hello_world", :stream => true end @@ -52,6 +56,11 @@ module RenderStreaming assert_streaming! end + test "skip rendering with streaming at render level" do + get "/render_streaming/basic/skip" + assert_body "Hello world, I'm here!" + end + def assert_streaming!(cache="no-cache") assert_status 200 assert_equal nil, headers["Content-Length"] -- cgit v1.2.3 From af41d5540b0bab8c8cee8cbc7cbfb8fab0676518 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 18 Apr 2011 09:33:00 -0300 Subject: Fixed docs for NilClass#try --- activesupport/lib/active_support/core_ext/object/try.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 0977ac51e8..c2a3abbc7c 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -9,12 +9,12 @@ class Object # # ==== Examples # - # Without try + # Without +try+ # @person && @person.name # or # @person ? @person.name : nil # - # With try + # With +try+ # @person.try(:name) # # +try+ also accepts arguments and/or a block, for the method it is trying @@ -35,17 +35,17 @@ class Object end class NilClass - # Instances of NilClass return always +nil+ - # It becomes specially helpful when navigating through associations that may return nil + # Instances of +NilClass+ return always +nil+. + # It becomes specially helpful when navigating through associations that may return +nil+. # # === Examples # - # nil.try(:name) # => nil + # nill.try(:name) # => nil # - # Without try - # @person && @person.children.first && @person.children.first.name + # Without +try+ + # @person && !@person.children.blank? && @person.children.first.name # - # With try + # With +try+ # @person.try(:children).try(:first).try(:name) def try(*args) nil -- cgit v1.2.3 From 51f5209dc13afd8399a7f8e7a15041e642d3775b Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 18 Apr 2011 09:34:51 -0300 Subject: oops fixed typo --- activesupport/lib/active_support/core_ext/object/try.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index c2a3abbc7c..aedf5c8c82 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -40,7 +40,7 @@ class NilClass # # === Examples # - # nill.try(:name) # => nil + # nil.try(:name) # => nil # # Without +try+ # @person && !@person.children.blank? && @person.children.first.name -- cgit v1.2.3 From 280a8709923d2bf707ec4d82ac1e5cb14941da3d Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 18 Apr 2011 10:02:55 -0300 Subject: Formated docs --- .../lib/active_support/core_ext/object/blank.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index b92277f427..6d14a6dce1 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,6 +1,6 @@ class Object - # An object is blank if it's false, empty, or a whitespace string. - # For example, "", " ", +nil+, [], and {} are blank. + # An object is +blank+ if it's false, empty, or a whitespace string. + # For example, "", " ", +nil+, [], and {} are +blank+. # # This simplifies: # @@ -18,7 +18,7 @@ class Object !blank? end - # Returns object if it's #present? otherwise returns nil. + # Returns object if it's present? otherwise returns nil. # object.presence is equivalent to object.present? ? object : nil. # # This is handy for any representation of objects where blank is the same @@ -38,7 +38,7 @@ class Object end class NilClass - # Instances of NilClass are always blank + # Instances of +NilClass+ are always +blank+. # # === Example # @@ -49,7 +49,7 @@ class NilClass end class FalseClass - # Instances of FalseClass are always blank + # Instances of +FalseClass+ are always +blank+. # # === Example # @@ -60,7 +60,7 @@ class FalseClass end class TrueClass - # Instances of TrueClass are never blank + # Instances of +TrueClass+ are never +blank+. # # === Example # @@ -71,7 +71,7 @@ class TrueClass end class Array - # An array is blank if it's empty + # An array is +blank+ if it's +empty+. # # === Examples # @@ -81,7 +81,7 @@ class Array end class Hash - # A hash is blank if it's empty + # A hash is +blank+ if it's +empty+. # # === Examples # @@ -91,7 +91,7 @@ class Hash end class String - # A string is blank if it's empty or contains whitespaces only + # A string is +blank+ if it's empty or contains whitespaces only. # # === Examples # -- cgit v1.2.3 From 92537b8c273ea57eb7e1d686071f1876a5e9abe4 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Mon, 18 Apr 2011 17:06:07 +0530 Subject: File should be open in read/write mode. When doing lock on a file. --- activesupport/lib/active_support/cache/file_store.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 9936b33e22..a1376ae52a 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -121,7 +121,7 @@ module ActiveSupport # Lock a file for a block so only one process can modify it at a time. def lock_file(file_name, &block) # :nodoc: if File.exist?(file_name) - File.open(file_name, 'r') do |f| + File.open(file_name, 'r+') do |f| begin f.flock File::LOCK_EX yield -- cgit v1.2.3 From 0acc6bd6cb1d27fdbb0c00ac3a322bc8413e03cc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 18 Apr 2011 21:07:04 +0200 Subject: Use proper coffee comments --- .../rails/generators/rails/assets/templates/javascript.js.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee index 09b2da094a..761567942f 100644 --- a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee +++ b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee @@ -1,3 +1,3 @@ -// Place all the behaviors and hooks related to the matching controller here. -// All this logic will automatically be available in application.js. -// You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ -- cgit v1.2.3 From e019587e31abe66416c0b5d26b6ac177b345a727 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Mon, 18 Apr 2011 21:39:15 +0200 Subject: removed AS backends and instead rely on MultiJson for json decoding --- Gemfile | 2 + .../lib/active_support/json/backends/jsongem.rb | 47 --------- .../lib/active_support/json/backends/yajl.rb | 44 -------- .../lib/active_support/json/backends/yaml.rb | 113 --------------------- activesupport/lib/active_support/json/decoding.rb | 56 +++++----- activesupport/test/json/decoding_test.rb | 11 +- 6 files changed, 39 insertions(+), 234 deletions(-) delete mode 100644 activesupport/lib/active_support/json/backends/jsongem.rb delete mode 100644 activesupport/lib/active_support/json/backends/yajl.rb delete mode 100644 activesupport/lib/active_support/json/backends/yaml.rb diff --git a/Gemfile b/Gemfile index c720b09a2c..0b6de3061f 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,8 @@ gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" gem "sass", ">= 3.0" +gem "multi_json", :git => 'git://github.com/intridea/multi_json.git' + gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" diff --git a/activesupport/lib/active_support/json/backends/jsongem.rb b/activesupport/lib/active_support/json/backends/jsongem.rb deleted file mode 100644 index 533ba25da3..0000000000 --- a/activesupport/lib/active_support/json/backends/jsongem.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'json' unless defined?(JSON) - -module ActiveSupport - module JSON - module Backends - module JSONGem - ParseError = ::JSON::ParserError - extend self - - # Parses a JSON string or IO and convert it into an object - def decode(json) - if json.respond_to?(:read) - json = json.read - end - data = ::JSON.parse(json) - if ActiveSupport.parse_json_times - convert_dates_from(data) - else - data - end - end - - private - def convert_dates_from(data) - case data - when nil - nil - when DATE_REGEX - begin - DateTime.parse(data) - rescue ArgumentError - data - end - when Array - data.map! { |d| convert_dates_from(d) } - when Hash - data.each do |key, value| - data[key] = convert_dates_from(value) - end - else - data - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/json/backends/yajl.rb b/activesupport/lib/active_support/json/backends/yajl.rb deleted file mode 100644 index 58818658c7..0000000000 --- a/activesupport/lib/active_support/json/backends/yajl.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'yajl' unless defined?(Yajl) - -module ActiveSupport - module JSON - module Backends - module Yajl - ParseError = ::Yajl::ParseError - extend self - - # Parses a JSON string or IO and convert it into an object - def decode(json) - data = ::Yajl::Parser.new.parse(json) - if ActiveSupport.parse_json_times - convert_dates_from(data) - else - data - end - end - - private - def convert_dates_from(data) - case data - when nil - nil - when DATE_REGEX - begin - DateTime.parse(data) - rescue ArgumentError - data - end - when Array - data.map! { |d| convert_dates_from(d) } - when Hash - data.each do |key, value| - data[key] = convert_dates_from(value) - end - else - data - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb deleted file mode 100644 index e25e29d36b..0000000000 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'active_support/core_ext/string/starts_ends_with' - -module ActiveSupport - module JSON - module Backends - module Yaml - ParseError = ::StandardError - extend self - - EXCEPTIONS = [::ArgumentError] # :nodoc: - begin - require 'psych' - EXCEPTIONS << Psych::SyntaxError - rescue LoadError - end - - # Parses a JSON string or IO and converts it into an object - def decode(json) - if json.respond_to?(:read) - json = json.read - end - YAML.load(convert_json_to_yaml(json)) - rescue *EXCEPTIONS => e - raise ParseError, "Invalid JSON string: '%s'" % json - end - - protected - # Ensure that ":" and "," are always followed by a space - def convert_json_to_yaml(json) #:nodoc: - require 'strscan' unless defined? ::StringScanner - scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, [] - while scanner.scan_until(/(\\['"]|['":,\\]|\\.|[\]])/) - case char = scanner[1] - when '"', "'" - if !quoting - quoting = char - pos = scanner.pos - elsif quoting == char - if valid_date?(json[pos..scanner.pos-2]) - # found a date, track the exact positions of the quotes so we can - # overwrite them with spaces later. - times << pos - end - quoting = false - end - when ":",",", "]" - marks << scanner.pos - 1 unless quoting - when "\\" - scanner.skip(/\\/) - end - end - - if marks.empty? - json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do - ustr = $1 - if ustr.start_with?('u') - char = [ustr[1..-1].to_i(16)].pack("U") - # "\n" needs extra escaping due to yaml formatting - char == "\n" ? "\\n" : char - elsif ustr == '\\' - '\\\\' - else - ustr - end - end - else - left_pos = [-1].push(*marks) - right_pos = marks << scanner.pos + scanner.rest_size - output = [] - left_pos.each_with_index do |left, i| - scanner.pos = left.succ - chunk = scanner.peek(right_pos[i] - scanner.pos + 1) - if ActiveSupport.parse_json_times - # overwrite the quotes found around the dates with spaces - while times.size > 0 && times[0] <= right_pos[i] - chunk.insert(times.shift - scanner.pos - 1, '! ') - end - end - chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do - ustr = $1 - if ustr.start_with?('u') - char = [ustr[1..-1].to_i(16)].pack("U") - # "\n" needs extra escaping due to yaml formatting - char == "\n" ? "\\n" : char - elsif ustr == '\\' - '\\\\' - else - ustr - end - end - output << chunk - end - output = output * " " - - output.gsub!(/\\\//, '/') - output - end - end - - private - def valid_date?(date_string) - begin - date_string =~ DATE_REGEX && DateTime.parse(date_string) - rescue ArgumentError - false - end - end - - end - end - end -end - diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index c1f6330c6c..ac99f180a5 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -1,32 +1,31 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/module/delegation' +require 'multi_json' module ActiveSupport # Look for and parse json strings that look like ISO 8601 times. mattr_accessor :parse_json_times module JSON - # Listed in order of preference. - DECODERS = %w(Yajl Yaml) - class << self - attr_reader :parse_error - delegate :decode, :to => :backend + def decode(json, options ={}) + data = MultiJson.decode(json, options) + if ActiveSupport.parse_json_times + convert_dates_from(data) + else + data + end + end - def backend - set_default_backend unless defined?(@backend) - @backend + def engine + MultiJson.engine end + alias :backend :engine - def backend=(name) - if name.is_a?(Module) - @backend = name - else - require "active_support/json/backends/#{name.to_s.downcase}" - @backend = ActiveSupport::JSON::Backends::const_get(name) - end - @parse_error = @backend::ParseError + def engine=(name) + MultiJson.engine = name end + alias :backend= :engine= def with_backend(name) old_backend, self.backend = backend, name @@ -35,15 +34,26 @@ module ActiveSupport self.backend = old_backend end - def set_default_backend - DECODERS.find do |name| + private + + def convert_dates_from(data) + case data + when nil + nil + when DATE_REGEX begin - self.backend = name - true - rescue LoadError - # Try next decoder. - false + DateTime.parse(data) + rescue ArgumentError + data end + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.each do |key, value| + data[key] = convert_dates_from(value) + end + else + data end end end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 88cf97de7e..0e6772e284 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -56,12 +56,9 @@ class TestJSONDecoding < ActiveSupport::TestCase %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"} } - # load the default JSON backend - ActiveSupport::JSON.backend = 'Yaml' - - backends = %w(Yaml) - backends << "JSONGem" if defined?(::JSON) - backends << "Yajl" if defined?(::Yajl) + backends = [:ok_json] + backends << :json_gem if defined?(::JSON) + backends << :yajl if defined?(::Yajl) backends.each do |backend| TESTS.each do |json, expected| @@ -85,7 +82,7 @@ class TestJSONDecoding < ActiveSupport::TestCase end def test_failed_json_decoding - assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } + assert_raise(MultiJson::DecodeError) { ActiveSupport::JSON.decode(%({: 1})) } end end -- cgit v1.2.3 From 64e2a549cf92f25026a85575940a722492310125 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 18 Apr 2011 14:43:24 -0700 Subject: adding a rake task to help generate changelog notes for release announcements --- tasks/release.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tasks/release.rb b/tasks/release.rb index a605fed160..01950b227d 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -61,6 +61,32 @@ directory "dist" end end +namespace :changelog do + task :release_date do + FRAMEWORKS.each do |fw| + require 'date' + replace = '\1(' + Date.today.strftime('%B %d, %Y') + ')' + fname = File.join fw, 'CHANGELOG' + + contents = File.read(fname).sub(/^([^(]*)\(unreleased\)/, replace) + File.open(fname, 'wb') { |f| f.write contents } + end + end + + task :release_summary do + FRAMEWORKS.each do |fw| + puts "## #{fw}" + fname = File.join fw, 'CHANGELOG' + contents = File.readlines fname + contents.shift + changes = [] + changes << contents.shift until contents.first =~ /^\*Rails \d+\.\d+\.\d+/ + puts changes.reject { |change| change.strip.empty? }.join + puts + end + end +end + namespace :all do task :build => FRAMEWORKS.map { |f| "#{f}:build" } + ['rails:build'] task :install => FRAMEWORKS.map { |f| "#{f}:install" } + ['rails:install'] -- cgit v1.2.3 From 1d8be7bc6f098b829ac14ef457af688043975ddc Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 19 Apr 2011 00:01:50 +0200 Subject: AS Json parse_error makes a return for backwards compatibility, although it will return MultiJson::DecodeError --- activesupport/lib/active_support/json/decoding.rb | 4 ++++ activesupport/test/json/decoding_test.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index ac99f180a5..cbeb6c0a28 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -34,6 +34,10 @@ module ActiveSupport self.backend = old_backend end + def parse_error + MultiJson::DecodeError + end + private def convert_dates_from(data) diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 0e6772e284..6ccffa59b1 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -82,7 +82,7 @@ class TestJSONDecoding < ActiveSupport::TestCase end def test_failed_json_decoding - assert_raise(MultiJson::DecodeError) { ActiveSupport::JSON.decode(%({: 1})) } + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } end end -- cgit v1.2.3 From 6f84c73dc48538202766cff3d973a53d3c30848e Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 18 Apr 2011 23:15:38 +0100 Subject: Un-deprecate using 'default_scope' as a macro, but if you are calling the macro multiple times that will give deprecation warnings, and in 3.2 we will simply overwrite the default scope when you call the macro multiple times. --- activerecord/CHANGELOG | 27 ++-- activerecord/lib/active_record/base.rb | 47 +++--- activerecord/test/cases/relation_scoping_test.rb | 181 +++-------------------- activerecord/test/models/bulb.rb | 5 +- activerecord/test/models/car.rb | 8 +- activerecord/test/models/categorization.rb | 5 +- activerecord/test/models/developer.rb | 68 ++------- activerecord/test/models/post.rb | 5 +- activerecord/test/models/reference.rb | 5 +- activerecord/test/models/without_table.rb | 4 +- 10 files changed, 85 insertions(+), 270 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 6b3d408720..4ae82a6419 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -11,25 +11,34 @@ [Jon Leighton] -* Deprecated support for passing hashes and relations to 'default_scope'. Please create a class - method for your scope instead. For example, change this: +* Calling 'default_scope' multiple times in a class (including when a superclass calls + 'default_scope') is deprecated. The current behavior is that this will merge the default + scopes together: - class Post < ActiveRecord::Base + class Post < ActiveRecord::Base # Rails 3.1 + default_scope where(:published => true) + default_scope where(:hidden => false) + # The default scope is now: where(:published => true, :hidden => false) + end + + In Rails 3.2, the behavior will be changed to overwrite previous scopes: + + class Post < ActiveRecord::Base # Rails 3.2 default_scope where(:published => true) + default_scope where(:hidden => false) + # The default scope is now: where(:hidden => false) end - To this: + If you wish to merge default scopes in special ways, it is recommended to define your default + scope as a class method and use the standard techniques for sharing code (inheritance, mixins, + etc.): class Post < ActiveRecord::Base def self.default_scope - where(:published => true) + where(:published => true).where(:hidden => false) end end - Rationale: It will make the implementation simpler because we can simply use inheritance to - handle inheritance scenarios, rather than trying to make up our own rules about what should - happen when you call default_scope multiple times or in subclasses. - [Jon Leighton] * PostgreSQL adapter only supports PostgreSQL version 8.2 and higher. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 08a41e2d8b..5b316c17be 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1177,13 +1177,11 @@ MSG Thread.current[:"#{self}_current_scope"] = scope end - # Implement this method in your model to set a default scope for all operations on + # Use this macro in your model to set a default scope for all operations on # the model. # # class Person < ActiveRecord::Base - # def self.default_scope - # order('last_name, first_name') - # end + # default_scope order('last_name, first_name') # end # # Person.all # => SELECT * FROM people ORDER BY last_name, first_name @@ -1192,39 +1190,48 @@ MSG # applied while updating a record. # # class Article < ActiveRecord::Base - # def self.default_scope - # where(:published => true) - # end + # default_scope where(:published => true) # end # # Article.new.published # => true # Article.create.published # => true # - # === Deprecation warning + # If you need to do more complex things with a default scope, you can alternatively + # define it as a class method: # - # There is an alternative syntax as follows: - # - # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end # end - # - # This is now deprecated and will be removed in Rails 3.2. def default_scope(scope = {}) - ActiveSupport::Deprecation.warn <<-WARN -Passing a hash or scope to default_scope is deprecated and will be removed in Rails 3.2. You should create a class method for your scope instead. For example, change this: + if default_scopes.length != 0 + ActiveSupport::Deprecation.warn <<-WARN +Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together: -class Post < ActiveRecord::Base +class Post < ActiveRecord::Base # Rails 3.1 default_scope where(:published => true) + default_scope where(:hidden => false) + # The default scope is now: where(:published => true, :hidden => false) end -To this: +In Rails 3.2, the behavior will be changed to overwrite previous scopes: + +class Post < ActiveRecord::Base # Rails 3.2 + default_scope where(:published => true) + default_scope where(:hidden => false) + # The default scope is now: where(:hidden => false) +end + +If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.): class Post < ActiveRecord::Base def self.default_scope - where(:published => true) + where(:published => true).where(:hidden => false) end end -WARN + WARN + end self.default_scopes = default_scopes.dup << scope end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 5079aec9ba..e231371cf8 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -308,6 +308,10 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_default_scope_as_class_method + assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all + end + def test_default_scope_is_unscoped_on_find assert_equal 1, DeveloperCalledDavid.count assert_equal 11, DeveloperCalledDavid.unscoped.count @@ -339,6 +343,12 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 50000, wheres[:salary] end + def test_default_scope_with_multiple_calls + wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash + assert_equal "Jamis", wheres[:name] + assert_equal 50000, wheres[:salary] + end + def test_method_scope expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } @@ -434,175 +444,18 @@ class DefaultScopingTest < ActiveRecord::TestCase assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 10, DeveloperCalledJamis.unscoped.poor.length end -end - -class DeprecatedDefaultScopingTest < ActiveRecord::TestCase - fixtures :developers, :posts - - def test_default_scope - expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary } - received = DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - assert_equal expected, received - end - def test_default_scope_is_unscoped_on_find - assert_equal 1, DeprecatedDeveloperCalledDavid.count - assert_equal 11, DeprecatedDeveloperCalledDavid.unscoped.count - end + def test_multiple_default_scope_calls_are_deprecated + klass = Class.new(ActiveRecord::Base) - def test_default_scope_is_unscoped_on_create - assert_nil DeprecatedDeveloperCalledJamis.unscoped.create!.name - end - - def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeprecatedDeveloperCalledDavid.find(:all).map(&:id).sort - assert_equal nil, DeprecatedDeveloperCalledDavid.create!.name - end - - def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeprecatedDeveloperCalledJamis.find(:all).map(&:id).sort - assert_equal 'Jamis', DeprecatedDeveloperCalledJamis.create!.name - end - - def test_default_scoping_with_threads - 2.times do - Thread.new { assert DeprecatedDeveloperOrderedBySalary.scoped.to_sql.include?('salary DESC') }.join + assert_not_deprecated do + klass.send(:default_scope, :foo => :bar) end - end - - def test_default_scoping_with_inheritance - # Inherit a class having a default scope and define a new default scope - klass = Class.new(DeprecatedDeveloperOrderedBySalary) - ActiveSupport::Deprecation.silence { klass.send :default_scope, :limit => 1 } - - # Scopes added on children should append to parent scope - assert_equal [developers(:jamis).id], klass.all.map(&:id) - - # Parent should still have the original scope - assert_equal Developer.order('salary DESC').map(&:id), DeprecatedDeveloperOrderedBySalary.all.map(&:id) - end - - def test_default_scope_called_twice_merges_conditions - Developer.destroy_all - Developer.create!(:name => "David", :salary => 80000) - Developer.create!(:name => "David", :salary => 100000) - Developer.create!(:name => "Brian", :salary => 100000) - klass = Class.new(Developer) - ActiveSupport::Deprecation.silence do - klass.__send__ :default_scope, :conditions => { :name => "David" } - klass.__send__ :default_scope, :conditions => { :salary => 100000 } + assert_deprecated do + klass.send(:default_scope, :foo => :bar) end - assert_equal 1, klass.count - assert_equal "David", klass.first.name - assert_equal 100000, klass.first.salary - end - - def test_default_scope_called_twice_in_different_place_merges_where_clause - Developer.destroy_all - Developer.create!(:name => "David", :salary => 80000) - Developer.create!(:name => "David", :salary => 100000) - Developer.create!(:name => "Brian", :salary => 100000) - - klass = Class.new(Developer) - ActiveSupport::Deprecation.silence do - klass.class_eval do - default_scope where("name = 'David'") - default_scope where("salary = 100000") - end - end - - assert_equal 1, klass.count - assert_equal "David", klass.first.name - assert_equal 100000, klass.first.salary - end - def test_method_scope - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } - received = DeprecatedDeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_nested_scope - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary } - received = DeprecatedDeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do - DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - - def test_scope_overwrites_default - expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name } - received = DeprecatedDeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name } - assert_equal expected, received - end - - def test_reorder_overrides_default_scope_order - expected = Developer.order('name DESC').collect { |dev| dev.name } - received = DeprecatedDeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name } - assert_equal expected, received - end - - def test_nested_exclusive_scope - expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary } - received = DeprecatedDeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do - DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } - end - assert_equal expected, received - end - - def test_order_in_default_scope_should_prevail - expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary } - received = DeprecatedDeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } - assert_equal expected, received - end - - def test_default_scope_using_relation - posts = DeprecatedPostWithComment.scoped - assert_equal 2, posts.to_a.length - assert_equal posts(:thinking), posts.first - end - - def test_create_attribute_overwrites_default_scoping - assert_equal 'David', DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').name - assert_equal 200000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary - end - - def test_create_attribute_overwrites_default_values - assert_equal nil, DeprecatedPoorDeveloperCalledJamis.create!(:salary => nil).salary - assert_equal 50000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').salary - end - - def test_default_scope_attribute - jamis = DeprecatedPoorDeveloperCalledJamis.new(:name => 'David') - assert_equal 50000, jamis.salary - end - - def test_where_attribute - aaron = DeprecatedPoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron') - assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name - end - - def test_where_attribute_merge - aaron = DeprecatedPoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') - assert_equal 'Aaron', aaron.name - end - - def test_create_with_merge - aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge( - DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new - assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name - - aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20). - create_with(:name => 'Aaron').new - assert_equal 20, aaron.salary - assert_equal 'Aaron', aaron.name - end - - def test_create_with_reset - jamis = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new - assert_equal 'Jamis', jamis.name + assert_equal 2, klass.default_scopes.length end end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 89ee5416bf..c68d008c26 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,8 +1,5 @@ class Bulb < ActiveRecord::Base - def self.default_scope - where :name => 'defaulty' - end - + default_scope where(:name => 'defaulty') belongs_to :car attr_reader :scope_after_initialize diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index a978debb58..b036f0f5c9 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -15,13 +15,9 @@ class Car < ActiveRecord::Base end class CoolCar < Car - def self.default_scope - order 'name desc' - end + default_scope :order => 'name desc' end class FastCar < Car - def self.default_scope - order 'name desc' - end + default_scope :order => 'name desc' end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb index 39441e8610..4bd980e606 100644 --- a/activerecord/test/models/categorization.rb +++ b/activerecord/test/models/categorization.rb @@ -12,10 +12,7 @@ end class SpecialCategorization < ActiveRecord::Base self.table_name = 'categorizations' - - def self.default_scope - where(:special => true) - end + default_scope where(:special => true) belongs_to :author belongs_to :category diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 10385ba899..93363f47c5 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -86,10 +86,7 @@ end class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' - - def self.default_scope - order('salary DESC') - end + default_scope :order => 'salary DESC' scope :by_name, order('name DESC') @@ -102,74 +99,41 @@ end class DeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' - - def self.default_scope - where "name = 'David'" - end + default_scope where("name = 'David'") end -class DeveloperCalledJamis < ActiveRecord::Base +class ClassMethodDeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' def self.default_scope - where :name => 'Jamis' + where(:name => 'David') end - - scope :poor, where('salary < 150000') end -class AbstractDeveloperCalledJamis < ActiveRecord::Base - self.abstract_class = true - - def self.default_scope - where :name => 'Jamis' - end +class DeveloperCalledJamis < ActiveRecord::Base + self.table_name = 'developers' + default_scope where(:name => 'Jamis') + scope :poor, where('salary < 150000') end class PoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' - - def self.default_scope - where :name => 'Jamis', :salary => 50000 - end + default_scope where(:name => 'Jamis', :salary => 50000) end class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis self.table_name = 'developers' - def self.default_scope - super.where :salary => 50000 + ActiveSupport::Deprecation.silence do + default_scope where(:salary => 50000) end end -ActiveSupport::Deprecation.silence do - class DeprecatedDeveloperOrderedBySalary < ActiveRecord::Base - self.table_name = 'developers' - default_scope :order => 'salary DESC' - - def self.by_name - order('name DESC') - end - - def self.all_ordered_by_name - with_scope(:find => { :order => 'name DESC' }) do - find(:all) - end - end - end - - class DeprecatedDeveloperCalledDavid < ActiveRecord::Base - self.table_name = 'developers' - default_scope :conditions => "name = 'David'" - end - - class DeprecatedDeveloperCalledJamis < ActiveRecord::Base - self.table_name = 'developers' - default_scope :conditions => { :name => 'Jamis' } - end +class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base + self.table_name = 'developers' + default_scope where(:name => 'Jamis') - class DeprecatedPoorDeveloperCalledJamis < ActiveRecord::Base - self.table_name = 'developers' - default_scope :conditions => { :name => 'Jamis', :salary => 50000 } + ActiveSupport::Deprecation.silence do + default_scope where(:salary => 50000) end end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 34cea60053..80296032bb 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -157,10 +157,7 @@ end class FirstPost < ActiveRecord::Base self.table_name = 'posts' - - def self.default_scope - where(:id => 1) - end + default_scope where(:id => 1) has_many :comments, :foreign_key => :post_id has_one :comment, :foreign_key => :post_id diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index 76c0a1a32e..c5af0b5d5f 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -19,8 +19,5 @@ end class BadReference < ActiveRecord::Base self.table_name = 'references' - - def self.default_scope - where :favourite => false - end + default_scope where(:favourite => false) end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb index 1a63d6ceb6..184ab1649e 100644 --- a/activerecord/test/models/without_table.rb +++ b/activerecord/test/models/without_table.rb @@ -1,5 +1,3 @@ class WithoutTable < ActiveRecord::Base - def self.default_scope - where(:published => true) - end + default_scope where(:published => true) end -- cgit v1.2.3 From 019cd51a3f36ec7631bf1b63c069e62a3b5185d4 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 18 Apr 2011 23:35:22 +0100 Subject: Bring back support for passing a callable object to the default_scope macro. You can also just use a block. --- activerecord/CHANGELOG | 11 +++++++++++ activerecord/lib/active_record/base.rb | 12 ++++++++++++ activerecord/test/cases/relation_scoping_test.rb | 12 ++++++++++++ activerecord/test/models/developer.rb | 17 +++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4ae82a6419..9ff29f1155 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,16 @@ *Rails 3.1.0 (unreleased)* +* default_scope can take a block, lambda, or any other object which responds to `call` for lazy + evaluation: + + default_scope { ... } + default_scope lambda { ... } + default_scope method(:foo) + + This feature was originally implemented by Tim Morgan, but was then removed in favour of + defining a 'default_scope' class method, but has now been added back in by Jon Leighton. + The relevant lighthouse ticket is #1812. + * Default scopes are now evaluated at the latest possible moment, to avoid problems where scopes would be created which would implicitly contain the default scope, which would then be impossible to get rid of via Model.unscoped. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5b316c17be..9a01d793f9 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1196,6 +1196,15 @@ MSG # Article.new.published # => true # Article.create.published # => true # + # You can also use default_scope with a block, in order to have it lazily evaluated: + # + # class Article < ActiveRecord::Base + # default_scope { where(:published_at => Time.now - 1.week) } + # end + # + # (You can also pass any object which responds to call to the default_scope + # macro, and it will be called when building the default scope.) + # # If you need to do more complex things with a default scope, you can alternatively # define it as a class method: # @@ -1233,6 +1242,7 @@ end WARN end + scope = Proc.new if block_given? self.default_scopes = default_scopes.dup << scope end @@ -1245,6 +1255,8 @@ end default_scopes.inject(relation) do |default_scope, scope| if scope.is_a?(Hash) default_scope.apply_finder_options(scope) + elsif !scope.is_a?(Relation) && scope.respond_to?(:call) + default_scope.merge(scope.call) else default_scope.merge(scope) end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index e231371cf8..2ed676fe69 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -312,6 +312,18 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all end + def test_default_scope_with_lambda + assert_equal [developers(:david).becomes(LazyLambdaDeveloperCalledDavid)], LazyLambdaDeveloperCalledDavid.all + end + + def test_default_scope_with_block + assert_equal [developers(:david).becomes(LazyBlockDeveloperCalledDavid)], LazyBlockDeveloperCalledDavid.all + end + + def test_default_scope_with_callable + assert_equal [developers(:david).becomes(CallableDeveloperCalledDavid)], CallableDeveloperCalledDavid.all + end + def test_default_scope_is_unscoped_on_find assert_equal 1, DeveloperCalledDavid.count assert_equal 11, DeveloperCalledDavid.unscoped.count diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 93363f47c5..10701dd6fd 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -1,3 +1,5 @@ +require 'ostruct' + module DeveloperProjectsAssociationExtension def find_most_recent find(:first, :order => "id DESC") @@ -102,6 +104,21 @@ class DeveloperCalledDavid < ActiveRecord::Base default_scope where("name = 'David'") end +class LazyLambdaDeveloperCalledDavid < ActiveRecord::Base + self.table_name = 'developers' + default_scope lambda { where(:name => 'David') } +end + +class LazyBlockDeveloperCalledDavid < ActiveRecord::Base + self.table_name = 'developers' + default_scope { where(:name => 'David') } +end + +class CallableDeveloperCalledDavid < ActiveRecord::Base + self.table_name = 'developers' + default_scope OpenStruct.new(:call => where(:name => 'David')) +end + class ClassMethodDeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' -- cgit v1.2.3 From 082e299e978f705e6ceed747ead3d71ccba36ed4 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 18 Apr 2011 23:37:27 -0300 Subject: docs for ActionMailer::Base.default_i18n_subject --- actionmailer/lib/action_mailer/base.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index cd76383931..b5d62ad14e 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -681,7 +681,10 @@ module ActionMailer #:nodoc: end end - def default_i18n_subject #:nodoc: + # Translates the +subject+ using Rails I18n class under [:actionmailer, mailer_scope, action_name] scope. + # If it does not find a translation for the +subject+ under the specified scope it will default to a + # humanized version of the action_name. + def default_i18n_subject mailer_scope = self.class.mailer_name.gsub('/', '.') I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize) end -- cgit v1.2.3 From 6380f1a9f45e68f38480c0805cac62eb6708f72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 10:34:17 +0200 Subject: Be sure to not store the closed flash in the session. --- actionpack/lib/action_dispatch/middleware/flash.rb | 41 +++++++++++++--------- actionpack/test/controller/flash_test.rb | 9 ++++- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 027ff7f8ac..414405cc9e 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -4,7 +4,7 @@ module ActionDispatch # read a notice you put there or flash["notice"] = "hello" # to put a new one. def flash - @env['action_dispatch.request.flash_hash'] ||= (session["flash"] || Flash::FlashHash.new) + @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new) end end @@ -40,18 +40,14 @@ module ActionDispatch # # See docs on the FlashHash class for more details about the flash. class Flash + KEY = 'action_dispatch.request.flash_hash'.freeze + class FlashNow #:nodoc: def initialize(flash) @flash = flash - @closed = false end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true end - def []=(k, v) - raise ClosedError, :flash if closed? @flash[k] = v @flash.discard(k) v @@ -70,6 +66,10 @@ module ActionDispatch def notice=(message) self[:notice] = message end + + def close!(new_flash) + @flash = new_flash + end end class FlashHash @@ -81,10 +81,6 @@ module ActionDispatch @flashes = {} end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true end - def []=(k, v) #:nodoc: raise ClosedError, :flash if closed? keep(k) @@ -152,6 +148,14 @@ module ActionDispatch @now ||= FlashNow.new(self) end + attr_reader :closed + alias :closed? :closed + + def close! + @closed = true + @now.close!(self) if @now + end + # Keeps either the entire current flash or a specific flash entry available for the next action: # # flash.keep # keeps the entire flash @@ -231,13 +235,18 @@ module ActionDispatch @app.call(env) ensure session = env['rack.session'] || {} - flash_hash = env['action_dispatch.request.flash_hash'] + flash_hash = env[KEY] if flash_hash - if !flash_hash.empty? || session.key?('flash') - session["flash"] = flash_hash - end - flash_hash.close! + if !flash_hash.empty? || session.key?('flash') + session["flash"] = flash_hash + new_hash = flash_hash.dup + else + new_hash = flash_hash + end + + env[KEY] = new_hash + new_hash.close! end if session.key?('flash') && session['flash'].empty? diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 9c89f1334d..7b5bf8b21a 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -264,6 +264,14 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_setting_flash_does_not_raise_in_following_requests + with_test_route_set do + env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } + get '/set_flash', nil, env + get '/set_flash', nil, env + end + end + def test_setting_flash_raises_after_stream_back_to_client_even_with_an_empty_flash with_test_route_set do env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } @@ -294,7 +302,6 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end - private # Overwrite get to send SessionSecret in env hash -- cgit v1.2.3 From 66a2ffcdb78c74b1d68cd75331d3214b10a8731d Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 19 Apr 2011 11:24:09 +0200 Subject: Update link to Rails 2.3 guides from 2.3.8 to last release 2.3.11. Maybe someone could regenerate the guides --- railties/guides/source/index.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb index af46beee56..b48488d8a2 100644 --- a/railties/guides/source/index.html.erb +++ b/railties/guides/source/index.html.erb @@ -17,7 +17,7 @@ Ruby on Rails Guides <% else %>

These are the new guides for Rails 3. The guides for Rails 2.3 are still available - at http://guides.rubyonrails.org/v2.3.8/. + at http://guides.rubyonrails.org/v2.3.11/.

<% end %>

-- cgit v1.2.3 From a66c91723565d37969de4cb46baa50fb8865b02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 11:54:12 +0200 Subject: Do not inherit from Rack::Response, remove a shit-ton of unused code. --- actionpack/lib/abstract_controller/rendering.rb | 8 +- .../lib/action_controller/metal/rendering.rb | 11 +++ actionpack/lib/action_dispatch/http/response.rb | 101 ++++++++++----------- actionpack/lib/action_dispatch/middleware/flash.rb | 1 + actionpack/test/dispatch/response_test.rb | 16 ---- 5 files changed, 61 insertions(+), 76 deletions(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index d0dd730b06..306bd41e2d 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -114,13 +114,7 @@ module AbstractController # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) - if self.response_body = render_to_body(options) - string = "" - response_body.each { |r| string << r } - string - end - ensure - self.response_body = nil + render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 32d52c84c4..70fd79bb8b 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -18,6 +18,17 @@ module ActionController response_body end + # Overwrite render_to_string because body can now be set to a rack body. + def render_to_string(*) + if self.response_body = super + string = "" + response_body.each { |r| string << r } + string + end + ensure + self.response_body = nil + end + private # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 8e03a7879f..78ecf177be 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,24 +32,35 @@ module ActionDispatch # :nodoc: # puts @response.body # end # end - class Response < Rack::Response - attr_accessor :request, :blank + class Response + attr_accessor :request, :header, :status + attr_writer :sending_file - attr_writer :header, :sending_file alias_method :headers=, :header= + alias_method :headers, :header + + delegate :[], :[]=, :to => :@header + delegate :each, :to => :@body + + # Sets the HTTP response's content MIME type. For example, in the controller + # you could write this: + # + # response.content_type = "text/plain" + # + # If a character set has been defined for this response (see charset=) then + # the character set information will also be included in the content type + # information. + attr_accessor :charset, :content_type + + CONTENT_TYPE = "Content-Type" + + cattr_accessor(:default_charset) { "utf-8" } module Setup def initialize(status = 200, header = {}, body = []) - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 - - @header = header - self.body, self.status = body, status + self.body, self.header, self.status = body, header, status - @cookie = [] @sending_file = false - @blank = false if content_type = self["Content-Type"] @@ -62,6 +73,7 @@ module ActionDispatch # :nodoc: end end + include Rack::Response::Helpers include Setup include ActionDispatch::Http::Cache::Response @@ -106,13 +118,21 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:to_str) ? [body] : body + @body = body.respond_to?(:each) ? body : [body] end def body_parts @body end + def set_cookie(key, value) + ::Rack::Utils.set_cookie_header!(header, key, value) + end + + def delete_cookie(key, value={}) + ::Rack::Utils.delete_cookie_header!(header, key, value) + end + def location headers['Location'] end @@ -122,46 +142,21 @@ module ActionDispatch # :nodoc: headers['Location'] = url end - # Sets the HTTP response's content MIME type. For example, in the controller - # you could write this: - # - # response.content_type = "text/plain" - # - # If a character set has been defined for this response (see charset=) then - # the character set information will also be included in the content type - # information. - attr_accessor :charset, :content_type - - CONTENT_TYPE = "Content-Type" - - cattr_accessor(:default_charset) { "utf-8" } - def to_a assign_default_content_type_and_charset! handle_conditional_get! - self["Set-Cookie"] = self["Set-Cookie"].join("\n") if self["Set-Cookie"].respond_to?(:join) - super - end - alias prepare! to_a + @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join) - def each(&callback) - if @body.respond_to?(:call) - @writer = lambda { |x| callback.call(x) } - @body.call(self, self) + if [204, 304].include?(@status) + @header.delete "Content-Type" + [@status, @header, []] else - @body.each { |part| callback.call(part.to_s) } + [@status, @header, self] end - - @writer = callback - @block.call(self) if @block - end - - def write(str) - str = str.to_s - @writer.call str - str end + alias prepare! to_a + alias to_ary to_a # For implicit splat on 1.9.2 # Returns the response cookies, converted to a Hash of (name => value) pairs # @@ -180,18 +175,18 @@ module ActionDispatch # :nodoc: cookies end - private - def assign_default_content_type_and_charset! - return if headers[CONTENT_TYPE].present? + private - @content_type ||= Mime::HTML - @charset ||= self.class.default_charset + def assign_default_content_type_and_charset! + return if headers[CONTENT_TYPE].present? - type = @content_type.to_s.dup - type << "; charset=#{@charset}" unless @sending_file + @content_type ||= Mime::HTML + @charset ||= self.class.default_charset - headers[CONTENT_TYPE] = type - end + type = @content_type.to_s.dup + type << "; charset=#{@charset}" unless @sending_file + headers[CONTENT_TYPE] = type + end end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 414405cc9e..735c72d34a 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -79,6 +79,7 @@ module ActionDispatch @used = Set.new @closed = false @flashes = {} + @now = nil end def []=(k, v) #:nodoc: diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 6f38714b2e..5abbaf74fe 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -33,22 +33,6 @@ class ResponseTest < ActiveSupport::TestCase }, headers) end - test "streaming block" do - @response.body = Proc.new do |response, output| - 5.times { |n| output.write(n) } - end - - status, headers, body = @response.to_a - assert_equal 200, status - assert_equal({ - "Content-Type" => "text/html; charset=utf-8" - }, headers) - - parts = [] - body.each { |part| parts << part.to_s } - assert_equal ["0", "1", "2", "3", "4"], parts - end - test "content type" do [204, 304].each do |c| @response.status = c.to_s -- cgit v1.2.3 From b398520c1406824efd12df6bb57996aa9781f876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 12:25:01 +0200 Subject: Output a redirect to the 500 page if something happens when streaming. Currently, we output: "> --- actionpack/lib/action_view/base.rb | 6 ++++++ .../renderer/streaming_template_renderer.rb | 11 +++++----- .../controller/new_base/render_streaming_test.rb | 24 +++++++++++++++++++++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 9e8a3c51a3..87501d5b88 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -137,6 +137,12 @@ module ActionView #:nodoc: cattr_accessor :field_error_proc @@field_error_proc = Proc.new{ |html_tag, instance| "

#{html_tag}
".html_safe } + # How to complete the streaming when an exception occurs. + # This is our best guess: first try to close the attribute, then the tag. + # Currently this is private API and may be changed at *any* time. + cattr_accessor :streaming_completion_on_exception + @@streaming_completion_on_exception = %(">) + class_attribute :helpers class_attribute :_routes diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index 52f0e9f5bd..03aab444f8 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -46,11 +46,8 @@ module ActionView # # == TODO # - # * Add streaming support in the controllers with no-cache settings - # * What should happen when an error happens? # * Support streaming from child templates, partials and so on. - # * Support on sprockets async JS load? - # + # * Integrate exceptions with exceptron class StreamingTemplateRenderer < TemplateRenderer #:nodoc: # A valid Rack::Body (i.e. it responds to each). # It is initialized with a block that, when called, starts @@ -61,7 +58,11 @@ module ActionView end def each(&block) - @start.call(block) + begin + @start.call(block) + rescue + block.call ActionView::Base.streaming_completion_on_exception + end self end end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index ffc4b331ec..b8df5ec4e8 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -4,7 +4,9 @@ module RenderStreaming class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( "render_streaming/basic/hello_world.html.erb" => "Hello world", - "layouts/application.html.erb" => "<%= yield %>, I'm here!" + "render_streaming/basic/boom.html.erb" => "<%= nil.invalid! %>", + "layouts/application.html.erb" => "<%= yield %>, I'm here!", + "layouts/boom.html.erb" => "\"<%= yield %>" )] layout "application" @@ -13,6 +15,14 @@ module RenderStreaming def hello_world end + def layout_exception + render :action => "hello_world", :stream => true, :layout => "boom" + end + + def template_exception + render :action => "boom", :stream => true + end + def skip render :action => "hello_world", :stream => false end @@ -61,6 +71,18 @@ module RenderStreaming assert_body "Hello world, I'm here!" end + test "rendering with layout exception" do + get "/render_streaming/basic/layout_exception" + assert_body "d\r\n\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with template exception" do + get "/render_streaming/basic/template_exception" + assert_body "4e\r\n\">\r\n0\r\n\r\n" + assert_streaming! + end + def assert_streaming!(cache="no-cache") assert_status 200 assert_equal nil, headers["Content-Length"] -- cgit v1.2.3 From 069e9b004f91c4ace1373ab5203bb00ab41bd1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 15:04:28 +0200 Subject: Do not stream on HTTP/1.0. --- actionpack/lib/action_controller/metal/streaming.rb | 10 +++++++--- actionpack/test/controller/new_base/render_streaming_test.rb | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index adb3e94134..b9bd49f670 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -38,9 +38,13 @@ module ActionController #:nodoc: def _process_options(options) #:nodoc: super if options[:stream] - headers["Cache-Control"] ||= "no-cache" - headers["Transfer-Encoding"] = "chunked" - headers.delete("Content-Length") + if env["HTTP_VERSION"] == "HTTP/1.0" + options.delete(:stream) + else + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") + end end end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index b8df5ec4e8..fed8d40b47 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -83,6 +83,14 @@ module RenderStreaming assert_streaming! end + test "do not stream on HTTP/1.0" do + get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" + assert_body "Hello world, I'm here!" + assert_status 200 + assert_equal "22", headers["Content-Length"] + assert_equal nil, headers["Transfer-Encoding"] + end + def assert_streaming!(cache="no-cache") assert_status 200 assert_equal nil, headers["Content-Length"] -- cgit v1.2.3 From b24621809ebd4c69796b5ca6b41e7720bc52228a Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 19 Apr 2011 16:34:34 +0200 Subject: remove MultiJson from the Gemfile and instead add the current rc to the AS gemspec --- Gemfile | 2 -- activesupport/activesupport.gemspec | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0b6de3061f..c720b09a2c 100644 --- a/Gemfile +++ b/Gemfile @@ -15,8 +15,6 @@ gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" gem "sass", ">= 3.0" -gem "multi_json", :git => 'git://github.com/intridea/multi_json.git' - gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index eaecb30090..968d6ff4d0 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -16,4 +16,6 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' + + s.add_dependency('multi_json', '~> 1.0.0.rc3') end -- cgit v1.2.3 From 30472d4244f08ad861590fb41dd75efb5f274422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 17:56:46 +0200 Subject: Avoid define_method if possible. --- activemodel/lib/active_model/attribute_methods.rb | 51 +++++++++++++++++----- .../lib/active_record/attribute_methods/read.rb | 2 +- .../lib/active_record/attribute_methods/write.rb | 2 +- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index be55581c66..137657bd29 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -56,6 +56,8 @@ module ActiveModel module AttributeMethods extend ActiveSupport::Concern + COMPILABLE_REGEXP = /^[a-zA-Z_]\w*[!?=]?$/ + included do class_attribute :attribute_method_matchers, :instance_writer => false self.attribute_method_matchers = [] @@ -106,13 +108,16 @@ module ActiveModel if block_given? sing.send :define_method, name, &block else - if name =~ /^[a-zA-Z_]\w*[!?=]?$/ - sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 - def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end - eorb + # If we can compile the method name, do it. Otherwise use define_method. + # This is an important *optimization*, please don't change it. define_method + # has slower dispatch and consumes more memory. + if name =~ COMPILABLE_REGEXP + sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end + RUBY else value = value.to_s if value - sing.send(:define_method, name) { value } + sing.send(:define_method, name) { value && value.dup } end end end @@ -232,8 +237,19 @@ module ActiveModel def alias_attribute(new_name, old_name) attribute_method_matchers.each do |matcher| - define_method(matcher.method_name(new_name)) do |*args| - send(matcher.method_name(old_name), *args) + matcher_new = matcher.method_name(new_name).to_s + matcher_old = matcher.method_name(old_name).to_s + + if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{matcher_new}(*args) + send(:#{matcher_old}, *args) + end + RUBY + else + define_method(matcher_new) do |*args| + send(matcher_old, *args) + end end end end @@ -276,14 +292,25 @@ module ActiveModel else method_name = matcher.method_name(attr_name) - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 if method_defined?('#{method_name}') undef :'#{method_name}' end - define_method('#{method_name}') do |*args| - send('#{matcher.method_missing_target}', '#{attr_name}', *args) - end - STR + RUBY + + if method_name.to_s =~ COMPILABLE_REGEXP + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method_name}(*args) + send(:#{matcher.method_missing_target}, '#{attr_name}', *args) + end + RUBY + else + generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + define_method('#{method_name}') do |*args| + send('#{matcher.method_missing_target}', '#{attr_name}', *args) + end + RUBY + end end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index a248eb3a7b..aef99e3129 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -79,7 +79,7 @@ module ActiveRecord # # The second, slower, branch is necessary to support instances where the database # returns columns with extra stuff in (like 'my_column(omg)'). - if method_name =~ /^[a-zA-Z_]\w*[!?=]?$/ + if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ def _#{method_name} #{access_code} diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 7661676f8c..c77a3ac145 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord module ClassMethods protected def define_method_attribute=(attr_name) - if attr_name =~ /^[a-zA-Z_]\w*[!?=]?$/ + if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) else generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value| -- cgit v1.2.3 From dca31b9224bd9feb23208d668a77d85ce062cf93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 18:06:01 +0200 Subject: Let's not dup because value may not always be duplicable. --- activemodel/lib/active_model/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 137657bd29..3dc6a68e44 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -117,7 +117,7 @@ module ActiveModel RUBY else value = value.to_s if value - sing.send(:define_method, name) { value && value.dup } + sing.send(:define_method, name) { value } end end end -- cgit v1.2.3 From a3a5c7eba39c64413abd0fb4766282c9f071d248 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 18:07:14 +0200 Subject: All assets, including images, audio, and video, now uses the asset pipeline when its on --- .../lib/action_view/helpers/asset_tag_helper.rb | 18 +++++++++++++++--- .../lib/action_view/helpers/sprockets_helper.rb | 15 ++++++++++----- .../test/fixtures/sprockets/app/images/logo.png | Bin 0 -> 6646 bytes actionpack/test/template/sprockets_helper_test.rb | 19 +++++++++++++++++++ 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 actionpack/test/fixtures/sprockets/app/images/logo.png diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index f6b2d4f3f4..10bdede1b4 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -274,7 +274,11 @@ module ActionView # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source) - asset_paths.compute_public_path(source, 'images') + if config.use_sprockets + sprockets_asset_path(source) + else + asset_paths.compute_public_path(source, 'images') + end end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route @@ -289,7 +293,11 @@ module ActionView # video_path("/trailers/hd.avi") # => /trailers/hd.avi # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi def video_path(source) - asset_paths.compute_public_path(source, 'videos') + if config.use_sprockets + sprockets_asset_path(source) + else + asset_paths.compute_public_path(source, 'videos') + end end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route @@ -304,7 +312,11 @@ module ActionView # audio_path("/sounds/horse.wav") # => /sounds/horse.wav # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav def audio_path(source) - asset_paths.compute_public_path(source, 'audios') + if config.use_sprockets + sprockets_asset_path(source) + else + asset_paths.compute_public_path(source, 'audios') + end end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index 408a2030ab..fee13be886 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -3,8 +3,12 @@ require 'uri' module ActionView module Helpers module SprocketsHelper + def sprockets_asset_path(source, default_ext = nil) + compute_sprockets_path(source, 'assets', default_ext) + end + def sprockets_javascript_path(source) - compute_sprockets_path source, 'assets', 'js' + sprockets_asset_path(source, 'js') end def sprockets_javascript_include_tag(source, options = {}) @@ -17,9 +21,9 @@ module ActionView end def sprockets_stylesheet_path(source) - compute_sprockets_path source, 'assets', 'css' + sprockets_asset_path(source, 'css') end - + def sprockets_stylesheet_link_tag(source, options = {}) options = { 'rel' => "stylesheet", @@ -31,13 +35,14 @@ module ActionView tag 'link', options end + private - def compute_sprockets_path(source, dir, default_ext) + def compute_sprockets_path(source, dir, default_ext = nil) source = source.to_s return source if URI.parse(source).host - # Add /javscripts to relative paths + # Add /assets to relative paths if source[0] != ?/ source = "/#{dir}/#{source}" end diff --git a/actionpack/test/fixtures/sprockets/app/images/logo.png b/actionpack/test/fixtures/sprockets/app/images/logo.png new file mode 100644 index 0000000000..d5edc04e65 Binary files /dev/null and b/actionpack/test/fixtures/sprockets/app/images/logo.png differ diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index 67aee86d02..67774c1893 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -22,6 +22,7 @@ class SprocketsHelperTest < ActionView::TestCase @assets = Sprockets::Environment.new @assets.paths << FIXTURES.join("sprockets/app/javascripts") @assets.paths << FIXTURES.join("sprockets/app/stylesheets") + @assets.paths << FIXTURES.join("sprockets/app/images") config.perform_caching = true end @@ -30,6 +31,24 @@ class SprocketsHelperTest < ActionView::TestCase "http://www.example.com" end + test "asset path" do + assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", + sprockets_asset_path("logo.png") + + assert_equal "/images/logo", + sprockets_asset_path("/images/logo") + assert_equal "/images/logo.gif", + sprockets_asset_path("/images/logo.gif") + + assert_equal "/dir/audio", + sprockets_asset_path("/dir/audio") + + assert_equal "http://www.example.com/video/play", + sprockets_asset_path("http://www.example.com/video/play") + assert_equal "http://www.example.com/video/play.mp4", + sprockets_asset_path("http://www.example.com/video/play.mp4") + end + test "javascript path" do assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js", sprockets_javascript_path(:application) -- cgit v1.2.3 From 9b423ac0b73483c4a812d7376f818e831f909120 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 18:11:42 +0200 Subject: let this regexp be stricter The purpose of this regexp is to detect strings that are valid identifiers to be used as method names. The anchor $ allows a trailing newline, and both ^ and $ may be bypassed because Ruby regexps are multiline always. The anchors \A and \z are the proper ones to ensure string boundaries. --- activemodel/lib/active_model/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 3dc6a68e44..6ee5e04267 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -56,7 +56,7 @@ module ActiveModel module AttributeMethods extend ActiveSupport::Concern - COMPILABLE_REGEXP = /^[a-zA-Z_]\w*[!?=]?$/ + COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ included do class_attribute :attribute_method_matchers, :instance_writer => false -- cgit v1.2.3 From d35c91225e8eea967358328ba618e6608222a615 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 18:29:18 +0200 Subject: Cleanup compute_sprockets_path -- when you are reaching for code comments, the method could be simpler --- .../lib/action_view/helpers/sprockets_helper.rb | 51 +++++++++++++--------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index fee13be886..d2a31c02d4 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -11,6 +11,11 @@ module ActionView sprockets_asset_path(source, 'js') end + def sprockets_stylesheet_path(source) + sprockets_asset_path(source, 'css') + end + + def sprockets_javascript_include_tag(source, options = {}) options = { 'type' => "text/javascript", @@ -20,10 +25,6 @@ module ActionView content_tag 'script', "", options end - def sprockets_stylesheet_path(source) - sprockets_asset_path(source, 'css') - end - def sprockets_stylesheet_link_tag(source, options = {}) options = { 'rel' => "stylesheet", @@ -40,30 +41,38 @@ module ActionView def compute_sprockets_path(source, dir, default_ext = nil) source = source.to_s - return source if URI.parse(source).host - - # Add /assets to relative paths - if source[0] != ?/ - source = "/#{dir}/#{source}" + unless source_is_a_url?(source) + add_asset_directory(source, dir) + add_default_extension(source, default_ext) + add_fingerprint(source, dir) + add_asset_host(source) end - # Add default extension if there isn't one - if default_ext && File.extname(source).empty? - source = "#{source}.#{default_ext}" - end - - # Fingerprint url - if source =~ /^\/#{dir}\/(.+)/ - source = assets.path($1, config.perform_caching, dir) - end + source + end + + def add_asset_directory(source, dir) + source.replace("/#{dir}/#{source}") if source[0] != ?/ + end + + def add_default_extension(source, default_ext) + source.replace("#{source}.#{default_ext}") if default_ext && File.extname(source).empty? + end + + def add_fingerprint(source, dir) + source.replace(assets.path($1, config.perform_caching, dir)) if source =~ /^\/#{dir}\/(.+)/ + end + def add_asset_host(source) host = compute_asset_host(source) if controller.respond_to?(:request) && host && URI.parse(host).host - source = "#{controller.request.protocol}#{host}#{source}" + source.replace("#{controller.request.protocol}#{host}#{source}") end - - source + end + + def source_is_a_url?(source) + URI.parse(source).host.present? end def compute_asset_host(source) -- cgit v1.2.3 From 626bcc9bf415ca9872bbdaceac30a4df9aca86bb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 19:05:07 +0200 Subject: Switch to asset_path and make it available in the Sprockets::Context (now you can do asset_path("logo.png") in a stylesheet.css.erb file and get fingerprinting) --- .../lib/action_view/helpers/asset_tag_helper.rb | 6 ++-- .../asset_tag_helpers/javascript_tag_helpers.rb | 2 +- .../asset_tag_helpers/stylesheet_tag_helpers.rb | 2 +- .../lib/action_view/helpers/sprockets_helper.rb | 32 ++++++++++------- actionpack/test/template/sprockets_helper_test.rb | 40 ++++++++++------------ 5 files changed, 42 insertions(+), 40 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 10bdede1b4..e859b3ae49 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -275,7 +275,7 @@ module ActionView # plugin authors are encouraged to do so. def image_path(source) if config.use_sprockets - sprockets_asset_path(source) + asset_path(source) else asset_paths.compute_public_path(source, 'images') end @@ -294,7 +294,7 @@ module ActionView # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi def video_path(source) if config.use_sprockets - sprockets_asset_path(source) + asset_path(source) else asset_paths.compute_public_path(source, 'videos') end @@ -313,7 +313,7 @@ module ActionView # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav def audio_path(source) if config.use_sprockets - sprockets_asset_path(source) + asset_path(source) else asset_paths.compute_public_path(source, 'audios') end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index ce5a7dc2e5..a0f6fb5692 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -87,7 +87,7 @@ module ActionView # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js def javascript_path(source) if config.use_sprockets - sprockets_javascript_path(source) + asset_path(source, 'js') else asset_paths.compute_public_path(source, 'javascripts', 'js') end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index a994afb65e..309762ee05 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -64,7 +64,7 @@ module ActionView # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css def stylesheet_path(source) if config.use_sprockets - sprockets_stylesheet_path(source) + asset_path(source, 'css') else asset_paths.compute_public_path(source, 'stylesheets', 'css') end diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index d2a31c02d4..bf47af6e10 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -3,23 +3,14 @@ require 'uri' module ActionView module Helpers module SprocketsHelper - def sprockets_asset_path(source, default_ext = nil) + def asset_path(source, default_ext = nil) compute_sprockets_path(source, 'assets', default_ext) end - def sprockets_javascript_path(source) - sprockets_asset_path(source, 'js') - end - - def sprockets_stylesheet_path(source) - sprockets_asset_path(source, 'css') - end - - def sprockets_javascript_include_tag(source, options = {}) options = { 'type' => "text/javascript", - 'src' => sprockets_javascript_path(source) + 'src' => asset_path(source, 'js') }.merge(options.stringify_keys) content_tag 'script', "", options @@ -30,7 +21,7 @@ module ActionView 'rel' => "stylesheet", 'type' => "text/css", 'media' => "screen", - 'href' => sprockets_stylesheet_path(source) + 'href' => asset_path(source, 'css') }.merge(options.stringify_keys) tag 'link', options @@ -60,10 +51,15 @@ module ActionView end def add_fingerprint(source, dir) - source.replace(assets.path($1, config.perform_caching, dir)) if source =~ /^\/#{dir}\/(.+)/ + if source =~ /^\/#{dir}\/(.+)/ + source.replace(assets.path($1, performing_caching?, dir)) + end end def add_asset_host(source) + # When included in Sprockets::Context, there's no controller + return unless respond_to?(:controller) + host = compute_asset_host(source) if controller.respond_to?(:request) && host && URI.parse(host).host @@ -94,6 +90,16 @@ module ActionView def assets Rails.application.assets end + + def performing_caching? + # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available + respond_to?(:config) ? config.perform_caching : Rails.application.config.action_controller.perform_caching + end end end end + +# FIXME: Temp hack for extending Sprockets::Context so +class Sprockets::Context + include ActionView::Helpers::SprocketsHelper +end if defined?(Sprockets) \ No newline at end of file diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index 67774c1893..863e473697 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -33,38 +33,38 @@ class SprocketsHelperTest < ActionView::TestCase test "asset path" do assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", - sprockets_asset_path("logo.png") + asset_path("logo.png") assert_equal "/images/logo", - sprockets_asset_path("/images/logo") + asset_path("/images/logo") assert_equal "/images/logo.gif", - sprockets_asset_path("/images/logo.gif") + asset_path("/images/logo.gif") assert_equal "/dir/audio", - sprockets_asset_path("/dir/audio") + asset_path("/dir/audio") assert_equal "http://www.example.com/video/play", - sprockets_asset_path("http://www.example.com/video/play") + asset_path("http://www.example.com/video/play") assert_equal "http://www.example.com/video/play.mp4", - sprockets_asset_path("http://www.example.com/video/play.mp4") + asset_path("http://www.example.com/video/play.mp4") end test "javascript path" do assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path(:application) + asset_path(:application, "js") assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path("xmlhr") + asset_path("xmlhr", "js") assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path("dir/xmlhr.js") + asset_path("dir/xmlhr.js", "js") assert_equal "/dir/xmlhr.js", - sprockets_javascript_path("/dir/xmlhr") + asset_path("/dir/xmlhr", "js") assert_equal "http://www.railsapplication.com/js/xmlhr", - sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr") + asset_path("http://www.railsapplication.com/js/xmlhr", "js") assert_equal "http://www.railsapplication.com/js/xmlhr.js", - sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr.js") + asset_path("http://www.railsapplication.com/js/xmlhr.js", "js") end test "javascript include tag" do @@ -80,20 +80,16 @@ class SprocketsHelperTest < ActionView::TestCase end test "stylesheet path" do - assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path(:application) + assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", asset_path(:application, "css") - assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path("style") - assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path("dir/style.css") - assert_equal "/dir/style.css", - sprockets_stylesheet_path("/dir/style.css") + assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css") + assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css") + assert_equal "/dir/style.css", asset_path("/dir/style.css", "css") assert_equal "http://www.railsapplication.com/css/style", - sprockets_stylesheet_path("http://www.railsapplication.com/css/style") + asset_path("http://www.railsapplication.com/css/style", "css") assert_equal "http://www.railsapplication.com/css/style.css", - sprockets_stylesheet_path("http://www.railsapplication.com/css/style.css") + asset_path("http://www.railsapplication.com/css/style.css", "css") end test "stylesheet link tag" do -- cgit v1.2.3 From 1e56c8955937f3a06b2bef3a6d132ea8faaf434c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 20:06:09 +0200 Subject: Give assets access to asset_path during precompilation --- railties/lib/rails/tasks/assets.rake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake index 9ed697f142..158df31749 100644 --- a/railties/lib/rails/tasks/assets.rake +++ b/railties/lib/rails/tasks/assets.rake @@ -1,6 +1,9 @@ namespace :assets do desc "Compile all the assets named in config.assets.precompile" task :precompile => :environment do + # Give assets access to asset_path + ActionView::Helpers::SprocketsHelper + assets = Rails.application.config.assets.precompile Rails.application.assets.precompile(*assets) end -- cgit v1.2.3 From e6850136d6f7055f9186d59611080a5028f4bbcb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 20:33:56 +0200 Subject: Set a default precompile target to be the application.js, application.css, and all non-css/js files (images etc) --- railties/lib/rails/application/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index e5476fbe7a..bd8c314da6 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -33,7 +33,7 @@ module Rails @assets = ActiveSupport::OrderedOptions.new @assets.enabled = false @assets.paths = [] - @assets.precompile = [] + @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ] @assets.prefix = "/assets" end -- cgit v1.2.3 From 9aac54c4bff67adac8a8b5ef4ff5e5564fb237b1 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 20:35:53 +0200 Subject: Add note about the option to add other precompile targets --- .../rails/app/templates/config/environments/production.rb.tt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 874cc403ba..80951cf73a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -30,6 +30,9 @@ # Enable serving of images, stylesheets, and javascripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # config.assets.precompile += %w( search.js ) + # Disable delivery errors, bad email addresses will be ignored # config.action_mailer.raise_delivery_errors = false -- cgit v1.2.3 From c96bf840ad715cb203f29e1607929d59c3743d87 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 19 Apr 2011 20:38:38 +0200 Subject: Add app/assets/images and include the rails.png as a sample there --- railties/lib/rails/generators/rails/app/app_generator.rb | 8 -------- .../rails/app/templates/app/assets/images/rails.png | Bin 0 -> 6646 bytes .../rails/app/templates/public/images/rails.png | Bin 6646 -> 0 bytes .../generators/rails/app/templates/public/index.html | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png delete mode 100644 railties/lib/rails/generators/rails/app/templates/public/images/rails.png diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 4df68d67c7..bc55efa261 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -89,10 +89,6 @@ module Rails directory "public", "public", :recursive => false end - def images - directory "public/images" - end - def script directory "script" do |content| "#{shebang}\n" + content @@ -219,10 +215,6 @@ module Rails build(:public_directory) end - def create_public_image_files - build(:images) - end - def create_script_files build(:script) end diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png new file mode 100644 index 0000000000..d5edc04e65 Binary files /dev/null and b/railties/lib/rails/generators/rails/app/templates/app/assets/images/rails.png differ diff --git a/railties/lib/rails/generators/rails/app/templates/public/images/rails.png b/railties/lib/rails/generators/rails/app/templates/public/images/rails.png deleted file mode 100644 index d5edc04e65..0000000000 Binary files a/railties/lib/rails/generators/rails/app/templates/public/images/rails.png and /dev/null differ diff --git a/railties/lib/rails/generators/rails/app/templates/public/index.html b/railties/lib/rails/generators/rails/app/templates/public/index.html index 13a203dd08..9d9811a5bf 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/index.html +++ b/railties/lib/rails/generators/rails/app/templates/public/index.html @@ -59,7 +59,7 @@ #header { - background-image: url("images/rails.png"); + background-image: url("/assets/rails.png"); background-repeat: no-repeat; background-position: top left; height: 64px; -- cgit v1.2.3 From a1639ad7524127be0a9e9398dc2ccc8c6629adee Mon Sep 17 00:00:00 2001 From: Florent Piteau Date: Tue, 19 Apr 2011 20:44:20 +0200 Subject: Undocumented :openssl_verify_mode option for smtp_settings --- actionmailer/lib/action_mailer/base.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index b5d62ad14e..bf7e50596a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -296,6 +296,10 @@ module ActionMailer #:nodoc: # information and a cryptographic Message Digest 5 algorithm to hash important information) # * :enable_starttls_auto - When set to true, detects if STARTTLS is enabled in your SMTP server # and starts to use it. + # * :openssl_verify_mode - When using TLS, you can set how OpenSSL checks the certificate. This is + # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name + # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the + # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...). # # * sendmail_settings - Allows you to override options for the :sendmail delivery method. # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. -- cgit v1.2.3 From 3265516c50abe2b6946fc724fa50c1cf522126d3 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 20:48:39 +0200 Subject: brings a #:nodoc: back --- actionmailer/lib/action_mailer/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index bf7e50596a..06de43b76f 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -688,7 +688,7 @@ module ActionMailer #:nodoc: # Translates the +subject+ using Rails I18n class under [:actionmailer, mailer_scope, action_name] scope. # If it does not find a translation for the +subject+ under the specified scope it will default to a # humanized version of the action_name. - def default_i18n_subject + def default_i18n_subject #:nodoc: mailer_scope = self.class.mailer_name.gsub('/', '.') I18n.t(:subject, :scope => [mailer_scope, action_name], :default => action_name.humanize) end -- cgit v1.2.3 From 571b4a2a91fcfb46166349c148326d38999a0d7d Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 20:51:24 +0200 Subject: Revert "Formated docs" Reason: "To be blank" and "to be empty" belongs to our everyday terminology, they go in regular font. This reverts commit 280a8709923d2bf707ec4d82ac1e5cb14941da3d. --- .../lib/active_support/core_ext/object/blank.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 6d14a6dce1..b92277f427 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,6 +1,6 @@ class Object - # An object is +blank+ if it's false, empty, or a whitespace string. - # For example, "", " ", +nil+, [], and {} are +blank+. + # An object is blank if it's false, empty, or a whitespace string. + # For example, "", " ", +nil+, [], and {} are blank. # # This simplifies: # @@ -18,7 +18,7 @@ class Object !blank? end - # Returns object if it's present? otherwise returns nil. + # Returns object if it's #present? otherwise returns nil. # object.presence is equivalent to object.present? ? object : nil. # # This is handy for any representation of objects where blank is the same @@ -38,7 +38,7 @@ class Object end class NilClass - # Instances of +NilClass+ are always +blank+. + # Instances of NilClass are always blank # # === Example # @@ -49,7 +49,7 @@ class NilClass end class FalseClass - # Instances of +FalseClass+ are always +blank+. + # Instances of FalseClass are always blank # # === Example # @@ -60,7 +60,7 @@ class FalseClass end class TrueClass - # Instances of +TrueClass+ are never +blank+. + # Instances of TrueClass are never blank # # === Example # @@ -71,7 +71,7 @@ class TrueClass end class Array - # An array is +blank+ if it's +empty+. + # An array is blank if it's empty # # === Examples # @@ -81,7 +81,7 @@ class Array end class Hash - # A hash is +blank+ if it's +empty+. + # A hash is blank if it's empty # # === Examples # @@ -91,7 +91,7 @@ class Hash end class String - # A string is +blank+ if it's empty or contains whitespaces only. + # A string is blank if it's empty or contains whitespaces only # # === Examples # -- cgit v1.2.3 From eaf0d1a491c836ec3c05417613272df423c83bb5 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 20:58:00 +0200 Subject: commit copy-edit: simplifies blank? rdoc and revises formatting --- .../lib/active_support/core_ext/object/blank.rb | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index b92277f427..20085c4fb3 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -18,8 +18,8 @@ class Object !blank? end - # Returns object if it's #present? otherwise returns nil. - # object.presence is equivalent to object.present? ? object : nil. + # Returns object if it's present? otherwise returns +nil+. + # object.presence is equivalent to object.present? ? object : nil. # # This is handy for any representation of objects where blank is the same # as not present at all. For example, this simplifies a common check for @@ -38,72 +38,71 @@ class Object end class NilClass - # Instances of NilClass are always blank + # +nil+ is blank: # - # === Example + # nil.blank? # => true # - # nil.blank? # => true def blank? true end end class FalseClass - # Instances of FalseClass are always blank + # +false+ is blank: # - # === Example + # false.blank? # => true # - # false.blank? # => true def blank? true end end class TrueClass - # Instances of TrueClass are never blank + # +true+ is not blank: # - # === Example + # true.blank? # => false # - # true.blank? # => false def blank? false end end class Array - # An array is blank if it's empty + # An array is blank if it's empty: # - # === Examples + # [].blank? # => true + # [1,2,3].blank? # => false # - # [].blank? # => true - # [1,2,3].blank? # => false alias_method :blank?, :empty? end class Hash - # A hash is blank if it's empty + # A hash is blank if it's empty: # - # === Examples + # {}.blank? # => true + # {:key => 'value'}.blank? # => false # - # {}.blank? # => true - # {:key => 'value'}.blank? # => false alias_method :blank?, :empty? end class String - # A string is blank if it's empty or contains whitespaces only + # A string is blank if it's empty or contains whitespaces only: # - # === Examples + # "".blank? # => true + # " ".blank? # => true + # " something here ".blank? # => false # - # "".blank? # => true - # " ".blank? # => true - # " something here ".blank? # => false def blank? self !~ /\S/ end end class Numeric #:nodoc: + # No number is blank: + # + # 1.blank? # => false + # 0.blank? # => false + # def blank? false end -- cgit v1.2.3 From 5fee98adf8ce515a98ddd7d7833ab9d730489bad Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 21:15:15 +0200 Subject: remove unwanted Example headers as per the guidelines, s/instaces of NilClass/nil/ and friends, completes some rdocs --- .../active_support/core_ext/object/duplicable.rb | 57 ++++++++++++++-------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 0f9129d0b6..02cb5dfee7 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -15,74 +15,89 @@ # That's why we hardcode the following cases and check duplicable? instead of # using that rescue idiom. class Object - # Can you safely .dup this object? - # False for nil, false, true, symbols, numbers, class and module objects; true otherwise. + # Can you safely dup this object? + # + # False for +nil+, +false+, +true+, symbols, numbers, class and module objects; + # true otherwise. def duplicable? true end end class NilClass - # Instances of NilClass are not duplicable + # +nil+ is not duplicable: # - # === Example + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass def duplicable? false end end class FalseClass - # Instances of FalseClass are not duplicable + # +false+ is not duplicable: # - # === Example + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass def duplicable? false end end class TrueClass - # Instances of TrueClass are not duplicable + # +true+ is not duplicable: # - # === Example + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass def duplicable? false end end class Symbol - # Symbols are not duplicable + # Symbols are not duplicable: # - # === Example + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol def duplicable? false end end -class Numeric #:nodoc: +class Numeric + # Numbers are not duplicable: + # + # 3.duplicable? # => false + # 3.dup # => TypeError: can't dup Fixnum + # def duplicable? false end end -class Class #:nodoc: +class Class + # Classes are not duplicable: + # + # c = Class.new # => # + # c.dup # => # + # + # Note +dup+ returned the same class object. def duplicable? false end end -class Module #:nodoc: +class Module + # Modules are not duplicable: + # + # m = Module.new # => # + # m.dup # => # + # + # Note +dup+ returned the same module object. def duplicable? false end -- cgit v1.2.3 From 914218ef302542f3f58ef7f8f46c0ff0b540ac82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 21:14:55 +0200 Subject: Let's use inheritance here, shall we? --- .../helpers/asset_tag_helpers/asset_include_tag.rb | 5 +- .../helpers/asset_tag_helpers/asset_paths.rb | 13 ++-- .../asset_tag_helpers/javascript_tag_helpers.rb | 3 - .../asset_tag_helpers/stylesheet_tag_helpers.rb | 3 - .../lib/action_view/helpers/sprockets_helper.rb | 77 +++++++--------------- actionpack/test/template/sprockets_helper_test.rb | 8 +++ 6 files changed, 43 insertions(+), 66 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index 52eb43a1cd..e4662a2919 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -8,9 +8,11 @@ module ActionView module AssetTagHelper class AssetIncludeTag - attr_reader :config, :asset_paths + include TagHelper + attr_reader :config, :asset_paths class_attribute :expansions + def self.inherited(base) base.expansions = { } end @@ -56,7 +58,6 @@ module ActionView end end - private def path_to_asset(source, include_host = true) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 1e00fd996b..955634bb19 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -26,15 +26,18 @@ module ActionView # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) + source = source.to_s return source if is_uri?(source) source = rewrite_extension(source, dir, ext) if ext source = "/#{dir}/#{source}" unless source[0] == ?/ - source = rewrite_asset_path(source, config.asset_path) + source = rewrite_asset_path(source, dir) - has_request = controller.respond_to?(:request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request && include_host - source = rewrite_host_and_protocol(source, has_request) if include_host + if controller && include_host + has_request = controller.respond_to?(:request) + source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request + source = rewrite_host_and_protocol(source, has_request) + end source end @@ -70,6 +73,8 @@ module ActionView # Break out the asset path rewrite in case plugins wish to put the asset id # someplace other than the query string. def rewrite_asset_path(source, path = nil) + path = config.asset_path + if path && path.respond_to?(:call) return path.call(source) elsif path && path.is_a?(String) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index a0f6fb5692..07ff49659a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class JavascriptIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'javascript' end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index 309762ee05..c3dcd410bb 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class StylesheetIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'stylesheet' end diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index bf47af6e10..e2e844c74d 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -1,10 +1,11 @@ require 'uri' +require 'action_view/helpers/asset_tag_helpers/asset_paths' module ActionView module Helpers module SprocketsHelper def asset_path(source, default_ext = nil) - compute_sprockets_path(source, 'assets', default_ext) + sprockets_asset_paths.compute_public_path(source, 'assets', default_ext, true) end def sprockets_javascript_include_tag(source, options = {}) @@ -27,74 +28,42 @@ module ActionView tag 'link', options end - private - def compute_sprockets_path(source, dir, default_ext = nil) - source = source.to_s - - unless source_is_a_url?(source) - add_asset_directory(source, dir) - add_default_extension(source, default_ext) - add_fingerprint(source, dir) - add_asset_host(source) - end - source - end - - def add_asset_directory(source, dir) - source.replace("/#{dir}/#{source}") if source[0] != ?/ - end - - def add_default_extension(source, default_ext) - source.replace("#{source}.#{default_ext}") if default_ext && File.extname(source).empty? - end - - def add_fingerprint(source, dir) - if source =~ /^\/#{dir}\/(.+)/ - source.replace(assets.path($1, performing_caching?, dir)) - end + def sprockets_asset_paths + @sprockets_asset_paths ||= begin + config = self.config if respond_to?(:config) + controller = self.controller if respond_to?(:controller) + SprocketsHelper::AssetPaths.new(config, controller) end + end - def add_asset_host(source) - # When included in Sprockets::Context, there's no controller - return unless respond_to?(:controller) - - host = compute_asset_host(source) - - if controller.respond_to?(:request) && host && URI.parse(host).host - source.replace("#{controller.request.protocol}#{host}#{source}") + class AssetPaths < ActionView::Helpers::AssetTagHelper::AssetPaths + def rewrite_asset_path(source, dir) + if source =~ /^\/#{dir}\/(.+)/ + assets.path($1, performing_caching?, dir) + else + source end end - - def source_is_a_url?(source) - URI.parse(source).host.present? - end - def compute_asset_host(source) - if host = config.asset_host - if host.is_a?(Proc) || host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end + def rewrite_extension(source, dir, ext) + if ext && File.extname(source).empty? + "#{source}.#{ext}" + else + source end end def assets Rails.application.assets end - + + # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available def performing_caching? - # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available - respond_to?(:config) ? config.perform_caching : Rails.application.config.action_controller.perform_caching + @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching end + end end end end diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index 863e473697..d8aef34bfb 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -1,5 +1,8 @@ require 'abstract_unit' require 'sprockets' +require 'mocha' + +module Rails; end class SprocketsHelperTest < ActionView::TestCase tests ActionView::Helpers::SprocketsHelper @@ -24,6 +27,11 @@ class SprocketsHelperTest < ActionView::TestCase @assets.paths << FIXTURES.join("sprockets/app/stylesheets") @assets.paths << FIXTURES.join("sprockets/app/images") + application = Object.new + Rails.stubs(:application).returns(application) + application.stubs(:config).returns(config) + application.stubs(:assets).returns(@assets) + config.perform_caching = true end -- cgit v1.2.3 From ad602869260b4233f7471f8aa467d3b83ffeb801 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 19 Apr 2011 21:43:17 +0200 Subject: Revert "Better formatting" No need to change this formatting. This reverts commit 9c861e8a0e2f21959ede5f71b56f8a50996d6416. --- railties/guides/source/action_view_overview.textile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index e11cb00d9f..172932fdab 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -227,13 +227,13 @@ To render a partial as part of a view, you use the +render+ method within the vi <%= render "menu" %>
-This will render a file named _menu.html.erb at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: +This will render a file named +_menu.html.erb+ at that point within the view is being rendered. Note the leading underscore character: partials are named with a leading underscore to distinguish them from regular views, even though they are referred to without the underscore. This holds true even when you're pulling in a partial from another folder: <%= render "shared/menu" %> -That code will pull in the partial from app/views/shared/_menu.html.erb. +That code will pull in the partial from +app/views/shared/_menu.html.erb+. h5. Using Partials to Simplify Views @@ -252,7 +252,7 @@ One way to use partials is to treat them as the equivalent of subroutines: as a <%= render "shared/footer" %> -Here, the _ad_banner.html.erb and _footer.html.erb partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. +Here, the +_ad_banner.html.erb+ and +_footer.html.erb+ partials could contain content that is shared among many pages in your application. You don't need to see the details of these sections when you're concentrating on a particular page. h5. The :as and :object options -- cgit v1.2.3 From a19c260038a9b5b688a2e8d883b604983ac59eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 21:49:28 +0200 Subject: Include modules to the context in the railtie. --- actionpack/lib/action_view/helpers/sprockets_helper.rb | 7 +------ actionpack/lib/sprockets/railtie.rb | 6 ++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index e2e844c74d..947c827f3c 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -66,9 +66,4 @@ module ActionView end end end -end - -# FIXME: Temp hack for extending Sprockets::Context so -class Sprockets::Context - include ActionView::Helpers::SprocketsHelper -end if defined?(Sprockets) \ No newline at end of file +end \ No newline at end of file diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index fe3c8c9783..2f7f95c44d 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -32,6 +32,12 @@ module Sprockets next unless assets.enabled app.assets = asset_environment(app) + + # FIXME: Temp hack for extending Sprockets::Context so + ActiveSupport.on_load(:action_view) do + ::Sprockets::Context.send :include, ::ActionView::Helpers::SprocketsHelper + end + app.routes.append do mount app.assets => assets.prefix end -- cgit v1.2.3 From 22fcef90b185199563719fc511346bf4c2f5bbff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 22:01:25 +0200 Subject: Actually add an abstract class, so it is easier to get rid of old asset paths in the future. --- actionpack/lib/action_view/helpers/asset_paths.rb | 80 ++++++++++ .../lib/action_view/helpers/asset_tag_helper.rb | 2 +- .../helpers/asset_tag_helpers/asset_paths.rb | 161 +++++++-------------- .../lib/action_view/helpers/sprockets_helper.rb | 4 +- 4 files changed, 136 insertions(+), 111 deletions(-) create mode 100644 actionpack/lib/action_view/helpers/asset_paths.rb diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb new file mode 100644 index 0000000000..55a4c442fd --- /dev/null +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -0,0 +1,80 @@ +require 'active_support/core_ext/file' +require 'action_view/helpers/asset_paths' + +module ActionView + module Helpers + + class AssetPaths #:nodoc: + attr_reader :config, :controller + + def initialize(config, controller) + @config = config + @controller = controller + end + + # Add the extension +ext+ if not present. Return full URLs otherwise untouched. + # Prefix with /dir/ if lacking a leading +/+. Account for relative URL + # roots. Rewrite the asset path for cache-busting asset ids. Include + # asset host, if configured, with the correct request protocol. + def compute_public_path(source, dir, ext = nil, include_host = true) + source = source.to_s + return source if is_uri?(source) + + source = rewrite_extension(source, dir, ext) if ext + source = "/#{dir}/#{source}" unless source[0] == ?/ + source = rewrite_asset_path(source, dir) + + if controller && include_host + has_request = controller.respond_to?(:request) + source = rewrite_host_and_protocol(source, has_request) + end + + source + end + + def is_uri?(path) + path =~ %r{^[-a-z]+://|^cid:} + end + + private + + def rewrite_extension(source, dir, ext) + raise NotImplementedError + end + + def rewrite_asset_path(source, path = nil) + raise NotImplementedError + end + + def rewrite_host_and_protocol(source, has_request) + host = compute_asset_host(source) + if has_request && host && !is_uri?(host) + host = "#{controller.request.protocol}#{host}" + end + "#{host}#{source}" + end + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains %d (the number is the source hash mod 4), + # or the value returned from invoking the proc if it's a proc or the value from + # invoking call if it's an object responding to call. + def compute_asset_host(source) + if host = config.asset_host + if host.is_a?(Proc) || host.respond_to?(:call) + case host.is_a?(Proc) ? host.arity : host.method(:call).arity + when 2 + request = controller.respond_to?(:request) && controller.request + host.call(source, request) + else + host.call(source) + end + else + (host =~ /%d/) ? host % (source.hash % 4) : host + end + end + end + end + + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index e859b3ae49..a7ae6ad0bf 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -446,7 +446,7 @@ module ActionView private def asset_paths - @asset_paths ||= AssetPaths.new(config, controller) + @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller) end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 955634bb19..38860431b4 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,10 +1,11 @@ require 'active_support/core_ext/file' +require 'action_view/helpers/asset_paths' module ActionView module Helpers module AssetTagHelper - class AssetPaths + class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: # You can enable or disable the asset tag ids cache. # With the cache enabled, the asset tag helper methods will make fewer # expensive file system calls (the default implementation checks the file @@ -14,34 +15,6 @@ module ActionView # ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false mattr_accessor :cache_asset_ids - attr_reader :config, :controller - - def initialize(config, controller) - @config = config - @controller = controller - end - - # Add the extension +ext+ if not present. Return full URLs otherwise untouched. - # Prefix with /dir/ if lacking a leading +/+. Account for relative URL - # roots. Rewrite the asset path for cache-busting asset ids. Include - # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) - source = source.to_s - return source if is_uri?(source) - - source = rewrite_extension(source, dir, ext) if ext - source = "/#{dir}/#{source}" unless source[0] == ?/ - source = rewrite_asset_path(source, dir) - - if controller && include_host - has_request = controller.respond_to?(:request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request - source = rewrite_host_and_protocol(source, has_request) - end - - source - end - # Add or change an asset id in the asset id cache. This can be used # for SASS on Heroku. # :api: public @@ -51,103 +24,75 @@ module ActionView end end - def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:} - end - - private - - def rewrite_extension(source, dir, ext) - source_ext = File.extname(source) + private - source_with_ext = if source_ext.empty? - "#{source}.#{ext}" - elsif ext != source_ext[1..-1] - with_ext = "#{source}.#{ext}" - with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext)) - end + def rewrite_extension(source, dir, ext) + source_ext = File.extname(source) - source_with_ext || source + source_with_ext = if source_ext.empty? + "#{source}.#{ext}" + elsif ext != source_ext[1..-1] + with_ext = "#{source}.#{ext}" + with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext)) end - # Break out the asset path rewrite in case plugins wish to put the asset id - # someplace other than the query string. - def rewrite_asset_path(source, path = nil) - path = config.asset_path + source_with_ext || source + end - if path && path.respond_to?(:call) - return path.call(source) - elsif path && path.is_a?(String) - return path % [source] - end + # Break out the asset path rewrite in case plugins wish to put the asset id + # someplace other than the query string. + def rewrite_asset_path(source, path = nil) + path = config.asset_path - asset_id = rails_asset_id(source) - if asset_id.empty? - source - else - "#{source}?#{asset_id}" - end + if path && path.respond_to?(:call) + return path.call(source) + elsif path && path.is_a?(String) + return path % [source] end - mattr_accessor :asset_ids_cache - self.asset_ids_cache = {} + asset_id = rails_asset_id(source) + if asset_id.empty? + source + else + "#{source}?#{asset_id}" + end + end + + mattr_accessor :asset_ids_cache + self.asset_ids_cache = {} - mattr_accessor :asset_ids_cache_guard - self.asset_ids_cache_guard = Mutex.new + mattr_accessor :asset_ids_cache_guard + self.asset_ids_cache_guard = Mutex.new - # Use the RAILS_ASSET_ID environment variable or the source's - # modification time as its cache-busting asset id. - def rails_asset_id(source) - if asset_id = ENV["RAILS_ASSET_ID"] + # Use the RAILS_ASSET_ID environment variable or the source's + # modification time as its cache-busting asset id. + def rails_asset_id(source) + if asset_id = ENV["RAILS_ASSET_ID"] + asset_id + else + if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source]) asset_id else - if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source]) - asset_id - else - path = File.join(config.assets_dir, source) - asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' + path = File.join(config.assets_dir, source) + asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' - if self.cache_asset_ids - add_to_asset_ids_cache(source, asset_id) - end - - asset_id + if self.cache_asset_ids + add_to_asset_ids_cache(source, asset_id) end - end - end - - def rewrite_relative_url_root(source, relative_url_root) - relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source - end - def rewrite_host_and_protocol(source, has_request) - host = compute_asset_host(source) - if has_request && host && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" + asset_id end - "#{host}#{source}" end + end - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains %d (the number is the source hash mod 4), - # or the value returned from invoking the proc if it's a proc or the value from - # invoking call if it's an object responding to call. - def compute_asset_host(source) - if host = config.asset_host - if host.is_a?(Proc) || host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end - end - end + def rewrite_relative_url_root(source, relative_url_root) + relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source + end + + def rewrite_host_and_protocol(source, has_request) + source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request + super(source, has_request) + end end end diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index 947c827f3c..b43b91178c 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -1,5 +1,5 @@ require 'uri' -require 'action_view/helpers/asset_tag_helpers/asset_paths' +require 'action_view/helpers/asset_paths' module ActionView module Helpers @@ -38,7 +38,7 @@ module ActionView end end - class AssetPaths < ActionView::Helpers::AssetTagHelper::AssetPaths + class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: def rewrite_asset_path(source, dir) if source =~ /^\/#{dir}\/(.+)/ assets.path($1, performing_caching?, dir) -- cgit v1.2.3 From 89ed9fbd1917e431e489dc856042d996d0f088c5 Mon Sep 17 00:00:00 2001 From: Florent Piteau Date: Wed, 20 Apr 2011 02:10:29 +0800 Subject: Don't reuse a closed flash when using now --- actionpack/lib/action_dispatch/middleware/flash.rb | 6 +++++- actionpack/test/controller/flash_test.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 735c72d34a..c7f7d4d4f0 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -70,6 +70,10 @@ module ActionDispatch def close!(new_flash) @flash = new_flash end + + def closed? + @flash.closed? + end end class FlashHash @@ -146,7 +150,7 @@ module ActionDispatch # # Entries set via now are accessed the same way as standard entries: flash['my-key']. def now - @now ||= FlashNow.new(self) + @now = (!@now || @now.closed?) ? FlashNow.new(self) : @now end attr_reader :closed diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 7b5bf8b21a..e19612eace 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -272,6 +272,14 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_setting_flash_now_does_not_raise_in_following_requests + with_test_route_set do + env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } + get '/set_flash_now', nil, env + get '/set_flash_now', nil, env + end + end + def test_setting_flash_raises_after_stream_back_to_client_even_with_an_empty_flash with_test_route_set do env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } -- cgit v1.2.3 From 2f549b8bbd733ad0563d977e83a9b2a2b6b8e07c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 19 Apr 2011 22:38:51 +0200 Subject: Use initialize_copy! to proper initialize now on clone. --- actionpack/lib/action_dispatch/middleware/flash.rb | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index c7f7d4d4f0..2adbce031b 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -43,6 +43,8 @@ module ActionDispatch KEY = 'action_dispatch.request.flash_hash'.freeze class FlashNow #:nodoc: + attr_accessor :flash + def initialize(flash) @flash = flash end @@ -66,14 +68,6 @@ module ActionDispatch def notice=(message) self[:notice] = message end - - def close!(new_flash) - @flash = new_flash - end - - def closed? - @flash.closed? - end end class FlashHash @@ -86,6 +80,14 @@ module ActionDispatch @now = nil end + def initialize_copy(other) + if other.now_is_loaded? + @now = other.now.dup + @now.flash = self + end + super + end + def []=(k, v) #:nodoc: raise ClosedError, :flash if closed? keep(k) @@ -150,16 +152,12 @@ module ActionDispatch # # Entries set via now are accessed the same way as standard entries: flash['my-key']. def now - @now = (!@now || @now.closed?) ? FlashNow.new(self) : @now + @now ||= FlashNow.new(self) end attr_reader :closed alias :closed? :closed - - def close! - @closed = true - @now.close!(self) if @now - end + def close!; @closed = true; end # Keeps either the entire current flash or a specific flash entry available for the next action: # @@ -214,7 +212,12 @@ module ActionDispatch self[:notice] = message end - private + protected + + def now_is_loaded? + !!@now + end + # Used internally by the keep and discard methods # use() # marks the entire flash as used # use('msg') # marks the "msg" entry as used -- cgit v1.2.3 From 68eb84d73562478af347d9a9924fc10ca7869a82 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 19 Apr 2011 22:52:00 +0200 Subject: correction to the i18n locale filter examples --- railties/guides/source/i18n.textile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/i18n.textile b/railties/guides/source/i18n.textile index 5478a80567..608643b3d3 100644 --- a/railties/guides/source/i18n.textile +++ b/railties/guides/source/i18n.textile @@ -133,9 +133,9 @@ The _setting part_ is easy. You can set the locale in a +before_filter+ in the + before_filter :set_locale + def set_locale - # if params[:locale] is nil then I18n.default_locale will be used - I18n.locale = params[:locale] + I18n.locale = params[:locale] || I18n.default_locale end @@ -158,7 +158,7 @@ You can implement it like this in your +ApplicationController+: before_filter :set_locale def set_locale - I18n.locale = extract_locale_from_tld + I18n.locale = extract_locale_from_tld || I18n.default_locale end # Get locale from top-level domain or return nil if such locale is not available @@ -182,7 +182,7 @@ We can also set the locale from the _subdomain_ in a very similar way: # in your /etc/hosts file to try this out locally def extract_locale_from_subdomain parsed_locale = request.subdomains.first - I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil + I18n.available_locales.include?(parsed_locale.to_sym) ? parsed_locale : nil end -- cgit v1.2.3 From 1c5d5ad3e96ec742aee815ed33598822f62b6987 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 20 Apr 2011 06:28:22 +0900 Subject: NilClass is a singleton --- activesupport/lib/active_support/core_ext/object/try.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index aedf5c8c82..e77a9da0ec 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -35,7 +35,7 @@ class Object end class NilClass - # Instances of +NilClass+ return always +nil+. + # Calling +try+ on +nil+ always returns +nil+. # It becomes specially helpful when navigating through associations that may return +nil+. # # === Examples -- cgit v1.2.3 From aa19a7d0859052bf197e8898995ad4c280f64330 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 19 Apr 2011 23:21:40 -0300 Subject: Fix 'FIXME' annotation typo --- railties/test/generators/assets_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index e99f0f092a..375632e5bc 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -1,7 +1,7 @@ require 'generators/generators_test_helper' require 'rails/generators/rails/assets/assets_generator' -# FOXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub +# FIXME: Silence the 'Could not find task "using_coffee?"' message in tests due to the public stub class AssetsGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments %w(posts) -- cgit v1.2.3 From a48f8089aba974465ccd1f99506f04d1f60ac2d2 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Wed, 20 Apr 2011 10:43:35 -0400 Subject: Exception got moved to ActiveModel with Rails 3 --- railties/guides/source/active_record_querying.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 7cdffe4c2e..2f0a51e868 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -418,7 +418,7 @@ SELECT viewable_by, locked FROM clients Be careful because this also means you're initializing a model object with only the fields that you've selected. If you attempt to access a field that is not in the initialized record you'll receive: -ActiveRecord::MissingAttributeError: missing attribute: +ActiveModel::MissingAttributeError: missing attribute: Where +<attribute>+ is the attribute you asked for. The +id+ method will not raise the +ActiveRecord::MissingAttributeError+, so just be careful when working with associations because they need the +id+ method to function properly. -- cgit v1.2.3 From 2f0dca5ee081f22d20457150b92225958b6ef363 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 20 Apr 2011 23:25:33 +0530 Subject: document the Active Support extension - String#inquiry --- railties/guides/source/active_support_core_extensions.textile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index b7f842a0d0..d4dda88937 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1266,6 +1266,15 @@ WARNING: The option +:separator+ can't be a regexp. NOTE: Defined in +active_support/core_ext/string/filters.rb+. +h4. +inquiry+ + +The String#inquiry method converts a string into a +StringInquirer+ object making equality checks prettier. + + +"production".inquiry.production? # => true +"active".inquiry.inactive? # => false + + h4. Key-based Interpolation In Ruby 1.9 the % string operator supports key-based interpolation, both formatted and unformatted: -- cgit v1.2.3 From d31af44012a6ba3ac5dbec45417ae9bfb5454d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 20 Apr 2011 21:54:19 +0200 Subject: Fix tests on 1.8 by explicitly checking for strings (which also improves performance). --- actionpack/lib/action_dispatch/http/response.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 78ecf177be..1f4f3ac0da 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -118,7 +118,15 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:each) ? body : [body] + + # Explicitly check for strings. This is *wrong* theoretically + # but if we don't check this, the performance on string bodies + # is bad on Ruby 1.8 (because strings responds to each then). + @body = if body.respond_to?(:to_str) || !body.respond_to?(:each) + [body] + else + body + end end def body_parts -- cgit v1.2.3 From 783007a8ad7b4b61a1a671d1737a8a6e0369ceb9 Mon Sep 17 00:00:00 2001 From: Joost Baaij Date: Thu, 21 Apr 2011 16:32:02 +0200 Subject: Replace example hostname with "example.com". The hostname used in these comments actually exists, which is undesirable. See also RFC 2606. --- .../lib/action_view/helpers/asset_tag_helper.rb | 6 +++--- .../asset_tag_helpers/javascript_tag_helpers.rb | 18 ++++++++-------- .../asset_tag_helpers/stylesheet_tag_helpers.rb | 14 ++++++------- actionpack/lib/action_view/helpers/url_helper.rb | 2 +- actionpack/test/template/sprockets_helper_test.rb | 24 +++++++++++----------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index a7ae6ad0bf..f7cb1f5b58 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -268,7 +268,7 @@ module ActionView # image_path("edit.png") # => "/images/edit.png" # image_path("icons/edit.png") # => "/images/icons/edit.png" # image_path("/icons/edit.png") # => "/icons/edit.png" - # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png" + # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" # # If you have images as application resources this method may conflict with their named routes. # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and @@ -291,7 +291,7 @@ module ActionView # video_path("hd.avi") # => /videos/hd.avi # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi # video_path("/trailers/hd.avi") # => /trailers/hd.avi - # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi + # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi def video_path(source) if config.use_sprockets asset_path(source) @@ -310,7 +310,7 @@ module ActionView # audio_path("horse.wav") # => /audios/horse.wav # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav # audio_path("/sounds/horse.wav") # => /sounds/horse.wav - # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav + # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav def audio_path(source) if config.use_sprockets asset_path(source) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 07ff49659a..3d815b5e1f 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -77,11 +77,11 @@ module ActionView # Used internally by javascript_include_tag to build the script path. # # ==== Examples - # javascript_path "xmlhr" # => /javascripts/xmlhr.js - # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js - # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js - # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr - # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js + # javascript_path "xmlhr" # => /javascripts/xmlhr.js + # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js + # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js + # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr + # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def javascript_path(source) if config.use_sprockets asset_path(source, 'js') @@ -123,11 +123,11 @@ module ActionView # # => # # # - # javascript_include_tag "http://www.railsapplication.com/xmlhr" - # # => + # javascript_include_tag "http://www.example.com/xmlhr" + # # => # - # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" - # # => + # javascript_include_tag "http://www.example.com/xmlhr.js" + # # => # # javascript_include_tag :defaults # # => diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index c3dcd410bb..a95eb221be 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -54,11 +54,11 @@ module ActionView # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # # ==== Examples - # stylesheet_path "style" # => /stylesheets/style.css - # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css - # stylesheet_path "/dir/style.css" # => /dir/style.css - # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style - # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css + # stylesheet_path "style" # => /stylesheets/style.css + # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css + # stylesheet_path "/dir/style.css" # => /dir/style.css + # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style + # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source) if config.use_sprockets asset_path(source, 'css') @@ -79,8 +79,8 @@ module ActionView # stylesheet_link_tag "style.css" # => # # - # stylesheet_link_tag "http://www.railsapplication.com/style.css" # => - # + # stylesheet_link_tag "http://www.example.com/style.css" # => + # # # stylesheet_link_tag "style", :media => "all" # => # diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 051d3eb049..ffa9a5bb0b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -68,7 +68,7 @@ module ActionView # # => /books/find # # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %> - # # => https://www.railsapplication.com/members/login/ + # # => https://www.example.com/members/login/ # # <%= url_for(:action => 'play', :anchor => 'player') %> # # => /messages/play/#player diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index d8aef34bfb..8d3be09a4f 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -69,10 +69,10 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/dir/xmlhr.js", asset_path("/dir/xmlhr", "js") - assert_equal "http://www.railsapplication.com/js/xmlhr", - asset_path("http://www.railsapplication.com/js/xmlhr", "js") - assert_equal "http://www.railsapplication.com/js/xmlhr.js", - asset_path("http://www.railsapplication.com/js/xmlhr.js", "js") + assert_equal "http://www.example.com/js/xmlhr", + asset_path("http://www.example.com/js/xmlhr", "js") + assert_equal "http://www.example.com/js/xmlhr.js", + asset_path("http://www.example.com/js/xmlhr.js", "js") end test "javascript include tag" do @@ -83,8 +83,8 @@ class SprocketsHelperTest < ActionView::TestCase sprockets_javascript_include_tag("xmlhr") assert_equal '', sprockets_javascript_include_tag("xmlhr.js") - assert_equal '', - sprockets_javascript_include_tag("http://www.railsapplication.com/xmlhr") + assert_equal '', + sprockets_javascript_include_tag("http://www.example.com/xmlhr") end test "stylesheet path" do @@ -94,10 +94,10 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css") assert_equal "/dir/style.css", asset_path("/dir/style.css", "css") - assert_equal "http://www.railsapplication.com/css/style", - asset_path("http://www.railsapplication.com/css/style", "css") - assert_equal "http://www.railsapplication.com/css/style.css", - asset_path("http://www.railsapplication.com/css/style.css", "css") + assert_equal "http://www.example.com/css/style", + asset_path("http://www.example.com/css/style", "css") + assert_equal "http://www.example.com/css/style.css", + asset_path("http://www.example.com/css/style.css", "css") end test "stylesheet link tag" do @@ -109,8 +109,8 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal '', sprockets_stylesheet_link_tag("style.css") - assert_equal '', - sprockets_stylesheet_link_tag("http://www.railsapplication.com/style.css") + assert_equal '', + sprockets_stylesheet_link_tag("http://www.example.com/style.css") assert_equal '', sprockets_stylesheet_link_tag("style", :media => "all") assert_equal '', -- cgit v1.2.3 From 84f1b83df84d36b75cc7b6d1c84ffd84c6c07260 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 21 Apr 2011 11:03:56 -0500 Subject: Only include SprocketsHelper into assets context --- actionpack/lib/sprockets/railtie.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 2f7f95c44d..ccec65ba95 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -33,9 +33,10 @@ module Sprockets app.assets = asset_environment(app) - # FIXME: Temp hack for extending Sprockets::Context so ActiveSupport.on_load(:action_view) do - ::Sprockets::Context.send :include, ::ActionView::Helpers::SprocketsHelper + app.assets.context.instance_eval do + include ::ActionView::Helpers::SprocketsHelper + end end app.routes.append do @@ -59,4 +60,4 @@ module Sprockets env end end -end \ No newline at end of file +end -- cgit v1.2.3 From 724a7866774c7847cc79b699ccf7da9fee72b154 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 21 Apr 2011 22:47:13 -0500 Subject: stop using distinct on for the unique id queries. [#6450 state:resolved] --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- activerecord/lib/active_record/relation/finder_methods.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index acc3e9c5e3..66ca831d6c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -935,7 +935,7 @@ module ActiveRecord # Return a DISTINCT ON() clause that's distinct on the columns we want but includes # all the required columns for the ORDER BY to work properly. - sql = "DISTINCT ON (#{columns}) #{columns}, " + sql = "DISTINCT #{columns}, " sql << order_columns * ', ' end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index aae257a0e7..a3d4b7f45a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -246,6 +246,8 @@ module ActiveRecord orders = relation.order_values values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders) + relation = relation.dup + ids_array = relation.select(values).collect {|row| row[primary_key]} ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array) end -- cgit v1.2.3 From c389b832f0f3aad2bf9550c81a20a8c5e6574423 Mon Sep 17 00:00:00 2001 From: Cheah Chu Yeow Date: Fri, 22 Apr 2011 15:14:14 +0800 Subject: Grammar fixes for Object#in? documentation. --- activesupport/lib/active_support/core_ext/object/inclusion.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index 51cfc62f2b..b5671f66d0 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -1,11 +1,11 @@ class Object # Returns true if this object is included in the argument. Argument must be - # any object which respond to +#include?+. Usage: + # any object which responds to +#include?+. Usage: # # characters = ["Konata", "Kagami", "Tsukasa"] # "Konata".in?(characters) # => true # - # This will throw an ArgumentError if the supplied argument doesnt not respond + # This will throw an ArgumentError if the argument doesn't respond # to +#include?+. def in?(another_object) another_object.include?(self) -- cgit v1.2.3 From 0cf7b2f1b4e0506ed015ed924933ecad441deee7 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Fri, 22 Apr 2011 15:24:11 +0200 Subject: minor correction to the Active Model instantiate_observers doc --- activemodel/lib/active_model/observing.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index ef36f80bec..3c80d584fe 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -43,7 +43,7 @@ module ActiveModel @observer_instances ||= [] end - # Instantiate the global Active Record observers. + # Instantiate the global observers. def instantiate_observers observers.each { |o| instantiate_observer(o) } end -- cgit v1.2.3 From ed2820d6ecef7533970952437b974c7fa10192e1 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Fri, 22 Apr 2011 20:33:43 +0800 Subject: Move #exec_insert to abstract adapter's database statements. --- .../connection_adapters/abstract/database_statements.rb | 7 +++++++ .../lib/active_record/connection_adapters/mysql_adapter.rb | 4 ---- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 ---- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ---- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6d9b5c7b32..5ff81aa023 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,6 +55,13 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) end + # Executes insert +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is the logged along with + # the executed +sql+ statement. + def exec_insert(sql, name, binds) + exec_query(sql, name, binds) + end + # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2e75acb9a..0f45565dc9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -427,10 +427,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.insert_id end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 66ca831d6c..5adb235835 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -552,10 +552,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk _, table = extract_schema_and_table(sql.split(" ", 4)[2]) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9e7f874f4b..8bff20fa70 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -173,10 +173,6 @@ module ActiveRecord end end - def exec_insert(sql, name, binds) - exec_query(sql, name, binds) - end - def last_inserted_id(result) @connection.last_insert_row_id end -- cgit v1.2.3 From 6822f39f67729884d7911b42542c965434a22250 Mon Sep 17 00:00:00 2001 From: Semyon Perepelitsa Date: Fri, 22 Apr 2011 23:12:14 +0800 Subject: Remove HTML escaping from Record Tag Helper docs. --- actionpack/lib/action_view/helpers/record_tag_helper.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 4d300a1469..142a25f118 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -10,7 +10,7 @@ module ActionView # relate to the specified Active Record object. Usage example: # # <%= div_for(@person, :class => "foo") do %> - # <%=h @person.name %> + # <%= @person.name %> # <% end %> # # produces: @@ -25,8 +25,8 @@ module ActionView # that relate to the specified Active Record object. For example: # # <%= content_tag_for(:tr, @person) do %> - # <%=h @person.first_name %> - # <%=h @person.last_name %> + # <%= @person.first_name %> + # <%= @person.last_name %> # <% end %> # # would produce the following HTML (assuming @person is an instance of -- cgit v1.2.3 From 20002850b30a2da97cea8e3f2f019826b123fdb6 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:14:23 -0300 Subject: Removed ON() on distinct comment --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index acc3e9c5e3..b7b4ce655e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -933,7 +933,7 @@ module ActiveRecord order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } - # Return a DISTINCT ON() clause that's distinct on the columns we want but includes + # Return a DISTINCT clause that's distinct on the columns we want but includes # all the required columns for the ORDER BY to work properly. sql = "DISTINCT ON (#{columns}) #{columns}, " sql << order_columns * ', ' -- cgit v1.2.3 From 00e9cbd78af42f0661b3538a966710244c40e276 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:22:53 -0300 Subject: Have a more connection specific rdoc for +supports_statement_cache?+ --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++-- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 ++-- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2e75acb9a..4ec31cb6c4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -208,8 +208,8 @@ module ActiveRecord true end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b7b4ce655e..94cf8d45d6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -211,8 +211,8 @@ module ActiveRecord ADAPTER_NAME end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9e7f874f4b..7888ef1fe1 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -66,8 +66,8 @@ module ActiveRecord sqlite_version >= '3.6.8' end - # Returns +true+ when the connection adapter supports prepared statement - # caching, otherwise returns +false+ + # Returns +true+, since this connection adapter supports prepared statement + # caching. def supports_statement_cache? true end -- cgit v1.2.3 From 3d459645c6c2a77b3ff219c22129ed1642f2e882 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:35:53 -0300 Subject: Added missing docs for +support_migrations?+ on some adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 4ec31cb6c4..551f6e23e5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -214,6 +214,7 @@ module ActiveRecord true end + # Does MySQL support migrations? def supports_migrations? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 7888ef1fe1..9fb537d949 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -72,6 +72,7 @@ module ActiveRecord true end + # Does SQLite support migrations? def supports_migrations? #:nodoc: true end -- cgit v1.2.3 From e2656e07b9f9482f19f53054b72167b1bca2e6b7 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:45:46 -0300 Subject: Added missing docs for +supports_primary_key?+ on some adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 551f6e23e5..e01419224b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -219,6 +219,7 @@ module ActiveRecord true end + # Does MySQL support finding primary key on non-Active Record tables? def supports_primary_key? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 9fb537d949..5d2ebff9a7 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -77,6 +77,7 @@ module ActiveRecord true end + # Does SQLite support finding primary key on non-Active Record tables? def supports_primary_key? #:nodoc: true end -- cgit v1.2.3 From dab96a267eeccd7380ad99fa19cefdfd3cd5ad2b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 22 Apr 2011 10:49:55 -0500 Subject: Add shorthand for js and css compressors --- actionpack/lib/sprockets/railtie.rb | 37 +++++++++++++++++++++++++ railties/lib/rails/application/configuration.rb | 3 ++ 2 files changed, 40 insertions(+) diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index ccec65ba95..9c10decd60 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -57,7 +57,44 @@ module Sprockets env.static_root = File.join(app.root.join("public"), assets.prefix) env.paths.concat assets.paths env.logger = Rails.logger + env.js_compressor = expand_js_compressor(assets.js_compressor) + env.css_compressor = expand_css_compressor(assets.css_compressor) env end + + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end + end + + def expand_css_compressor(sym) + case sym + when :scss + require 'sass' + compressor = Object.new + def compressor.compress(source) + Sass::Engine.new(source, + :syntax => :scss, :style => :compressed + ).render + end + compressor + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new(:munge => true) + else + sym + end + end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index bd8c314da6..f818313955 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -35,6 +35,9 @@ module Rails @assets.paths = [] @assets.precompile = [ /\w+\.(?!js|css)$/, "application.js", "application.css" ] @assets.prefix = "/assets" + + @assets.js_compressor = nil + @assets.css_compressor = nil end def compiled_asset_path -- cgit v1.2.3 From 0e2644cde043f1d7126fda0510d9961eb1692403 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:53:02 -0300 Subject: Added missing docs for clear_cache! on adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 + 3 files changed, 3 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e01419224b..3318075e9e 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -332,6 +332,7 @@ module ActiveRecord rows end + # Clear prepared statement cache. def clear_cache! @statements.values.each do |cache| cache[:stmt].close diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 94cf8d45d6..ba7852192e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -237,6 +237,7 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end + # Clear prepared statement cache. def clear_cache! @statements.each_value do |value| @connection.query "DEALLOCATE #{value}" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5d2ebff9a7..326f22ce82 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -96,6 +96,7 @@ module ActiveRecord @connection.close rescue nil end + # Clear prepared statement cache. def clear_cache! @statements.clear end -- cgit v1.2.3 From 02ee2003a8040e9e8de5f3258f88dfaaade06c27 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 12:59:05 -0300 Subject: Added docs for #disconnect! on adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 ++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 ++- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 3318075e9e..5f2e56c9c3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -310,6 +310,8 @@ module ActiveRecord connect end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! @connection.close rescue nil end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ba7852192e..53ea80cf15 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -276,7 +276,8 @@ module ActiveRecord super end - # Close the connection. + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! clear_cache! @connection.close rescue nil diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 326f22ce82..2ceb62dfc2 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -90,6 +90,8 @@ module ActiveRecord sqlite_version >= '3.1.6' end + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. def disconnect! super clear_cache! -- cgit v1.2.3 From 59ce0f695bcbcb903effa490e7aa56fc86e22172 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 22 Apr 2011 18:00:19 +0200 Subject: Turn on JS/CSS compression by default --- railties/lib/rails/generators/rails/app/templates/Gemfile | 1 + .../rails/app/templates/config/environments/production.rb.tt | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 0cee7deb72..141d9fd15c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -8,6 +8,7 @@ source 'http://rubygems.org' <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> gem 'sass', '~> 3.1.0.alpha' gem 'coffee-script' +gem 'uglifier' # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 80951cf73a..b00f10c545 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -15,6 +15,10 @@ # (comment out if your front-end server doesn't support this) config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx + # Compress both stylesheets and JavaScripts + config.assets.js_compressor = :uglifier + config.assets.css_compressor = :scss + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true -- cgit v1.2.3 From bc50c1cb02c317ef92e4aa0336d396dcad5bc578 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Fri, 22 Apr 2011 19:55:42 +0800 Subject: The #substitute_at gets an ActiveRecord::ConnectionAdapters::Column in #insert to match replacement in #exec_query. --- activerecord/lib/active_record/relation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 359f9d8a66..45a7000cce 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -56,7 +56,7 @@ module ActiveRecord end substitutes.each_with_index do |tuple, i| - tuple[1] = conn.substitute_at(tuple.first, i) + tuple[1] = conn.substitute_at(binds[i][0], i) end if values.empty? # empty insert -- cgit v1.2.3 From fe4932b531ec9b593085c4bb115c13f0852c3fbb Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Apr 2011 11:27:14 -0500 Subject: bumping the pg requirements --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c720b09a2c..fa8b67a743 100644 --- a/Gemfile +++ b/Gemfile @@ -52,7 +52,7 @@ platforms :ruby do gem "sqlite3", "~> 1.3.3" group :db do - gem "pg", ">= 0.9.0" + gem "pg", ">= 0.11.0" gem "mysql", ">= 2.8.1" gem "mysql2", :git => "git://github.com/brianmario/mysql2.git" end -- cgit v1.2.3 From 96acdd3ec48594635314e768bf18805af6a40dde Mon Sep 17 00:00:00 2001 From: Pavel Forkert Date: Sun, 27 Feb 2011 23:40:10 +0200 Subject: Added test case and fix for proper eager loading associations --- activerecord/test/cases/associations/eager_test.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 40c82f2fb8..9bc7910fc6 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -170,6 +170,16 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal [comment], category.posts[0].comments end end + + def test_associations_loaded_for_all_records + post = Post.create!(:title => 'foo', :body => "I like cars!") + comment = SpecialComment.create!(:body => 'Come on!', :post => post) + first_category = Category.create! :name => 'First!', :posts => [post] + second_category = Category.create! :name => 'Second!', :posts => [post] + + categories = Category.where(:id => [first_category.id, second_category.id]).includes(:posts => :special_comments) + assert_equal categories.map { |category| category.posts.first.special_comments.loaded? }, [true, true] + end def test_finding_with_includes_on_has_many_association_with_same_include_includes_only_once author_id = authors(:david).id -- cgit v1.2.3 From a37722182fd96190f2317ffd0450c49ed6d406f0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Apr 2011 12:21:59 -0500 Subject: removing incorrect comment and string concatenation --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5adb235835..203ecd2f03 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -929,10 +929,7 @@ module ActiveRecord order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } - # Return a DISTINCT ON() clause that's distinct on the columns we want but includes - # all the required columns for the ORDER BY to work properly. - sql = "DISTINCT #{columns}, " - sql << order_columns * ', ' + "DISTINCT #{columns}, #{order_columns * ', '}" end protected -- cgit v1.2.3 From 89f315bfb287f5428e78195e7c93c85c0892ab64 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 22 Apr 2011 19:37:52 +0200 Subject: We cant use assert_block because its buggy in MiniTest and wont actually show you the failure message you provide -- instead you just always get a "Expected block to return true" --- actionpack/lib/action_dispatch/testing/assertions/response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 8a04cfa886..e209978fb7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -42,7 +42,7 @@ module ActionDispatch elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert_block(build_message(message, "Expected response to be a , but was ", type, @response.response_code)) { false } + assert(false, build_message(message, "Expected response to be a , but was ", type, @response.response_code)) end end -- cgit v1.2.3 From bb6b17216d05cb3c29279b13423a424fa375aaf4 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 22 Apr 2011 20:03:12 +0200 Subject: Need this for testing --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index fa8b67a743..5c65ebd2e6 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ gem "rack-test", :git => "git://github.com/brynary/rack-test.git" gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" gem "sass", ">= 3.0" +gem "uglifier" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" -- cgit v1.2.3 From eb599295db00b9a614e01707db2371d8819947ed Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 23 Apr 2011 02:17:08 +0800 Subject: Images path fixed in test. --- railties/test/generators/app_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 3ef06c7f25..58febfd9c7 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -23,7 +23,7 @@ DEFAULT_APP_FILES = %w( lib lib/tasks log - public/images + app/assets/images script/rails test/fixtures test/functional -- cgit v1.2.3 From 6cea2436d7bb8e62acc35d98d141f3bab7743dbc Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Fri, 22 Apr 2011 23:07:46 +0530 Subject: Start Adding jdbcmysql support to new rails template. More will come for jruby app. --- railties/lib/rails/generators/app_base.rb | 5 ++-- .../app/templates/config/databases/jdbcmysql.yml | 30 ++++++++++++++++++++++ railties/test/generators/app_generator_test.rb | 6 +++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 481fa95068..ddfd5256fe 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -9,7 +9,7 @@ require 'uri' module Rails module Generators class AppBase < Base - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql ) JAVASCRIPTS = %w( jquery prototype ) attr_accessor :rails_template @@ -156,12 +156,13 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" + when "jdbcmysql" then "activerecord-jdbcmysql-adapter" else options[:database] end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml new file mode 100644 index 0000000000..ca807c9f3f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -0,0 +1,30 @@ +# MySQL. Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install activerecord-jdbcmysql-adapter +# +# And be sure to use new-style password hashing: +# http://dev.mysql.com/doc/refman/5.0/en/old-client.html +development: + adapter: jdbcmysql + database: <%= app_name %>_development + username: root + password: + host: localhost + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: jdbcmysql + database: <%= app_name %>_test + username: root + password: + host: localhost + +production: + adapter: jdbcmysql + database: <%= app_name %>_production + username: root + password: + host: localhost diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 58febfd9c7..176d016fb9 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -131,6 +131,12 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^gem\s+["']mysql2["']$/ end + def test_config_jdbcmysql_database + run_generator([destination_root, "-d", "jdbcmysql"]) + assert_file "config/database.yml", /jdbcmysql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" -- cgit v1.2.3 From a8870d140ececc6327533d08b112cece03c41499 Mon Sep 17 00:00:00 2001 From: Schneems Date: Sat, 23 Apr 2011 02:23:12 +0800 Subject: show http method in routing error message --- actionpack/lib/action_dispatch/middleware/show_exceptions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index dbe3206808..b1adf3d2d1 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -50,7 +50,7 @@ module ActionDispatch # Only this middleware cares about RoutingError. So, let's just raise # it here. if headers['X-Cascade'] == 'pass' - raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end rescue Exception => exception raise exception if env['action_dispatch.show_exceptions'] == false -- cgit v1.2.3 From d42e43b3e50efdcd75112db2bb386d62cda1eb6a Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 23 Apr 2011 02:40:17 +0800 Subject: Fixed images path in plugin_new_generator --- railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 81563f81d3..12921f47b6 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -93,7 +93,7 @@ task :default => :test remove_file "doc" remove_file "Gemfile" remove_file "lib/tasks" - remove_file "public/images/rails.png" + remove_file "app/assets/images/rails.png" remove_file "public/index.html" remove_file "public/robots.txt" remove_file "README" -- cgit v1.2.3 From 3eae734012fadd641a4ec95715480b962861294c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Apr 2011 14:37:53 -0500 Subject: set the backtrace to prevent AR exceptions from lying to us --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d24cce0a3c..468a2b106b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -223,7 +223,9 @@ module ActiveRecord rescue Exception => e message = "#{e.class.name}: #{e.message}: #{sql}" @logger.debug message if @logger - raise translate_exception(e, message) + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + raise exception end def translate_exception(e, message) -- cgit v1.2.3 From 3d19b356d7ccb37b23afcab0b0b698bfd3d668d7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Apr 2011 15:31:03 -0500 Subject: fetch result row arrays from pg in C and return early if there are no money or binary columns. :heart: --- .../connection_adapters/postgresql_adapter.rb | 67 ++++++++++++---------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 203ecd2f03..8763074121 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,9 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' + +# Make sure we're using pg high enough for PGResult#values +gem 'pg', '~> 0.11' require 'pg' module ActiveRecord @@ -460,42 +463,44 @@ module ActiveRecord # create a 2D array representing the result set def result_as_array(res) #:nodoc: # check if we have any binary column and if they need escaping - unescape_col = [] - res.nfields.times do |j| - unescape_col << res.ftype(j) + ftypes = Array.new(res.nfields) do |i| + [i, res.ftype(i)] end - ary = [] - res.ntuples.times do |i| - ary << [] - res.nfields.times do |j| - data = res.getvalue(i,j) - case unescape_col[j] - - # unescape string passed BYTEA field (OID == 17) - when BYTEA_COLUMN_TYPE_OID - data = unescape_bytea(data) if String === data - - # If this is a money type column and there are any currency symbols, - # then strip them off. Indeed it would be prettier to do this in - # PostgreSQLColumn.string_to_decimal but would break form input - # fields that call value_before_type_cast. - when MONEY_COLUMN_TYPE_OID - # Because money output is formatted according to the locale, there are two - # cases to consider (note the decimal separators): - # (1) $12,345,678.12 - # (2) $12.345.678,12 - case data - when /^-?\D+[\d,]+\.\d{2}$/ # (1) - data.gsub!(/[^-\d.]/, '') - when /^-?\D+[\d.]+,\d{2}$/ # (2) - data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') - end + rows = res.values + return rows unless ftypes.any? { |_, x| + x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID + } + + typehash = ftypes.group_by { |_, type| type } + binaries = (typehash[BYTEA_COLUMN_TYPE_OID] || []).map { |x| x.first } + monies = (typehash[MONEY_COLUMN_TYPE_OID] || []).map { |x| x.first } + + rows.each do |row| + # unescape string passed BYTEA field (OID == 17) + binaries.each do |index| + data = row[index] + row[index] = unescape_bytea(data) + end + + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + monies.each do |index| + data = row[index] + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case data + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + data.gsub!(/[^-\d.]/, '') + when /^-?\D+[\d.]+,\d{2}$/ # (2) + data.gsub!(/[^-\d,]/, '').sub!(/,/, '.') end - ary[i] << data end end - return ary end -- cgit v1.2.3 From 5bed6494d49506223dc3b6652858c21487776a02 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 22 Apr 2011 15:35:54 -0500 Subject: split indexes and column types on money / binary iteration --- .../active_record/connection_adapters/postgresql_adapter.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 8763074121..fbfccd4ec1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -473,21 +473,20 @@ module ActiveRecord } typehash = ftypes.group_by { |_, type| type } - binaries = (typehash[BYTEA_COLUMN_TYPE_OID] || []).map { |x| x.first } - monies = (typehash[MONEY_COLUMN_TYPE_OID] || []).map { |x| x.first } + binaries = typehash[BYTEA_COLUMN_TYPE_OID] || [] + monies = typehash[MONEY_COLUMN_TYPE_OID] || [] rows.each do |row| # unescape string passed BYTEA field (OID == 17) - binaries.each do |index| - data = row[index] - row[index] = unescape_bytea(data) + binaries.each do |index, _| + row[index] = unescape_bytea(row[index]) end # If this is a money type column and there are any currency symbols, # then strip them off. Indeed it would be prettier to do this in # PostgreSQLColumn.string_to_decimal but would break form input # fields that call value_before_type_cast. - monies.each do |index| + monies.each do |index, _| data = row[index] # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): -- cgit v1.2.3 From eac264d74754eee6f157a75e386031ccbccd4328 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 23 Apr 2011 02:42:22 +0530 Subject: updating to_xml example to new AR api --- railties/guides/source/active_support_core_extensions.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index d4dda88937..4d5baff86b 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -2101,7 +2101,7 @@ h5. +to_xml+ The method +to_xml+ returns a string containing an XML representation of its receiver: -Contributor.all(:limit => 2, :order => 'rank ASC').to_xml +Contributor.limit(2).order(:rank).to_xml # => # # @@ -2176,7 +2176,7 @@ The name of children nodes is by default the name of the root node singularized. The default XML builder is a fresh instance of Builder::XmlMarkup. You can configure your own builder via the :builder option. The method also accepts options like :dasherize and friends, they are forwarded to the builder: -Contributor.all(:limit => 2, :order => 'rank ASC').to_xml(:skip_types => true) +Contributor.limit(2).order(:rank).to_xml(:skip_types => true) # => # # -- cgit v1.2.3 From 98b700e0bfe836c7ee4754344202dd15c7ee0f2c Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 23 Apr 2011 02:45:29 +0530 Subject: add examples for array access methods --- railties/guides/source/active_support_core_extensions.textile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 4d5baff86b..c937da834c 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -2006,6 +2006,11 @@ Similarly, +from+ returns the tail from the element at the passed index on: The methods +second+, +third+, +fourth+, and +fifth+ return the corresponding element (+first+ is built-in). Thanks to social wisdom and positive constructiveness all around, +forty_two+ is also available. + +%w(a b c d).third # => c +%w(a b c d).fifth # => nil + + NOTE: Defined in +active_support/core_ext/array/access.rb+. h4. Random Access -- cgit v1.2.3 From b22f39ec6f887725dd2780032a9a5be10217b7cb Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 23 Apr 2011 02:53:30 +0530 Subject: fix indentation --- .../source/active_support_core_extensions.textile | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index c937da834c..b57adbb517 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -3421,11 +3421,11 @@ h4. +silence+ Silences every log level lesser to the specified one for the duration of the given block. Log level orders are: debug, info, error and fatal. - logger = Logger.new("log/development.log") - logger.silence(Logger::INFO) do - logger.debug("In space, no one can hear you scream.") - logger.info("Scream all you want, small mailman!") - end +logger = Logger.new("log/development.log") +logger.silence(Logger::INFO) do + logger.debug("In space, no one can hear you scream.") + logger.info("Scream all you want, small mailman!") +end h4. +datetime_format=+ @@ -3433,17 +3433,17 @@ h4. +datetime_format=+ Modifies the datetime format output by the formatter class associated with this logger. If the formatter class does not have a +datetime_format+ method then this is ignored. - class Logger::FormatWithTime < Logger::Formatter - cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } +class Logger::FormatWithTime < Logger::Formatter + cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" } - def self.call(severity, timestamp, progname, msg) - "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" - end + def self.call(severity, timestamp, progname, msg) + "#{timestamp.strftime(datetime_format)} -- #{String === msg ? msg : msg.inspect}\n" end +end - logger = Logger.new("log/development.log") - logger.formatter = Logger::FormatWithTime - logger.info("<- is the current time") +logger = Logger.new("log/development.log") +logger.formatter = Logger::FormatWithTime +logger.info("<- is the current time") NOTE: Defined in +active_support/core_ext/logger.rb+. -- cgit v1.2.3 From 013d9016ca2a12f4429b2f0b058283fb75b73cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 22 Apr 2011 23:25:48 +0200 Subject: Use .erb in the test. --- railties/test/railties/shared_tests.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb index b2b18938ae..e975950b85 100644 --- a/railties/test/railties/shared_tests.rb +++ b/railties/test/railties/shared_tests.rb @@ -11,7 +11,7 @@ module RailtiesTest end def test_serving_sprockets_assets - @plugin.write "app/assets/javascripts/engine.js.coffee", "square = (x) -> x * x" + @plugin.write "app/assets/javascripts/engine.js.erb", "<%= :alert %>();" boot_rails require 'rack/test' @@ -19,7 +19,7 @@ module RailtiesTest extend Rack::Test::Methods get "/assets/engine.js" - assert_match "square = function(x) {", last_response.body + assert_match "alert();", last_response.body end def test_copying_migrations -- cgit v1.2.3 From e46e106c547054d4c6a898012e78b6ccfc835254 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 23 Apr 2011 03:06:06 +0530 Subject: fix block alignment --- railties/guides/source/active_record_validations_callbacks.textile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile index 9aab4b6694..19bd4ad0f1 100644 --- a/railties/guides/source/active_record_validations_callbacks.textile +++ b/railties/guides/source/active_record_validations_callbacks.textile @@ -897,8 +897,9 @@ The macro-style class methods can also receive a block. Consider using this styl class User < ActiveRecord::Base validates_presence_of :login, :email - before_create {|user| user.name = user.login.capitalize - if user.name.blank?} + before_create do |user| + user.name = user.login.capitalize if user.name.blank? + end end -- cgit v1.2.3 From 3f7a434d2e11e14a5ec1422b6096969c5a62a2bf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Fri, 22 Apr 2011 23:38:03 +0200 Subject: The example uses erb --- railties/lib/rails/generators/rails/app/templates/db/seeds.rb | 7 ------- railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 railties/lib/rails/generators/rails/app/templates/db/seeds.rb create mode 100644 railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb deleted file mode 100644 index 9a2efa68a7..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb +++ /dev/null @@ -1,7 +0,0 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ <%= key_value :name, "'Chicago'" %> }, { <%= key_value :name, "'Copenhagen'" %> }]) -# Mayor.create(<%= key_value :name, "'Daley'" %>, <%= key_value :city, "cities.first" %>) diff --git a/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt new file mode 100644 index 0000000000..9a2efa68a7 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/db/seeds.rb.tt @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ <%= key_value :name, "'Chicago'" %> }, { <%= key_value :name, "'Copenhagen'" %> }]) +# Mayor.create(<%= key_value :name, "'Daley'" %>, <%= key_value :city, "cities.first" %>) -- cgit v1.2.3 From 3050497a546396f273adfa5cd19330a8b2832931 Mon Sep 17 00:00:00 2001 From: Ronnie Miller Date: Fri, 22 Apr 2011 14:50:52 -0700 Subject: Grammer fix on note about wildcard routes --- railties/guides/source/routing.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 95b877aecf..43c08165dc 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -557,7 +557,7 @@ match '*a/foo/*b' => 'test#index' would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+. -NOTE: Starting from Rails 3.1, wildcard route will always matching the optional format segment by default. For example if you have this route: +NOTE: Starting from Rails 3.1, wildcard routes will always match the optional format segment by default. For example if you have this route: map '*pages' => 'pages#show' -- cgit v1.2.3 From f7538808d466b756df15ac3f27ca08370f7a189c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 22 Apr 2011 23:55:53 +0200 Subject: File watcher won't make the cut for 3.1. Maybe on 3.2. --- Gemfile | 1 - activesupport/lib/active_support.rb | 1 - activesupport/test/file_watcher_test.rb | 233 -------------------------------- 3 files changed, 235 deletions(-) delete mode 100644 activesupport/test/file_watcher_test.rb diff --git a/Gemfile b/Gemfile index 5c65ebd2e6..c262b97819 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,6 @@ end # AS gem "memcache-client", ">= 1.8.5" -gem "fssm", "~> 0.2.5" platforms :mri_18 do gem "system_timer" diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 6b662ac660..6b87774978 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -42,7 +42,6 @@ module ActiveSupport autoload :DescendantsTracker autoload :FileUpdateChecker - autoload :FileWatcher autoload :LogSubscriber autoload :Notifications diff --git a/activesupport/test/file_watcher_test.rb b/activesupport/test/file_watcher_test.rb deleted file mode 100644 index 7b4d4be24f..0000000000 --- a/activesupport/test/file_watcher_test.rb +++ /dev/null @@ -1,233 +0,0 @@ -require 'abstract_unit' -require 'fssm' -require "fileutils" -require "timeout" - - -class FileWatcherTest < ActiveSupport::TestCase - class DumbBackend < ActiveSupport::FileWatcher::Backend - end - - def setup - @watcher = ActiveSupport::FileWatcher.new - - # In real life, the backend would take the path and use it to observe the file - # system. In our case, we will manually trigger the events for unit testing, - # so we can pass any path. - @backend = DumbBackend.new("RAILS_WOOT", @watcher) - - @payload = [] - @watcher.watch %r{^app/assets/.*\.scss$} do |pay| - pay.each do |status, files| - files.sort! - end - @payload << pay - end - end - - def test_use_triple_equals - fw = ActiveSupport::FileWatcher.new - called = [] - fw.watch("some_arbitrary_file.rb") do |file| - called << "omg" - end - fw.trigger(%w{ some_arbitrary_file.rb }) - assert_equal ['omg'], called - end - - def test_one_change - @backend.trigger("app/assets/main.scss" => :changed) - assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) - end - - def test_multiple_changes - @backend.trigger("app/assets/main.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) - assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) - end - - def test_multiple_changes_match - @backend.trigger("app/assets/main.scss" => :changed, "app/assets/print.scss" => :changed, "app/assets/javascripts/foo.coffee" => :changed) - assert_equal([{:changed => ["app/assets/main.scss", "app/assets/print.scss"]}], @payload) - end - - def test_multiple_state_changes - @backend.trigger("app/assets/main.scss" => :created, "app/assets/print.scss" => :changed) - assert_equal([{:changed => ["app/assets/print.scss"], :created => ["app/assets/main.scss"]}], @payload) - end - - def test_more_blocks - payload = [] - @watcher.watch %r{^config/routes\.rb$} do |pay| - payload << pay - end - - @backend.trigger "config/routes.rb" => :changed - assert_equal [:changed => ["config/routes.rb"]], payload - assert_equal [], @payload - end - - def test_overlapping_watchers - payload = [] - @watcher.watch %r{^app/assets/main\.scss$} do |pay| - payload << pay - end - - @backend.trigger "app/assets/print.scss" => :changed, "app/assets/main.scss" => :changed - assert_equal [:changed => ["app/assets/main.scss"]], payload - assert_equal [:changed => ["app/assets/main.scss", "app/assets/print.scss"]], @payload - end -end - -module FSSM::Backends - class Polling - def initialize_with_low_latency(options={}) - initialize_without_low_latency(options.merge(:latency => 0.1)) - end - alias_method_chain :initialize, :low_latency - end -end - -class FSSMFileWatcherTest < ActiveSupport::TestCase - class FSSMBackend < ActiveSupport::FileWatcher::Backend - def initialize(path, watcher) - super - - monitor = FSSM::Monitor.new - monitor.path(path, '**/*') do |p| - p.update { |base, relative| trigger relative => :changed } - p.delete { |base, relative| trigger relative => :deleted } - p.create { |base, relative| trigger relative => :created } - end - - @thread = Thread.new do - monitor.run - end - end - - def stop - @thread.kill - end - end - - def setup - Thread.abort_on_exception = true - - @payload = [] - @triggered = false - - @watcher = ActiveSupport::FileWatcher.new - - @path = path = File.expand_path("../tmp", __FILE__) - FileUtils.rm_rf path - - create "app/assets/main.scss", true - create "app/assets/javascripts/foo.coffee", true - create "app/assets/print.scss", true - create "app/assets/videos.scss", true - - @backend = FSSMBackend.new(path, @watcher) - - @watcher.watch %r{^app/assets/.*\.scss$} do |pay| - pay.each do |status, files| - files.sort! - end - @payload << pay - trigger - end - end - - def teardown - @backend.stop - Thread.abort_on_exception = false - end - - def create(path, past = false) - wait(past) do - path = File.join(@path, path) - FileUtils.mkdir_p(File.dirname(path)) - - FileUtils.touch(path) - File.utime(Time.now - 100, Time.now - 100, path) if past - end - end - - def change(path) - wait do - FileUtils.touch(File.join(@path, path)) - end - end - - def delete(path) - wait do - FileUtils.rm(File.join(@path, path)) - end - end - - def wait(past = false) - yield - return if past - - begin - Timeout.timeout(1) do - sleep 0.05 until @triggered - end - rescue Timeout::Error - end - - @triggered = false - end - - def trigger - @triggered = true - end - - def test_one_change - change "app/assets/main.scss" - assert_equal({:changed => ["app/assets/main.scss"]}, @payload.first) - end - - def test_multiple_changes - change "app/assets/main.scss" - change "app/assets/javascripts/foo.coffee" - assert_equal([{:changed => ["app/assets/main.scss"]}], @payload) - end - - def test_multiple_changes_match - change "app/assets/main.scss" - change "app/assets/print.scss" - change "app/assets/javascripts/foo.coffee" - assert_equal([{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload) - end - - def test_multiple_state_changes - create "app/assets/new.scss" - change "app/assets/print.scss" - delete "app/assets/videos.scss" - assert_equal([{:created => ["app/assets/new.scss"]}, {:changed => ["app/assets/print.scss"]}, {:deleted => ["app/assets/videos.scss"]}], @payload) - end - - def test_more_blocks - payload = [] - @watcher.watch %r{^config/routes\.rb$} do |pay| - payload << pay - trigger - end - - create "config/routes.rb" - assert_equal [{:created => ["config/routes.rb"]}], payload - assert_equal [], @payload - end - - def test_overlapping_watchers - payload = [] - @watcher.watch %r{^app/assets/main\.scss$} do |pay| - payload << pay - trigger - end - - change "app/assets/main.scss" - change "app/assets/print.scss" - assert_equal [{:changed => ["app/assets/main.scss"]}], payload - assert_equal [{:changed => ["app/assets/main.scss"]}, {:changed => ["app/assets/print.scss"]}], @payload - end -end -- cgit v1.2.3 From 1a7737353f5844201e5e02528db494cf18f4d72e Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 19:44:30 -0300 Subject: Added docs for #drop_database on MySQL adapter --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 5f2e56c9c3..d3efc50072 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -563,6 +563,10 @@ module ActiveRecord end end + # Drops a MySQL database. + # + # Example: + # drop_database 'sebastian_development' def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS `#{name}`" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 53ea80cf15..9fb3139178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -643,7 +643,7 @@ module ActiveRecord execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" end - # Drops a PostgreSQL database + # Drops a PostgreSQL database. # # Example: # drop_database 'matt_development' -- cgit v1.2.3 From ec33de5dcb1df9b5123412820dd8ac1d9d6be992 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 23 Apr 2011 00:43:58 +0200 Subject: copy-edits 2f0dca5 --- railties/guides/source/active_support_core_extensions.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index b57adbb517..f89c83e4cd 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1268,7 +1268,7 @@ NOTE: Defined in +active_support/core_ext/string/filters.rb+. h4. +inquiry+ -The String#inquiry method converts a string into a +StringInquirer+ object making equality checks prettier. +The inquiry method converts a string into a +StringInquirer+ object making equality checks prettier. "production".inquiry.production? # => true -- cgit v1.2.3 From 2c5023921655e0883ed893a82ce2a647118d83c6 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 23 Apr 2011 00:49:55 +0200 Subject: copy-edits 3d45964 --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index d3efc50072..52903a15fb 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -214,7 +214,7 @@ module ActiveRecord true end - # Does MySQL support migrations? + # Returns true. def supports_migrations? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 2ceb62dfc2..c199e11d64 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -72,7 +72,7 @@ module ActiveRecord true end - # Does SQLite support migrations? + # Returns true. def supports_migrations? #:nodoc: true end -- cgit v1.2.3 From ca2f826f5addfeee2b4d5265e7146ad1ead2e37b Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 23 Apr 2011 00:51:43 +0200 Subject: copy-edits e2656e0 --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 52903a15fb..e73ddc9893 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -219,7 +219,7 @@ module ActiveRecord true end - # Does MySQL support finding primary key on non-Active Record tables? + # Returns true. def supports_primary_key? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index c199e11d64..ae885c2a5d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -66,7 +66,7 @@ module ActiveRecord sqlite_version >= '3.6.8' end - # Returns +true+, since this connection adapter supports prepared statement + # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true @@ -77,7 +77,7 @@ module ActiveRecord true end - # Does SQLite support finding primary key on non-Active Record tables? + # Returns true. def supports_primary_key? #:nodoc: true end -- cgit v1.2.3 From df70b9dfb23aa05ed7ae7955ff0fb7eec68de9a3 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 23 Apr 2011 00:53:28 +0200 Subject: copy-edits 0e2644c --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e73ddc9893..1270a1292c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -334,7 +334,7 @@ module ActiveRecord rows end - # Clear prepared statement cache. + # Clears the prepared statements cache. def clear_cache! @statements.values.each do |cache| cache[:stmt].close diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9fb3139178..3f70d5d0f7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -237,7 +237,7 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end - # Clear prepared statement cache. + # Clears the prepared statements cache. def clear_cache! @statements.each_value do |value| @connection.query "DEALLOCATE #{value}" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ae885c2a5d..a9dc756bbb 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -98,7 +98,7 @@ module ActiveRecord @connection.close rescue nil end - # Clear prepared statement cache. + # Clears the prepared statements cache. def clear_cache! @statements.clear end -- cgit v1.2.3 From 900470cf3cd346ad1b17cf6902f3f98e6dae7709 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:13:24 -0300 Subject: Added docs for #indexes on adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1270a1292c..46842df024 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -611,6 +611,7 @@ module ActiveRecord super(table_name, options) end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil)#:nodoc: indexes = [] current_index = nil diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3f70d5d0f7..8d336de498 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -690,7 +690,7 @@ module ActiveRecord [schema, table] end - # Returns the list of all indexes for a table. + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') result = query(<<-SQL, name) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index a9dc756bbb..3eaa0f642f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -263,6 +263,7 @@ module ActiveRecord end end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) #:nodoc: exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| IndexDefinition.new( -- cgit v1.2.3 From 5d59cd8d6a8181cafa55af6189e0984b618e19be Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:20:25 -0300 Subject: Added docs for #columns on some adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c5a9ac683c..381995b1b6 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -626,6 +626,7 @@ module ActiveRecord indexes end + # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+. def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 24d1bed165..90f20c15b5 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -244,6 +244,7 @@ module ActiveRecord end end + # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+. def columns(table_name, name = nil) #:nodoc: table_structure(table_name).map do |field| case field["dflt_value"] -- cgit v1.2.3 From e382d95e1e491c48f583d2bf13a9f50d7b3ee6ef Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:26:20 -0300 Subject: Added docs for #rename_table on some adapters --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +++ activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 381995b1b6..6342504f83 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -640,6 +640,10 @@ module ActiveRecord super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d6beb67c4d..9f9b40e108 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -855,6 +855,9 @@ module ActiveRecord end # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(name, new_name) execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 90f20c15b5..afb768c6a0 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -284,6 +284,10 @@ module ActiveRecord exec_query "DROP INDEX #{quote_column_name(index_name)}" end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(name, new_name) exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end -- cgit v1.2.3 From 616eef33a5ffe006609e75b67cadcc4c89ff09b4 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:39:00 -0300 Subject: Added doc for #table_exists? --- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 8bae50885f..9a6f36598b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -17,6 +17,10 @@ module ActiveRecord # def tables(name = nil) end + # Checks to see if the table +table_name+ exists on the database. + # + # === Example + # table_exists?(:developers) def table_exists?(table_name) tables.include?(table_name.to_s) end -- cgit v1.2.3 From 0ce94f40203d4dcff577d8e3aadbc963adca569c Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:39:26 -0300 Subject: dot missing here --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 9a6f36598b..9f9c2c42cb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -28,7 +28,7 @@ module ActiveRecord # Returns an array of indexes for the given table. # def indexes(table_name, name = nil) end - # Checks to see if an index exists on a table for a given index definition + # Checks to see if an index exists on a table for a given index definition. # # === Examples # # Check an index exists -- cgit v1.2.3 From 868fb3825cb9631bdf284668f354f7bb1ea0d61b Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:50:15 -0300 Subject: Make this docs more consistent with the rest of the docs present --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 5 +++-- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 ++- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 6342504f83..82cfd10480 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -208,13 +208,13 @@ module ActiveRecord true end - # Returns +true+, since this connection adapter supports prepared statement + # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? true end - # Returns true. + # Returns true, since this connection adapter supports migrations. def supports_migrations? #:nodoc: true end @@ -224,6 +224,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? #:nodoc: true end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9f9b40e108..b1d7fa1bd6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -290,7 +290,7 @@ module ActiveRecord NATIVE_DATABASE_TYPES end - # Does PostgreSQL support migrations? + # Returns true, since this connection adapter supports migrations. def supports_migrations? true end @@ -316,6 +316,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? true end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index afb768c6a0..432bbbba45 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -72,7 +72,7 @@ module ActiveRecord true end - # Returns true. + # Returns true, since this connection adapter supports migrations. def supports_migrations? #:nodoc: true end -- cgit v1.2.3 From 3e678daa720f26cffc889a301a1d27b89294d14a Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 20:58:02 -0300 Subject: Added some docs on SQLite adapter --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 432bbbba45..4540cf015b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -58,10 +58,12 @@ module ActiveRecord 'SQLite' end + # Returns true if SQLite version is '2.0.0' or greater, false otherwise. def supports_ddl_transactions? sqlite_version >= '2.0.0' end + # Returns true if SQLite version is '3.6.8' or greater, false otherwise. def supports_savepoints? sqlite_version >= '3.6.8' end @@ -86,6 +88,7 @@ module ActiveRecord true end + # Returns true if SQLite version is '3.1.6' or greater, false otherwise. def supports_add_column? sqlite_version >= '3.1.6' end @@ -103,10 +106,12 @@ module ActiveRecord @statements.clear end + # Returns true if SQLite version is '3.2.6' or greater, false otherwise. def supports_count_distinct? #:nodoc: sqlite_version >= '3.2.6' end + # Returns true if SQLite version is '3.1.0' or greater, false otherwise. def supports_autoincrement? #:nodoc: sqlite_version >= '3.1.0' end -- cgit v1.2.3 From 17d34d149e0622a2c4cdaecd7f3356f33a4a8b12 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 21:01:29 -0300 Subject: Added docs for #version on mysql_adapter --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 82cfd10480..eaadbc179b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -882,6 +882,7 @@ module ActiveRecord version[0] >= 5 end + # Returns the version of the connected MySQL server. def version @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end -- cgit v1.2.3 From 4d0464187cebdf513f91a61c356a2452043d27e7 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 21:02:49 -0300 Subject: Fix #postgresql_version docs --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b1d7fa1bd6..aa9c9f5889 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -943,7 +943,7 @@ module ActiveRecord end protected - # Returns the version of the connected PostgreSQL version. + # Returns the version of the connected PostgreSQL server. def postgresql_version @postgresql_version ||= if @connection.respond_to?(:server_version) -- cgit v1.2.3 From a510f5c39fc8365ef2dffb8f0cd81c755f0b902e Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 21:13:35 -0300 Subject: Better docs formatting --- .../connection_adapters/abstract/database_limits.rb | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb index 29ac9341ec..30ccb8f0a4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -2,52 +2,53 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseLimits - # the maximum length of a table alias + # Returns the maximum length of a table alias. def table_alias_length 255 end - # the maximum length of a column name + # Returns the maximum length of a column name. def column_name_length 64 end - # the maximum length of a table name + # Returns the maximum length of a table name. def table_name_length 64 end - # the maximum length of an index name + # Returns the maximum length of an index name. def index_name_length 64 end - # the maximum number of columns per table + # Returns the maximum number of columns per table. def columns_per_table 1024 end - # the maximum number of indexes per table + # Returns the maximum number of indexes per table. def indexes_per_table 16 end - # the maximum number of columns in a multicolumn index + # Returns the maximum number of columns in a multicolumn index. def columns_per_multicolumn_index 16 end - # the maximum number of elements in an IN (x,y,z) clause. nil means no limit + # Returns the maximum number of elements in an IN (x,y,z) clause. + # nil means no limit. def in_clause_length nil end - # the maximum length of an SQL query + # Returns the maximum length of an SQL query. def sql_query_length 1048575 end - # maximum number of joins in a single query + # Returns maximum number of joins in a single query. def joins_per_query 256 end -- cgit v1.2.3 From 8f1b141b3f766a3a724648b2a9437ee5f18aaad9 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 21:17:51 -0300 Subject: Fixed punctuation errors. --- .../active_record/connection_adapters/abstract/connection_pool.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index b4db1eed18..6f21cea288 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -113,7 +113,7 @@ module ActiveRecord end end - # A cached lookup for table existence + # A cached lookup for table existence. def table_exists?(name) return true if @tables.key? name @@ -135,7 +135,7 @@ module ActiveRecord @tables.clear end - # Clear out internal caches for table with +table_name+ + # Clear out internal caches for table with +table_name+. def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name @@ -193,7 +193,7 @@ module ActiveRecord @connections = [] end - # Clears the cache which maps classes + # Clears the cache which maps classes. def clear_reloadable_connections! @reserved_connections.each do |name, conn| checkin conn @@ -365,7 +365,7 @@ module ActiveRecord @connection_pools.each_value {|pool| pool.release_connection } end - # Clears the cache which maps classes + # Clears the cache which maps classes. def clear_reloadable_connections! @connection_pools.each_value {|pool| pool.clear_reloadable_connections! } end -- cgit v1.2.3 From 0887385d8e18acda857fccc7f5c97acfec129902 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 22 Apr 2011 21:30:30 -0300 Subject: Added missing docs to mysql2_adapter --- .../lib/active_record/connection_adapters/mysql2_adapter.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 7ac72acd58..fc91814275 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -131,6 +131,7 @@ module ActiveRecord ADAPTER_NAME end + # Returns true, since this connection adapter supports migrations. def supports_migrations? true end @@ -139,6 +140,7 @@ module ActiveRecord true end + # Returns true, since this connection adapter supports savepoints. def supports_savepoints? true end @@ -376,6 +378,10 @@ module ActiveRecord end end + # Drops a MySQL database. + # + # Example: + # drop_database('sebastian_development') def drop_database(name) #:nodoc: execute "DROP DATABASE IF EXISTS `#{name}`" end @@ -406,6 +412,7 @@ module ActiveRecord super(table_name, options) end + # Returns an array of indexes for the given table. def indexes(table_name, name = nil) indexes = [] current_index = nil @@ -423,6 +430,7 @@ module ActiveRecord indexes end + # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+. def columns(table_name, name = nil) sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] @@ -437,6 +445,10 @@ module ActiveRecord super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') def rename_table(table_name, new_name) execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" end @@ -521,6 +533,7 @@ module ActiveRecord variables.first['Value'] unless variables.empty? end + # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) keys = [] result = execute("describe #{quote_table_name(table)}") -- cgit v1.2.3 From 8d7efe6a4e25b8707d46006cf20f6ce499e83780 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 23 Apr 2011 13:22:15 +0200 Subject: Stop adding actual dummy tests (they clutter up the real ones in a new app), just show how its done --- .../rails/plugin_new/templates/test/integration/navigation_test.rb | 7 +++---- .../generators/test_unit/controller/templates/functional_test.rb | 7 +++---- .../generators/test_unit/integration/templates/integration_test.rb | 7 +++---- .../rails/generators/test_unit/mailer/templates/functional_test.rb | 7 +++---- .../lib/rails/generators/test_unit/model/templates/unit_test.rb | 7 +++---- .../lib/rails/generators/test_unit/observer/templates/unit_test.rb | 7 +++---- .../generators/test_unit/plugin/templates/%file_name%_test.rb.tt | 7 +++---- 7 files changed, 21 insertions(+), 28 deletions(-) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb index dd4d2da4eb..824caecb24 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb @@ -5,9 +5,8 @@ class NavigationTest < ActionDispatch::IntegrationTest fixtures :all <% end -%> - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end diff --git a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb index 11a73ebad7..0bc5fd8ca2 100644 --- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb @@ -3,10 +3,9 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>ControllerTest < ActionController::TestCase <% if actions.empty? -%> - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end <% else -%> <% for action in actions -%> test "should get <%= action %>" do diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb index de0823749c..e7a06e4a73 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -3,8 +3,7 @@ require 'test_helper' class <%= class_name %>Test < ActionDispatch::IntegrationTest fixtures :all - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb index b62c7fd279..c05102290c 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/functional_test.rb @@ -13,10 +13,9 @@ class <%= class_name %>Test < ActionMailer::TestCase <% end -%> <% if actions.blank? -%> - # replace this with your real tests - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end <% end -%> end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb index 6f79879838..c9bc7d5b90 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb +++ b/railties/lib/rails/generators/test_unit/model/templates/unit_test.rb @@ -2,9 +2,8 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>Test < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb index cd116f5ce9..28aa23626a 100644 --- a/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb +++ b/railties/lib/rails/generators/test_unit/observer/templates/unit_test.rb @@ -2,9 +2,8 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>ObserverTest < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end <% end -%> diff --git a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt index 3e0bc29d3a..0cbae1120e 100644 --- a/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt +++ b/railties/lib/rails/generators/test_unit/plugin/templates/%file_name%_test.rb.tt @@ -1,8 +1,7 @@ require 'test_helper' class <%= class_name %>Test < ActiveSupport::TestCase - # Replace this with your real tests. - test "the truth" do - assert true - end + # test "the truth" do + # assert true + # end end -- cgit v1.2.3 From b335533e16436d1a12f15a60f6436bdc7b967012 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sat, 23 Apr 2011 18:55:17 +0530 Subject: jdbcsqlite3 support added into template. #jruby --- railties/lib/rails/generators/app_base.rb | 4 +++- .../app/templates/config/databases/jdbcsqlite3.yml | 17 +++++++++++++++++ railties/test/generators/app_generator_test.rb | 6 ++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index ddfd5256fe..b240222c6e 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -9,7 +9,8 @@ require 'uri' module Rails module Generators class AppBase < Base - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 ) + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + JDBC_DATABASES JAVASCRIPTS = %w( jquery prototype ) attr_accessor :rails_template @@ -163,6 +164,7 @@ module Rails when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" when "jdbcmysql" then "activerecord-jdbcmysql-adapter" + when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" else options[:database] end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml new file mode 100644 index 0000000000..30776b3b4e --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -0,0 +1,17 @@ +# SQLite version 3.x +# gem 'activerecord-jdbcsqlite3-adapter' + +development: + adapter: jdbcsqlite3 + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: jdbcsqlite3 + database: db/test.sqlite3 + +production: + adapter: jdbcsqlite3 + database: db/production.sqlite3 diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 176d016fb9..839f75d963 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -137,6 +137,12 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ end + def test_config_jdbcsqlite3_database + run_generator([destination_root, "-d", "jdbcsqlite3"]) + assert_file "config/database.yml", /jdbcsqlite3/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" -- cgit v1.2.3 From 3e1f4dbb4db852a3278a883b3f599664e5705de3 Mon Sep 17 00:00:00 2001 From: misfo Date: Sat, 23 Apr 2011 10:15:38 -0500 Subject: document HTML::Selector's :has(string) pseudo class --- actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb index 0fe2e6d1a6..1eadfc0390 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -128,6 +128,8 @@ module HTML # (no parent element). # * :empty -- Match the element only if it has no child elements, # and no text content. + # * :content(string) -- Match the element only if it has string + # as its text content (ignoring leading and trailing whitespace). # * :only-child -- Match the element if it is the only child (element) # of its parent element. # * :only-of-type -- Match the element if it is the only child (element) -- cgit v1.2.3 From d59a9507cb8d9d7647a12590f36399dd055b1c33 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sun, 24 Apr 2011 00:17:19 +0530 Subject: Looks more friendly. --- railties/lib/rails/generators/app_base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index b240222c6e..a7e80b0117 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -9,8 +9,9 @@ require 'uri' module Rails module Generators class AppBase < Base + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 ) - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + JDBC_DATABASES + DATABASES.concat(JDBC_DATABASES) JAVASCRIPTS = %w( jquery prototype ) attr_accessor :rails_template -- cgit v1.2.3 From 51854de52caa410862266a79b255132a0710aef0 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Sun, 24 Apr 2011 01:59:20 +0530 Subject: Adding jdbcsqlite3 name in comment. --- railties/lib/rails/generators/app_base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index a7e80b0117..46a2a3f154 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -158,7 +158,7 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql) + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" -- cgit v1.2.3 From dcee094b22891a95b69e0c14a963b2f857bfecd0 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 24 Apr 2011 02:17:06 +0530 Subject: indentation fixes --- .../guides/source/active_support_core_extensions.textile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index f89c83e4cd..7bda1760a9 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -449,9 +449,9 @@ The predicate +in?+ tests if an object is included in another object. An +Argume Examples of +in?+: - 1.in?([1,2]) # => true - "lo".in?("hello") # => true - 25.in?(30..50) # => false +1.in?([1,2]) # => true +"lo".in?("hello") # => true +25.in?(30..50) # => false NOTE: Defined in +active_support/core_ext/object/inclusion.rb+. @@ -541,9 +541,9 @@ The default value can be also specified with a block, which is called in the con class User attr_accessor :name, :surname - attr_accessor_with_default(:full_name) { - [name, surname].compact.join(" ") - } + attr_accessor_with_default(:full_name) do + [name, surname].compact.join(" ") + end end u = User.new @@ -1223,7 +1223,7 @@ NOTE: Defined in +active_support/core_ext/string/output_safety.rb+. h4. +squish+ -The method +String#squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: +The method +squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: " \n foo\n\r \t bar \n".squish # => "foo bar" -- cgit v1.2.3 From 220782e4f0ace529f4a48ff207d286f804a2ed75 Mon Sep 17 00:00:00 2001 From: David Chelimsky Date: Sat, 23 Apr 2011 17:18:06 -0500 Subject: rename test case to better describe use case --- actionpack/test/controller/view_paths_test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 9280a1c2d3..42356be1ea 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -131,8 +131,8 @@ class ViewLoadPathsTest < ActionController::TestCase assert_equal "Hello overridden world!", @response.body end - def test_override_view_paths_with_custom_resolver - resolver_class = Class.new(ActionView::PathResolver) do + def test_decorate_view_paths_with_custom_resolver + decorator_class = Class.new(ActionView::PathResolver) do def initialize(path_set) @path_set = path_set end @@ -140,7 +140,7 @@ class ViewLoadPathsTest < ActionController::TestCase def find_all(*args) @path_set.find_all(*args).collect do |template| ::ActionView::Template.new( - "Customized body", + "Decorated body", template.identifier, template.handler, { @@ -152,12 +152,12 @@ class ViewLoadPathsTest < ActionController::TestCase end end - resolver = resolver_class.new(TestController.view_paths) - TestController.view_paths = ActionView::PathSet.new.push(resolver) + decorator = decorator_class.new(TestController.view_paths) + TestController.view_paths = ActionView::PathSet.new.push(decorator) get :hello_world assert_response :success - assert_equal "Customized body", @response.body + assert_equal "Decorated body", @response.body end def test_inheritance -- cgit v1.2.3 From 1054ebd613c5596bc1ebb8d610d19e5fa374cca5 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 23 Apr 2011 12:50:29 +0200 Subject: AM mass assignment security attr_accessible and attr_protected now allow for scopes using :as => scope eg. attr_accessible :name attr_accessible :name, :admin, :as => :admin --- .../lib/active_model/mass_assignment_security.rb | 119 +++++++++++++++------ .../test/cases/mass_assignment_security_test.rb | 39 +++++-- activemodel/test/cases/secure_password_test.rb | 11 +- .../test/models/mass_assignment_specific.rb | 11 ++ 4 files changed, 135 insertions(+), 45 deletions(-) diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index be48415739..e329933175 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -24,10 +24,7 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessible :first_name, :last_name - # - # def self.admin_accessible_attributes - # accessible_attributes + [ :plan_id ] - # end + # attr_accessible :first_name, :last_name, :plan_id, :as => :admin # # def update # ... @@ -37,19 +34,18 @@ module ActiveModel # # protected # - # def account_params - # sanitize_for_mass_assignment(params[:account]) - # end - # - # def mass_assignment_authorizer - # admin ? admin_accessible_attributes : super + # def scope + # scope = admin ? :admin : :default + # sanitize_for_mass_assignment(params[:account], scope) # end # # end # module ClassMethods # Attributes named in this macro are protected from mass-assignment - # whenever attributes are sanitized before assignment. + # whenever attributes are sanitized before assignment. A scope for the + # attributes is optional, if no scope is provided then :default is used. + # A scope can be defined by using the :as option. # # Mass-assignment to these attributes will simply be ignored, to assign # to them you can use direct writer methods. This is meant to protect @@ -60,36 +56,58 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating - # attr_protected :credit_rating # - # def attributes=(values) - # sanitize_for_mass_assignment(values).each do |k, v| + # attr_protected :credit_rating, :last_login + # attr_protected :last_login, :as => :admin + # + # def assign_attributes(values, options = {}) + # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # + # When using a :default scope : + # # customer = Customer.new - # customer.attributes = { "name" => "David", "credit_rating" => "Excellent" } + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil + # customer.last_login # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # + # And using the :admin scope : + # + # customer = Customer.new + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.name # => "David" + # customer.credit_rating # => "Excellent" + # customer.last_login # => nil + # # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. # # Note that using Hash#except or Hash#slice in place of +attr_protected+ # to sanitize attributes won't provide sufficient protection. - def attr_protected(*names) - self._protected_attributes = self.protected_attributes + names + def attr_protected(*args) + options = args.extract_options! + scope = options[:as] || :default + + self._protected_attributes = protected_attributes_configs.dup + self._protected_attributes[scope] = self.protected_attributes(scope) + args + self._active_authorizer = self._protected_attributes end # Specifies a white list of model attributes that can be set via # mass-assignment. # + # Like +attr_protected+, a scope for the attributes is optional, + # if no scope is provided then :default is used. A scope can be defined by + # using the :as option. + # # This is the opposite of the +attr_protected+ macro: Mass-assignment # will only set attributes in this list, to assign to the rest of # attributes you can use direct writer methods. This is meant to protect @@ -102,57 +120,90 @@ module ActiveModel # include ActiveModel::MassAssignmentSecurity # # attr_accessor :name, :credit_rating + # # attr_accessible :name + # attr_accessible :name, :credit_rating, :as => :admin # - # def attributes=(values) - # sanitize_for_mass_assignment(values).each do |k, v| + # def assign_attributes(values, options = {}) + # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| # send("#{k}=", v) # end # end # end # + # When using a :default scope : + # # customer = Customer.new - # customer.attributes = { :name => "David", :credit_rating => "Excellent" } + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) # customer.name # => "David" # customer.credit_rating # => nil # # customer.credit_rating = "Average" # customer.credit_rating # => "Average" # + # And using the :admin scope : + # + # customer = Customer.new + # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.name # => "David" + # customer.credit_rating # => "Excellent" + # # Note that using Hash#except or Hash#slice in place of +attr_accessible+ # to sanitize attributes won't provide sufficient protection. - def attr_accessible(*names) - self._accessible_attributes = self.accessible_attributes + names + def attr_accessible(*args) + options = args.extract_options! + scope = options[:as] || :default + + self._accessible_attributes = accessible_attributes_configs.dup + self._accessible_attributes[scope] = self.accessible_attributes(scope) + args + self._active_authorizer = self._accessible_attributes end - def protected_attributes - self._protected_attributes ||= BlackList.new(attributes_protected_by_default).tap do |w| - w.logger = self.logger if self.respond_to?(:logger) - end + def protected_attributes(scope = :default) + protected_attributes_configs[scope] end - def accessible_attributes - self._accessible_attributes ||= WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) } + def accessible_attributes(scope = :default) + accessible_attributes_configs[scope] end - def active_authorizer - self._active_authorizer ||= protected_attributes + def active_authorizers + self._active_authorizer ||= protected_attributes_configs end + alias active_authorizer active_authorizers def attributes_protected_by_default [] end + + private + + def protected_attributes_configs + self._protected_attributes ||= begin + default_black_list = BlackList.new(attributes_protected_by_default).tap do |w| + w.logger = self.logger if self.respond_to?(:logger) + end + Hash.new(default_black_list) + end + end + + def accessible_attributes_configs + self._accessible_attributes ||= begin + default_white_list = WhiteList.new.tap { |w| w.logger = self.logger if self.respond_to?(:logger) } + Hash.new(default_white_list) + end + end end protected - def sanitize_for_mass_assignment(attributes) - mass_assignment_authorizer.sanitize(attributes) + def sanitize_for_mass_assignment(attributes, scope = :default) + mass_assignment_authorizer(scope).sanitize(attributes) end - def mass_assignment_authorizer - self.class.active_authorizer + def mass_assignment_authorizer(scope = :default) + self.class.active_authorizer[scope] end end end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index f84e55e8d9..b22ce874ea 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -10,10 +10,27 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_only_moderator_scope_attribute_accessible + user = SpecialUser.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), :moderator) + assert_equal expected, sanitized + + sanitized = user.sanitize_for_mass_assignment({ "name" => "John Smith", "email" => "john@smith.com", "admin" => true }) + assert_equal({}, sanitized) + end + def test_attributes_accessible user = Person.new expected = { "name" => "John Smith", "email" => "john@smith.com" } - sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true)) + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) + assert_equal expected, sanitized + end + + def test_admin_scoped_attributes_accessible + user = Person.new + expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } + sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) assert_equal expected, sanitized end @@ -26,20 +43,30 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase def test_mass_assignment_protection_inheritance assert_blank LoosePerson.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator']), LoosePerson.protected_attributes + assert_equal Set.new(['credit_rating', 'administrator']), LoosePerson.protected_attributes + + assert_blank LoosePerson.accessible_attributes + assert_equal Set.new(['credit_rating']), LoosePerson.protected_attributes(:admin) assert_blank LooseDescendant.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes + assert_equal Set.new(['credit_rating', 'administrator', 'phone_number']), LooseDescendant.protected_attributes assert_blank LooseDescendantSecond.accessible_attributes - assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, + assert_equal Set.new(['credit_rating', 'administrator', 'phone_number', 'name']), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections' assert_blank TightPerson.protected_attributes - TightPerson.attributes_protected_by_default - assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes + assert_equal Set.new(['name', 'address']), TightPerson.accessible_attributes + + assert_blank TightPerson.protected_attributes(:admin) - TightPerson.attributes_protected_by_default + assert_equal Set.new(['name', 'address', 'admin']), TightPerson.accessible_attributes(:admin) assert_blank TightDescendant.protected_attributes - TightDescendant.attributes_protected_by_default - assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes + assert_equal Set.new(['name', 'address', 'phone_number']), TightDescendant.accessible_attributes + + assert_blank TightDescendant.protected_attributes(:admin) - TightDescendant.attributes_protected_by_default + assert_equal Set.new(['name', 'address', 'admin', 'super_powers']), TightDescendant.accessible_attributes(:admin) + end def test_mass_assignment_multiparameter_protector diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index c455cf57b3..6950c3be1f 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -45,13 +45,14 @@ class SecurePasswordTest < ActiveModel::TestCase end test "visitor#password_digest should be protected against mass assignment" do - assert Visitor.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::BlackList) - assert Visitor.active_authorizer.include?(:password_digest) + assert Visitor.active_authorizers[:default].kind_of?(ActiveModel::MassAssignmentSecurity::BlackList) + assert Visitor.active_authorizers[:default].include?(:password_digest) end test "Administrator's mass_assignment_authorizer should be WhiteList" do - assert Administrator.active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList) - assert !Administrator.active_authorizer.include?(:password_digest) - assert Administrator.active_authorizer.include?(:name) + active_authorizer = Administrator.active_authorizers[:default] + assert active_authorizer.kind_of?(ActiveModel::MassAssignmentSecurity::WhiteList) + assert !active_authorizer.include?(:password_digest) + assert active_authorizer.include?(:name) end end diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb index 2a8fe170c2..53b37369ff 100644 --- a/activemodel/test/models/mass_assignment_specific.rb +++ b/activemodel/test/models/mass_assignment_specific.rb @@ -5,9 +5,17 @@ class User public :sanitize_for_mass_assignment end +class SpecialUser + include ActiveModel::MassAssignmentSecurity + attr_accessible :name, :email, :as => :moderator + + public :sanitize_for_mass_assignment +end + class Person include ActiveModel::MassAssignmentSecurity attr_accessible :name, :email + attr_accessible :name, :email, :admin, :as => :admin public :sanitize_for_mass_assignment end @@ -32,6 +40,7 @@ end class LoosePerson include ActiveModel::MassAssignmentSecurity attr_protected :credit_rating, :administrator + attr_protected :credit_rating, :as => :admin end class LooseDescendant < LoosePerson @@ -46,6 +55,7 @@ end class TightPerson include ActiveModel::MassAssignmentSecurity attr_accessible :name, :address + attr_accessible :name, :address, :admin, :as => :admin def self.attributes_protected_by_default ["mobile_number"] @@ -54,4 +64,5 @@ end class TightDescendant < TightPerson attr_accessible :phone_number + attr_accessible :super_powers, :as => :admin end \ No newline at end of file -- cgit v1.2.3 From a08d04bedfd01cc0a517ccedf74f2ceac70eb28d Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 23 Apr 2011 15:00:24 +0200 Subject: Added assign_attributes to Active Record which accepts a mass-assignment security scope using the :as option, while also allowing mass-assignment security to be bypassed using :with_protected --- activerecord/lib/active_record/base.rb | 41 ++++++++++++- activerecord/test/cases/base_test.rb | 2 +- .../test/cases/mass_assignment_security_test.rb | 71 ++++++++++++++++++++++ activerecord/test/cases/persistence_test.rb | 2 +- activerecord/test/models/loose_person.rb | 24 -------- activerecord/test/models/person.rb | 19 ++++++ 6 files changed, 132 insertions(+), 27 deletions(-) delete mode 100644 activerecord/test/models/loose_person.rb diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9a01d793f9..4512e8c8ad 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1640,10 +1640,49 @@ end # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) return unless new_attributes.is_a?(Hash) + if guard_protected_attributes + assign_attributes(new_attributes) + else + assign_attributes(new_attributes, :without_protection => true) + end + end + + # Allows you to set all the attributes for a particular mass-assignment + # security scope by passing in a hash of attributes with keys matching + # the attribute names (which again matches the column names) and the scope + # name using the :as option. + # + # To bypass mass-assignment security you can use the :without_protection => true + # option. + # + # class User < ActiveRecord::Base + # attr_accessible :name + # attr_accessible :name, :is_admin, :as => :admin + # end + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }) + # user.name # => "Josh" + # user.is_admin? # => false + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) + # user.name # => "Josh" + # user.is_admin? # => true + # + # user = User.new + # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) + # user.name # => "Josh" + # user.is_admin? # => true + def assign_attributes(new_attributes, options = {}) attributes = new_attributes.stringify_keys + scope = options[:as] || :default multi_parameter_attributes = [] - attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes + + unless options[:without_protection] + attributes = sanitize_for_mass_assignment(attributes, scope) + end attributes.each do |k, v| if k.include?("(") diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 815ff7b825..ef833857ce 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -18,7 +18,7 @@ require 'models/comment' require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' -require 'models/loose_person' +require 'models/person' require 'models/edge' require 'models/joke' require 'rexml/document' diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 025ec1d3fa..43016df479 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -3,6 +3,7 @@ require 'models/company' require 'models/subscriber' require 'models/keyboard' require 'models/task' +require 'models/person' class MassAssignmentSecurityTest < ActiveRecord::TestCase @@ -30,6 +31,66 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end + def test_assign_attributes_uses_default_scope_when_no_scope_is_provided + p = LoosePerson.new + p.assign_attributes(attributes_hash) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used + p = LoosePerson.new + p.assign_attributes(attributes_hash, :without_protection => true) + + assert_equal 5, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + + def test_assign_attributes_with_default_scope_and_attr_protected_attributes + p = LoosePerson.new + p.assign_attributes(attributes_hash, :as => :default) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_with_admin_scope_and_attr_protected_attributes + p = LoosePerson.new + p.assign_attributes(attributes_hash, :as => :admin) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + + def test_assign_attributes_with_default_scope_and_attr_accessible_attributes + p = TightPerson.new + p.assign_attributes(attributes_hash, :as => :default) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal nil, p.comments + end + + def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes + p = TightPerson.new + p.assign_attributes(attributes_hash, :as => :admin) + + assert_equal nil, p.id + assert_equal 'Josh', p.first_name + assert_equal 'male', p.gender + assert_equal 'rides a sweet bike', p.comments + end + def test_protection_against_class_attribute_writers [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :default_timezone, :schema_format, :lock_optimistically, :record_timestamps].each do |method| @@ -40,4 +101,14 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end + private + + def attributes_hash + { + :id => 5, + :first_name => 'Josh', + :gender => 'male', + :comments => 'rides a sweet bike' + } + end end \ No newline at end of file diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 9aa13f04cd..3683e3430c 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -12,7 +12,7 @@ require 'models/minimalistic' require 'models/warehouse_thing' require 'models/parrot' require 'models/minivan' -require 'models/loose_person' +require 'models/person' require 'rexml/document' require 'active_support/core_ext/exception' diff --git a/activerecord/test/models/loose_person.rb b/activerecord/test/models/loose_person.rb deleted file mode 100644 index 256c281d0d..0000000000 --- a/activerecord/test/models/loose_person.rb +++ /dev/null @@ -1,24 +0,0 @@ -class LoosePerson < ActiveRecord::Base - self.table_name = 'people' - self.abstract_class = true - - attr_protected :credit_rating, :administrator -end - -class LooseDescendant < LoosePerson - attr_protected :phone_number -end - -class LooseDescendantSecond< LoosePerson - attr_protected :phone_number - attr_protected :name -end - -class TightPerson < ActiveRecord::Base - self.table_name = 'people' - attr_accessible :name, :address -end - -class TightDescendant < TightPerson - attr_accessible :phone_number -end \ No newline at end of file diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index ad59d12672..9c4794902d 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -48,3 +48,22 @@ class PersonWithDependentNullifyJobs < ActiveRecord::Base has_many :references, :foreign_key => :person_id has_many :jobs, :source => :job, :through => :references, :dependent => :nullify end + + +class LoosePerson < ActiveRecord::Base + self.table_name = 'people' + self.abstract_class = true + + attr_protected :comments + attr_protected :as => :admin +end + +class LooseDescendant < LoosePerson; end + +class TightPerson < ActiveRecord::Base + self.table_name = 'people' + attr_accessible :first_name, :gender + attr_accessible :first_name, :gender, :comments, :as => :admin +end + +class TightDescendant < TightPerson; end \ No newline at end of file -- cgit v1.2.3 From b3ba36830b7c8154cbe11a3fe4a2b2574b228819 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sat, 23 Apr 2011 15:20:19 +0200 Subject: updated the security guide on the updated mass-assignment security scopes addition, and assign_attributes in AR --- railties/guides/source/security.textile | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index f4c1bde5b1..bf4f11f6b4 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -418,10 +418,17 @@ To avoid this, Rails provides two class methods in your Active Record class to c attr_protected :admin ++attr_protected+ also optionally takes a scope option using :as which allows you to define multiple mass-assignment groupings. If no scope is defined then attributes will be added to the default group. + + +attr_protected :last_login, :as => :admin + + A much better way, because it follows the whitelist-principle, is the +attr_accessible+ method. It is the exact opposite of +attr_protected+, because _(highlight)it takes a list of attributes that will be accessible_. All other attributes will be protected. This way you won't forget to protect attributes when adding new ones in the course of development. Here is an example: attr_accessible :name +attr_accessible :name, :is_admin, :as => :admin If you want to set a protected attribute, you will to have to assign it individually: @@ -434,7 +441,25 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true -A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: +When assigning attributes in Active Record using +new+, +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: + + +@user = User.new + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }) +@user.name # => Josh +@user.is_admin # => false + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin) +@user.name # => Josh +@user.is_admin # => true + +@user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true) +@user.name # => Josh +@user.is_admin # => true + + +A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: ActiveRecord::Base.send(:attr_accessible, nil) -- cgit v1.2.3 From f3b9d3aba8cc0ffaca2da1c73c4ba96de2066760 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sun, 24 Apr 2011 00:54:48 +0200 Subject: added config.active_record.whitelist_attributes which creates an empty whitelist of attributes available for mass assignment for all models in your app --- activerecord/lib/active_record/railtie.rb | 3 +++ railties/guides/source/configuring.textile | 2 ++ railties/guides/source/security.textile | 6 +++--- railties/test/application/configuration_test.rb | 12 ++++++++++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index cace6f0cc0..d38588519b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -50,6 +50,9 @@ module ActiveRecord initializer "active_record.set_configs" do |app| ActiveSupport.on_load(:active_record) do + if app.config.active_record.delete(:whitelist_attributes) + attr_accessible(nil) + end app.config.active_record.each do |k,v| send "#{k}=", v end diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 53460b8c36..d7069b31fc 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -229,6 +229,8 @@ h4. Configuring Active Record * +config.active_record.lock_optimistically+ controls whether ActiveRecord will use optimistic locking. By default this is +true+. +* +config.active_record.whitelist_attributes+ will create an empty whitelist of attributes available for mass-assignment security for all models in your app. + The MySQL adapter adds one additional configuration option: * +ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans+ controls whether ActiveRecord will consider all +tinyint(1)+ columns in a MySQL database to be booleans. By default this is +true+. diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index bf4f11f6b4..f87ffdb20d 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -459,13 +459,13 @@ When assigning attributes in Active Record using +new+, +attributes=+, or +updat @user.is_admin # => true -A more paranoid technique to protect your whole project would be to enforce that all models whitelist their accessible attributes. This can be easily achieved with a very simple initializer: +A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of: -ActiveRecord::Base.send(:attr_accessible, nil) +config.active_record.whitelist_attributes = true -This will create an empty whitelist of attributes available for mass assignment for all models in your app. As such, your models will need to explicitly whitelist accessible parameters by using an +attr_accessible+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to insert this initializer, run your tests, and expose each attribute (via +attr_accessible+) as dictated by your failing tests. +This will create an empty whitelist of attributes available for mass-assignment for all models in your app. As such, your models will need to explicitly whitelist or blacklist accessible parameters by using an +attr_accessible+ or +attr_protected+ declaration. This technique is best applied at the start of a new project. However, for an existing project with a thorough set of functional tests, it should be straightforward and relatively quick to use this application config option; run your tests, and expose each attribute (via +attr_accessible+ or +attr_protected+) as dictated by your failing tests. h3. User Management diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 62697b1bf9..ab3eb4c9e7 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -258,6 +258,18 @@ module ApplicationTests assert_equal res, last_response.body # value should be unchanged end + test "sets all Active Record models to whitelist all attributes by default" do + add_to_config <<-RUBY + config.active_record.whitelist_attributes = true + RUBY + + require "#{app_path}/config/environment" + + assert_equal ActiveModel::MassAssignmentSecurity::WhiteList, + ActiveRecord::Base.active_authorizers[:default].class + assert_equal [""], ActiveRecord::Base.active_authorizers[:default].to_a + end + test "registers interceptors with ActionMailer" do add_to_config <<-RUBY config.action_mailer.interceptors = MyMailInterceptor -- cgit v1.2.3 From bf40c729c6930ffc711760948701e5edf3edb25b Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sun, 24 Apr 2011 09:11:25 +0200 Subject: minor correction to the AMo mass-assignment security docs --- activemodel/lib/active_model/mass_assignment_security.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index e329933175..01eef762fd 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -34,7 +34,7 @@ module ActiveModel # # protected # - # def scope + # def account_params # scope = admin ? :admin : :default # sanitize_for_mass_assignment(params[:account], scope) # end -- cgit v1.2.3 From 35d0b6029fe499254d74a3cd5dc4c0397d3a50ee Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Sun, 24 Apr 2011 12:52:28 -0400 Subject: Use existing #empty_insert_statement_value for an insert with no columns. --- .../active_record/connection_adapters/abstract/database_statements.rb | 4 ---- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 4 ---- activerecord/lib/active_record/relation.rb | 2 +- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 5ff81aa023..70da9d5f1e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -287,10 +287,6 @@ module ActiveRecord execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end - def null_insert_value - Arel.sql 'DEFAULT' - end - def empty_insert_statement_value "VALUES(DEFAULT)" end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index e1518f9a0f..ed5006dcec 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -346,10 +346,6 @@ module ActiveRecord alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) end - def null_insert_value - Arel.sql 'NULL' - end - def empty_insert_statement_value "VALUES(NULL)" end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 45a7000cce..8e5f66ec1d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -60,7 +60,7 @@ module ActiveRecord end if values.empty? # empty insert - im.values = im.create_values [connection.null_insert_value], [] + im.values = Arel.sql(connection.empty_insert_statement_value) else im.insert substitutes end -- cgit v1.2.3 From c60e207674fb7844c9586f98be6ad930362b1a3d Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 24 Apr 2011 19:44:12 -0300 Subject: Better formatting here --- activerecord/lib/active_record/validations.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index d73fce9fd0..de36dd20b3 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -30,7 +30,7 @@ module ActiveRecord include ActiveModel::Validations module ClassMethods - # Creates an object just like Base.create but calls save! instead of save + # Creates an object just like Base.create but calls save! instead of +save+ # so an exception is raised if the record is invalid. def create!(attributes = nil, &block) if attributes.is_a?(Array) @@ -44,13 +44,13 @@ module ActiveRecord end end - # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is + # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. def save(options={}) perform_validations(options) ? super : false end - # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false + # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ exception instead of returning false # if the record is not valid. def save!(options={}) perform_validations(options) ? super : raise(RecordInvalid.new(self)) -- cgit v1.2.3 From ba9891f13da80bf5991c50ce53ab9db3fe852600 Mon Sep 17 00:00:00 2001 From: Stefan Huber Date: Mon, 25 Apr 2011 02:17:22 +0100 Subject: proper reset all inflector scopes --- .../lib/active_support/inflector/inflections.rb | 2 +- activesupport/test/inflector_test.rb | 42 +++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index e136e4c5b3..d5d55b7207 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -96,7 +96,7 @@ module ActiveSupport def clear(scope = :all) case scope when :all - @plurals, @singulars, @uncountables = [], [], [] + @plurals, @singulars, @uncountables, @humans = [], [], [], [] else instance_variable_set "@#{scope}", [] end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 1670d9ee7d..95f18126d4 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -255,12 +255,21 @@ class InflectorTest < Test::Unit::TestCase end def test_clear_all - 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? + cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear :all + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? + end 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] @@ -268,12 +277,21 @@ class InflectorTest < Test::Unit::TestCase end def test_clear_with_default - 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? + cached_values = ActiveSupport::Inflector.inflections.plurals.dup, ActiveSupport::Inflector.inflections.singulars.dup, ActiveSupport::Inflector.inflections.uncountables.dup, ActiveSupport::Inflector.inflections.humans.dup + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? + end 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] -- cgit v1.2.3 From 05ce0007db9361a2b777bc7378e395dafb199fd3 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 24 Apr 2011 20:34:22 -0500 Subject: Temporarily disable uglifier until a new version depending on execjs 0.2.0 can be released (tomorrow?) --- railties/lib/rails/generators/rails/app/templates/Gemfile | 2 +- .../generators/rails/app/templates/config/environments/production.rb.tt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 141d9fd15c..97c2258d09 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -8,7 +8,7 @@ source 'http://rubygems.org' <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> gem 'sass', '~> 3.1.0.alpha' gem 'coffee-script' -gem 'uglifier' +# gem 'uglifier' # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index b00f10c545..ce28e41b91 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -16,7 +16,7 @@ config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx # Compress both stylesheets and JavaScripts - config.assets.js_compressor = :uglifier + # config.assets.js_compressor = :uglifier config.assets.css_compressor = :scss # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. -- cgit v1.2.3 From a1c31eca47f19b14161b275ad6432da562a11438 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Sun, 24 Apr 2011 19:56:25 +0800 Subject: Add `app/assets/images` directory to plugin new generator --- .../rails/generators/rails/plugin_new/plugin_new_generator.rb | 10 ++++++++++ railties/test/generators/plugin_new_generator_test.rb | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 12921f47b6..5abb8d0e35 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -135,6 +135,12 @@ task :default => :test end end + def images + if mountable? || full? + empty_directory_with_gitkeep "app/assets/images" + end + end + def script(force = false) directory "script", :force => force do |content| "#{shebang}\n" + content @@ -199,6 +205,10 @@ task :default => :test build(:javascripts) end + def create_images_directory + build(:images) + end + def create_script_files build(:script) end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index d20335ad95..d12dc6925a 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -147,6 +147,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--full"] assert_file "app/assets/javascripts" assert_file "app/assets/stylesheets" + assert_file "app/assets/images" assert_file "app/models" assert_file "app/controllers" assert_file "app/views" @@ -162,6 +163,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_create_mountable_application_with_mountable_option run_generator [destination_root, "--mountable"] + assert_file "app/assets/javascripts" + assert_file "app/assets/stylesheets" + assert_file "app/assets/images" assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ -- cgit v1.2.3 From f2fc921447727a2647f6fab247569b61d546bf10 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Sun, 24 Apr 2011 20:21:22 +0800 Subject: No need to check for both full and mountable. All mountable engine are considered `full?` already. --- railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 5abb8d0e35..563bff28be 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -136,7 +136,7 @@ task :default => :test end def images - if mountable? || full? + if mountable? empty_directory_with_gitkeep "app/assets/images" end end -- cgit v1.2.3 From 8d00dfca7f6ea528ed5344b6cc1058c9f0402361 Mon Sep 17 00:00:00 2001 From: David Chelimsky Date: Sat, 23 Apr 2011 17:18:06 -0500 Subject: rename test case to better describe use case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim --- actionpack/test/controller/view_paths_test.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 9280a1c2d3..42356be1ea 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -131,8 +131,8 @@ class ViewLoadPathsTest < ActionController::TestCase assert_equal "Hello overridden world!", @response.body end - def test_override_view_paths_with_custom_resolver - resolver_class = Class.new(ActionView::PathResolver) do + def test_decorate_view_paths_with_custom_resolver + decorator_class = Class.new(ActionView::PathResolver) do def initialize(path_set) @path_set = path_set end @@ -140,7 +140,7 @@ class ViewLoadPathsTest < ActionController::TestCase def find_all(*args) @path_set.find_all(*args).collect do |template| ::ActionView::Template.new( - "Customized body", + "Decorated body", template.identifier, template.handler, { @@ -152,12 +152,12 @@ class ViewLoadPathsTest < ActionController::TestCase end end - resolver = resolver_class.new(TestController.view_paths) - TestController.view_paths = ActionView::PathSet.new.push(resolver) + decorator = decorator_class.new(TestController.view_paths) + TestController.view_paths = ActionView::PathSet.new.push(decorator) get :hello_world assert_response :success - assert_equal "Customized body", @response.body + assert_equal "Decorated body", @response.body end def test_inheritance -- cgit v1.2.3 From 2bc48561d54abaa09eeaa763f9017afb0c6ff87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 25 Apr 2011 11:14:39 +0200 Subject: Fix tests. --- .../lib/rails/generators/rails/plugin_new/plugin_new_generator.rb | 8 ++------ railties/test/generators/plugin_new_generator_test.rb | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 563bff28be..126aadb88d 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -13,11 +13,13 @@ module Rails directory "app" template "#{app_templates_dir}/app/views/layouts/application.html.erb.tt", "app/views/layouts/#{name}/application.html.erb" + empty_directory_with_gitkeep "app/assets/images" elsif full? empty_directory_with_gitkeep "app/models" empty_directory_with_gitkeep "app/controllers" empty_directory_with_gitkeep "app/views" empty_directory_with_gitkeep "app/helpers" + empty_directory_with_gitkeep "app/assets/images" end end @@ -135,12 +137,6 @@ task :default => :test end end - def images - if mountable? - empty_directory_with_gitkeep "app/assets/images" - end - end - def script(force = false) directory "script", :force => force do |content| "#{shebang}\n" + content diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index d12dc6925a..fb956a8335 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -140,7 +140,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root `bundle install` - assert_match(/2 tests, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) end def test_creating_engine_in_full_mode -- cgit v1.2.3 From 723a0f82c48e4aa7987c6ccf653b4e543a0d3715 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 25 Apr 2011 06:34:43 -0500 Subject: Go with latest Sass directly --- railties/lib/rails/generators/rails/app/templates/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 97c2258d09..9f2346028a 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -6,7 +6,7 @@ source 'http://rubygems.org' # Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> -gem 'sass', '~> 3.1.0.alpha' +gem 'sass' gem 'coffee-script' # gem 'uglifier' -- cgit v1.2.3 From e38b4436a57c43d2db78a4c8a647a09fe0e5d2c5 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 25 Apr 2011 13:12:07 +0200 Subject: Add Engine#helpers method which loads all the engine's helpers --- railties/lib/rails/engine.rb | 39 ++++++++++++++++++++++++++++++ railties/test/railties/engine_test.rb | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 87385814f7..fbd474fa1b 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -286,6 +286,27 @@ module Rails # # This code will use my_engine.user_path(@user) to generate the proper route. # + # == Isolated engine's helpers + # + # Sometimes you may want to isolate engine, but use helpers that are defined for it. + # If you want to share just a few specific helpers you can add them to application's + # helpers in ApplicationController: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::SharedEngineHelper + # end + # + # If you want to include all of the engine's helpers, you can use #helpers method on egine's + # instance: + # + # class ApplicationController < ActionController::Base + # helper MyEngine::Engine.helpers + # end + # + # It will include all of the helpers from engine's directory. Take into account that this does + # not include helpers defined in controllers with helper_method or other similar solutions, + # only helpers defined in helpers directory will be included. + # # == Migrations & seed data # # Engines can have their own migrations. The default path for migrations is exactly the same @@ -384,6 +405,24 @@ module Rails @railties ||= self.class::Railties.new(config) end + def helpers + @helpers ||= begin + helpers = Module.new + + helpers_paths = if config.respond_to?(:helpers_paths) + config.helpers_paths + else + paths["app/helpers"].existent + end + + all = ActionController::Base.send(:all_helpers_from_path, helpers_paths) + ActionController::Base.send(:modules_for_helpers, all).each do |mod| + helpers.send(:include, mod) + end + helpers + end + end + def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 7605984684..b3cf9ad449 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -584,6 +584,51 @@ module RailtiesTest assert_equal Bukkits::Engine.instance, Rails::Engine.find(engine_path) end + test "gather isolated engine's helpers in Engine#helpers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + isolate_namespace Bukkits + end + end + RUBY + + app_file "app/helpers/some_helper.rb", <<-RUBY + module SomeHelper + def foo + 'foo' + end + end + RUBY + + @plugin.write "app/helpers/bukkits/engine_helper.rb", <<-RUBY + module Bukkits + module EngineHelper + def bar + 'bar' + end + end + end + RUBY + + @plugin.write "app/helpers/engine_helper.rb", <<-RUBY + module EngineHelper + def baz + 'baz' + end + end + RUBY + + add_to_config("config.action_dispatch.show_exceptions = false") + + boot_rails + require "#{rails_root}/config/environment" + + methods = Bukkits::Engine.helpers.public_instance_methods.sort + expected = ["bar", "baz"] + assert_equal expected, methods + end + private def app Rails.application -- cgit v1.2.3 From 820c0feda33495cfe68add8c3f25adc515ab3e04 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 25 Apr 2011 14:56:58 +0200 Subject: Explicitly define main_app proxy --- actionpack/lib/action_dispatch/routing/route_set.rb | 3 +-- railties/lib/rails/application/finisher.rb | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b28f6c2297..1d09091dc7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -275,8 +275,7 @@ module ActionDispatch module MountedHelpers end - def mounted_helpers(name = :main_app) - define_mounted_helper(name) if name + def mounted_helpers MountedHelpers end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index bf865ce466..028c8814c4 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -41,6 +41,10 @@ module Rails ActionDispatch::Reloader.prepare! end + initializer :define_main_app_helper do |app| + app.routes.define_mounted_helper(:main_app) + end + initializer :eager_load! do if config.cache_classes && !$rails_rake_task ActiveSupport.run_load_hooks(:before_eager_load, self) -- cgit v1.2.3 From d4bea35f1ae458246a9e3bb1c914c5d05b8e9cdf Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 25 Apr 2011 15:02:41 +0200 Subject: Make ActionController::Base.modules_for_helpers and ActionController::Base.all_helpers_from_path public methods --- actionpack/lib/abstract_controller/helpers.rb | 22 +++++----- actionpack/lib/action_controller/metal/helpers.rb | 52 +++++++++++------------ railties/lib/rails/engine.rb | 4 +- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 20f8601a8e..0ff1c0491a 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -112,17 +112,6 @@ module AbstractController default_helper_module! unless anonymous? end - private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * module - The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end - # Returns a list of modules, normalized from the acceptable kinds of # helpers with the following behavior: # @@ -155,6 +144,17 @@ module AbstractController end end + private + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * module - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end + def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 91a88ab68a..75757db564 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -76,35 +76,35 @@ module ActionController @helper_proxy ||= ActionView::Base.new.extend(_helpers) end - private - # Overwrite modules_for_helpers to accept :all as argument, which loads - # all helpers in helpers_path. - # - # ==== Parameters - # * args - A list of helpers - # - # ==== Returns - # * array - A normalized list of modules for the list of helpers provided. - def modules_for_helpers(args) - args += all_application_helpers if args.delete(:all) - super(args) - end + # Overwrite modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_path. + # + # ==== Parameters + # * args - A list of helpers + # + # ==== Returns + # * array - A normalized list of modules for the list of helpers provided. + def modules_for_helpers(args) + args += all_application_helpers if args.delete(:all) + super(args) + end - # Extract helper names from files in app/helpers/**/*_helper.rb - def all_application_helpers - all_helpers_from_path(helpers_path) + def all_helpers_from_path(path) + helpers = [] + Array.wrap(path).each do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end + helpers.sort! + helpers.uniq! + helpers + end - def all_helpers_from_path(path) - helpers = [] - Array.wrap(path).each do |_path| - extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ - helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } - end - helpers.sort! - helpers.uniq! - helpers - end + private + # Extract helper names from files in app/helpers/**/*_helper.rb + def all_application_helpers + all_helpers_from_path(helpers_path) + end end end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index fbd474fa1b..6c1064c609 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -415,8 +415,8 @@ module Rails paths["app/helpers"].existent end - all = ActionController::Base.send(:all_helpers_from_path, helpers_paths) - ActionController::Base.send(:modules_for_helpers, all).each do |mod| + all = ActionController::Base.all_helpers_from_path(helpers_paths) + ActionController::Base.modules_for_helpers(all).each do |mod| helpers.send(:include, mod) end helpers -- cgit v1.2.3 From f545a5081ec7052dd9319cab84ab52d41ee267bd Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Mon, 25 Apr 2011 17:57:41 +0200 Subject: Fix tests, main_app mounted helper must be defined explicitly now. --- actionpack/test/dispatch/prefix_generation_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 18f28deee4..b28a058250 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -69,6 +69,7 @@ module TestGenerationPrefix # force draw RailsApplication.routes + RailsApplication.routes.define_mounted_helper(:main_app) class ::InsideEngineGeneratingController < ActionController::Base include BlogEngine.routes.url_helpers -- cgit v1.2.3 From 6464f7b9be3ccf3f05a36bc6be0de2cb8ebda97d Mon Sep 17 00:00:00 2001 From: Rashmi Yadav Date: Mon, 25 Apr 2011 23:05:17 +0530 Subject: Fixed error when running db:create with jdbcmysql --- activerecord/lib/active_record/railties/databases.rake | 8 +++++++- .../lib/active_record/railties/jdbcmysql_error.rb | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 activerecord/lib/active_record/railties/jdbcmysql_error.rb diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6b3c38cb58..a49f940e5b 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -70,7 +70,13 @@ db_namespace = namespace :db do @charset = ENV['CHARSET'] || 'utf8' @collation = ENV['COLLATION'] || 'utf8_unicode_ci' creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)} - error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + if config['adapter'] =~ /jdbc/ + #FIXME After Jdbcmysql gives this class + require 'active_record/railties/jdbcmysql_error' + error_class = ArJdbcMySQL::Error + else + error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error + end access_denied_error = 1045 begin ActiveRecord::Base.establish_connection(config.merge('database' => nil)) diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb new file mode 100644 index 0000000000..6b9af2a0cb --- /dev/null +++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb @@ -0,0 +1,16 @@ +#FIXME Remove if ArJdbcMysql will give. +module ArJdbcMySQL + class Error < StandardError + attr_accessor :error_number, :sql_state + + def initialize msg + super + @error_number = nil + @sql_state = nil + end + + # Mysql gem compatibility + alias_method :errno, :error_number + alias_method :error, :message + end +end -- cgit v1.2.3 From 75216248ef450974c6ea38358b6ea67a59dab328 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Mon, 25 Apr 2011 19:55:09 +0200 Subject: bump AS deprecation_horizon to 3.2 --- activesupport/lib/active_support/deprecation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index ce0775a690..45b9dda5ca 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -9,7 +9,7 @@ module ActiveSupport # The version the deprecated behavior will be removed, by default. attr_accessor :deprecation_horizon end - self.deprecation_horizon = '3.1' + self.deprecation_horizon = '3.2' # By default, warnings are not silenced and debugging is off. self.silenced = false -- cgit v1.2.3 From 070c9984a50a5d715a8d2cd3847ae4b603a10d19 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 25 Apr 2011 22:09:48 +0300 Subject: Do not use SQL LIKE operator for case insensitive uniqueness validation It can result in wrong results if values contain special % or _ characters. It is safer to use SQL LOWER function and compare for equality. --- .../lib/active_record/validations/uniqueness.rb | 5 +++-- .../validations/uniqueness_validation_test.rb | 26 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index d1225a9ed9..4db4105389 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -56,8 +56,9 @@ module ActiveRecord column = klass.columns_hash[attribute.to_s] value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? - if !options[:case_sensitive] && column.text? - relation = table[attribute].matches(value) + if !options[:case_sensitive] && value && column.text? + # will use SQL LOWER function before comparison + relation = table[attribute].lower.eq(table.lower(value)) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index b4f3dd034c..0f1b3667cc 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -162,6 +162,32 @@ class UniquenessValidationTest < ActiveRecord::TestCase end end + def test_validate_case_sensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => true) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + + def test_validate_case_insensitive_uniqueness_with_special_sql_like_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => false) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t2 = Topic.new("title" => "I'm %") + assert t2.save, "Should save t2 as unique" + + t3 = Topic.new("title" => "I'm uniqu_!") + assert t3.save, "Should save t3 as unique" + end + def test_validate_case_sensitive_uniqueness Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) -- cgit v1.2.3 From 16d3266144caeaca6385ed77ecd7d4e620160dec Mon Sep 17 00:00:00 2001 From: Vishnu Atrai Date: Tue, 26 Apr 2011 00:54:02 +0530 Subject: jdbcpostgresql support added into template. --- railties/lib/rails/generators/app_base.rb | 5 ++- .../templates/config/databases/jdbcpostgresql.yml | 48 ++++++++++++++++++++++ railties/test/generators/app_generator_test.rb | 6 +++ 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 46a2a3f154..3cc3762cee 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -10,7 +10,7 @@ module Rails module Generators class AppBase < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql ) DATABASES.concat(JDBC_DATABASES) JAVASCRIPTS = %w( jquery prototype ) @@ -158,7 +158,7 @@ module Rails end def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3) + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3 jdbcpostgresql ) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" @@ -166,6 +166,7 @@ module Rails when "mysql" then "mysql2" when "jdbcmysql" then "activerecord-jdbcmysql-adapter" when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" + when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" else options[:database] end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml new file mode 100644 index 0000000000..a228aca5d2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -0,0 +1,48 @@ +# PostgreSQL. Versions 7.4 and 8.x are supported. +# +# Install the pg driver: +# gem install pg +# On Mac OS X with macports: +# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Windows: +# gem install pg +# Choose the win32 build. +# Install PostgreSQL and put its /bin directory on your path. +development: + adapter: jdbcpostgresql + encoding: unicode + database: <%= app_name %>_development + username: <%= app_name %> + password: + + # Connect on a TCP socket. Omitted by default since the client uses a + # domain socket that doesn't need configuration. Windows does not have + # domain sockets, so uncomment these lines. + #host: localhost + #port: 5432 + + # Schema search path. The server defaults to $user,public + #schema_search_path: myapp,sharedapp,public + + # Minimum log levels, in increasing order: + # debug5, debug4, debug3, debug2, debug1, + # log, notice, warning, error, fatal, and panic + # The server defaults to notice. + #min_messages: warning + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: jdbcpostgresql + encoding: unicode + database: <%= app_name %>_test + username: <%= app_name %> + password: + +production: + adapter: jdbcpostgresql + encoding: unicode + database: <%= app_name %>_production + username: <%= app_name %> + password: diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 839f75d963..1902484301 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -143,6 +143,12 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ end + def test_config_jdbcpostgresql_database + run_generator([destination_root, "-d", "jdbcpostgresql"]) + assert_file "config/database.yml", /jdbcpostgresql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/ + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" -- cgit v1.2.3 From 253ba7779e62b334767b5b21f1811afcd0cb054d Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Tue, 26 Apr 2011 02:13:20 +0530 Subject: Database creation for jdbcpostgresql #jruby --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index a49f940e5b..7d76d7a19f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -100,7 +100,7 @@ db_namespace = namespace :db do $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset'] end end - when 'postgresql' + when /postgresql/ @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8' begin ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) -- cgit v1.2.3 From 873c13fc0ddc35e64a6b3abc8bde9721bd12b6b7 Mon Sep 17 00:00:00 2001 From: Diego Carrion Date: Wed, 20 Apr 2011 14:26:45 -0300 Subject: added test for number_to_phone with an empty string and area code true Signed-off-by: Santiago Pastorino --- actionpack/test/template/number_helper_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index c8d50ebf75..23a7e17e65 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -32,6 +32,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) + assert_equal("", number_to_phone("", {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) -- cgit v1.2.3 From 348b5b99380b245d45dd4a877546d1a90a85a94b Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Tue, 26 Apr 2011 02:52:25 +0530 Subject: Using sass directly as new version is out. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index c262b97819..98272c7261 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,7 @@ gem "rack-test", :git => "git://github.com/brynary/rack-test.git" gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" gem "coffee-script" -gem "sass", ">= 3.0" +gem "sass" gem "uglifier" gem "rake", ">= 0.8.7" -- cgit v1.2.3 From f9d5a7bb8c5d224f689dafb4ff641e2ced244f03 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Mon, 25 Apr 2011 23:56:06 +0200 Subject: deprecated the use of the guard_protected_attributes argument with attributes= in AR in favor of assign_attributes(attrs, :without_protection => true) --- activerecord/lib/active_record/base.rb | 23 +++++++++++++--------- .../lib/active_record/relation/finder_methods.rb | 4 ++-- activerecord/test/cases/base_test.rb | 6 ++++++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4512e8c8ad..04c12f86b6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1621,11 +1621,11 @@ end # Allows you to set all the attributes at once by passing in a hash with keys # matching the attribute names (which again matches the column names). # - # If +guard_protected_attributes+ is true (the default), then sensitive - # attributes can be protected from this form of mass-assignment by using - # the +attr_protected+ macro. Or you can alternatively specify which - # attributes *can* be accessed with the +attr_accessible+ macro. Then all the - # attributes not included in that won't be allowed to be mass-assigned. + # If any attributes are protected by either +attr_protected+ or + # +attr_accessible+ then only settable attributes will be assigned. + # + # The +guard_protected_attributes+ argument is now deprecated, use + # the +assign_attributes+ method if you want to bypass mass-assignment security. # # class User < ActiveRecord::Base # attr_protected :is_admin @@ -1635,11 +1635,16 @@ end # user.attributes = { :username => 'Phusion', :is_admin => true } # user.username # => "Phusion" # user.is_admin? # => false - # - # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) - # user.is_admin? # => true - def attributes=(new_attributes, guard_protected_attributes = true) + def attributes=(new_attributes, guard_protected_attributes = nil) + unless guard_protected_attributes.nil? + message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " + + "if you want to bypass mass-assignment security then look into using assign_attributes" + ActiveSupport::Deprecation.warn(message) + end + return unless new_attributes.is_a?(Hash) + + guard_protected_attributes ||= true if guard_protected_attributes assign_attributes(new_attributes) else diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index a3d4b7f45a..57c9921ea8 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -279,8 +279,8 @@ module ActiveRecord unless record record = @klass.new do |r| - r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty? - r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty? + r.assign_attributes(protected_attributes_for_create) + r.assign_attributes(unprotected_attributes_for_create, :without_protection => true) end yield(record) if block_given? record.save if match.instantiator == :create diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ef833857ce..5ee3b2d776 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -489,6 +489,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 'value2', weird.send('a$b') end + def test_attributes_guard_protected_attributes_is_deprecated + attributes = { "title" => "An amazing title" } + topic = Topic.new + assert_deprecated { topic.send(:attributes=, attributes, false) } + end + def test_multiparameter_attributes_on_date attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" } topic = Topic.find(1) -- cgit v1.2.3 From 474e313d02f0c8f9d821efe720bd5242e700233f Mon Sep 17 00:00:00 2001 From: Philipp Kempgen Date: Tue, 26 Apr 2011 01:04:05 +0200 Subject: test "Escapes special HTML/XML characters" do ... --- activesupport/test/core_ext/string_ext_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index bb865cae91..38b344b885 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -381,6 +381,18 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !@other_combination.html_safe? end + test "Escapes special HTML/XML characters" do + @other_string = "other".html_safe + @combination = @other_string + "&\"'" + @other_combination = @string + "&\"'" + + assert_equal "other<foo>&"'", @combination + assert_equal "hello&\"'", @other_combination + + assert @combination.html_safe? + assert !@other_combination.html_safe? + end + test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" -- cgit v1.2.3 From 057412ce38ead06307a887dca333837a99f84f22 Mon Sep 17 00:00:00 2001 From: Chad Krsek Date: Mon, 25 Apr 2011 21:57:28 -0700 Subject: asset helpers should understand scheme-relative URLs --- actionpack/lib/action_view/helpers/asset_paths.rb | 11 +++++++++-- actionpack/test/template/asset_tag_helper_test.rb | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 55a4c442fd..0429e60cd5 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -12,13 +12,13 @@ module ActionView @controller = controller end - # Add the extension +ext+ if not present. Return full URLs otherwise untouched. + # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) source = source.to_s - return source if is_uri?(source) + return source if is_uri?(source) || is_scheme_relative_uri?(source) source = rewrite_extension(source, dir, ext) if ext source = "/#{dir}/#{source}" unless source[0] == ?/ @@ -36,6 +36,13 @@ module ActionView path =~ %r{^[-a-z]+://|^cid:} end + # A URI relative to a base URI's scheme? + # See http://labs.apache.org/webarch/uri/rfc/rfc3986.html#relative-normal + # "//g" => "http://g" + def is_scheme_relative_uri?(path) + path =~ %r{^//} + end + private def rewrite_extension(source, dir, ext) diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 4a93def5a8..2abc806e97 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -66,6 +66,7 @@ class AssetTagHelperTest < ActionView::TestCase %(auto_discovery_link_tag(:xml)) => %(), %(auto_discovery_link_tag(:rss, :action => "feed")) => %(), %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(), + %(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(), %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(), %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(), %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(), @@ -100,6 +101,7 @@ class AssetTagHelperTest < ActionView::TestCase %(javascript_include_tag("http://example.com/all")) => %(), %(javascript_include_tag("http://example.com/all.js")) => %(), + %(javascript_include_tag("//example.com/all.js")) => %(), } StylePathToTag = { @@ -129,6 +131,7 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(), %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(), + %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(), } ImagePathToTag = { @@ -157,6 +160,7 @@ class AssetTagHelperTest < ActionView::TestCase %(image_tag("slash..png")) => %(Slash.), %(image_tag(".pdf.png")) => %(.pdf), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(Rails), + %(image_tag("//www.rubyonrails.com/images/rails.png")) => %(Rails), %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(Mouse), %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(Mouse), %(image_tag("mouse.png", :alt => nil)) => %() @@ -195,6 +199,7 @@ class AssetTagHelperTest < ActionView::TestCase %(video_tag("error.avi", "size" => "100 x 100")) => %( -Sometimes your migration will do something which is just plain irreversible, for example it might destroy some data. In cases like those when you can't reverse the migration you can raise +IrreversibleMigration+ from your +down+ method. If someone tries to revert your migration an error message will be -displayed saying that it can't be done. +Sometimes your migration will do something which is just plain irreversible, for example it might destroy some data. In cases like those when you can't reverse the migration you can raise +IrreversibleMigration+ from your +down+ method. If someone tries to revert your migration an error message will be displayed saying that it can't be done. h3. Running Migrations @@ -449,7 +462,7 @@ For example, this migration class CreateProducts < ActiveRecord::Migration - def self.up + def change suppress_messages do create_table :products do |t| t.string :name @@ -465,10 +478,6 @@ class CreateProducts < ActiveRecord::Migration 250 end end - - def self.down - drop_table :products - end end @@ -499,11 +508,7 @@ class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end - def self.up - ... - end - - def self.down + def change ... end end @@ -519,15 +524,11 @@ class AddPartNumberToProducts < ActiveRecord::Migration class Product < ActiveRecord::Base end - def self.up + def change add_column :product, :part_number, :string Product.reset_column_information ... end - - def self.down - ... - end end @@ -590,5 +591,6 @@ Although Active Record does not provide any tools for working directly with such h3. Changelog +* April 26, 2011: change generated +up+ and +down+ methods to +change+ method, and describe detail about +change+ method by "Prem Sichanugrist":http://sikachu.com * July 15, 2010: minor typos corrected by "Jaime Iniesta":http://jaimeiniesta.com * September 14, 2008: initial version by "Frederick Cheung":credits.html#fcheung -- cgit v1.2.3 From d4259d8932c9fe4128bd4b0876f9e48085035032 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 26 Apr 2011 23:33:39 +0700 Subject: Change from `self.(up|down)` to `(up|down)` method --- railties/guides/source/getting_started.textile | 2 +- railties/guides/source/migrations.textile | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 63bb38318c..a826a33120 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -1450,7 +1450,7 @@ Two very common sources of data that are not UTF-8: h3. Changelog -* April 26, 2011: Changed migration code from +self.up+, +self.down+ pair to +change+ method "Prem Sichanugrist":"http://sikachu.com" +* April 26, 2011: Changed migration code from +up+, +down+ pair to +change+ method "Prem Sichanugrist":"http://sikachu.com" * April 11, 2011: Changed scaffold_controller generator to create format block for JSON instead of XML "Sebastian Martinez":http://www.wyeworks.com * August 30, 2010: Minor editing after Rails 3 release by "Joost Baaij":http://www.spacebabies.nl * July 12, 2010: Fixes, editing and updating of code samples by "Jaime Iniesta":http://jaimeiniesta.com diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 238fe3603b..39f8a458a0 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -21,7 +21,7 @@ Before I dive into the details of a migration, here are a few examples of the so class CreateProducts < ActiveRecord::Migration - def self.up + def up create_table :products do |t| t.string :name t.text :description @@ -30,7 +30,7 @@ class CreateProducts < ActiveRecord::Migration end end - def self.down + def down drop_table :products end end @@ -42,14 +42,14 @@ Migrations are not limited to changing the schema. You can also use them to fix class AddReceiveNewsletterToUsers < ActiveRecord::Migration - def self.up + def up change_table :users do |t| t.boolean :receive_newsletter, :default => false end User.update_all ["receive_newsletter = ?", true] end - def self.down + def down remove_column :users, :receive_newsletter end end @@ -190,11 +190,11 @@ generates class RemovePartNumberFromProducts < ActiveRecord::Migration - def self.up + def up remove_column :products, :part_number end - def self.down + def down add_column :products, :part_number, :string end end @@ -358,7 +358,7 @@ The +down+ method of your migration should revert the transformations done by th class ExampleMigration < ActiveRecord::Migration - def self.up + def up create_table :products do |t| t.references :category end @@ -375,7 +375,7 @@ class ExampleMigration < ActiveRecord::Migration rename_column :users, :email, :email_address end - def self.down + def down rename_column :users, :email_address, :email remove_column :users, :home_page_url execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories" -- cgit v1.2.3 From b49a1192d3d7f2dffff742fc154c8b3d2533b621 Mon Sep 17 00:00:00 2001 From: Chad Krsek Date: Mon, 25 Apr 2011 21:57:28 -0700 Subject: asset helpers should understand scheme-relative URLs --- actionpack/lib/action_view/helpers/asset_paths.rb | 11 +++++++++-- actionpack/test/template/asset_tag_helper_test.rb | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 55a4c442fd..0429e60cd5 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -12,13 +12,13 @@ module ActionView @controller = controller end - # Add the extension +ext+ if not present. Return full URLs otherwise untouched. + # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. # Prefix with /dir/ if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) source = source.to_s - return source if is_uri?(source) + return source if is_uri?(source) || is_scheme_relative_uri?(source) source = rewrite_extension(source, dir, ext) if ext source = "/#{dir}/#{source}" unless source[0] == ?/ @@ -36,6 +36,13 @@ module ActionView path =~ %r{^[-a-z]+://|^cid:} end + # A URI relative to a base URI's scheme? + # See http://labs.apache.org/webarch/uri/rfc/rfc3986.html#relative-normal + # "//g" => "http://g" + def is_scheme_relative_uri?(path) + path =~ %r{^//} + end + private def rewrite_extension(source, dir, ext) diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 4a93def5a8..2abc806e97 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -66,6 +66,7 @@ class AssetTagHelperTest < ActionView::TestCase %(auto_discovery_link_tag(:xml)) => %(), %(auto_discovery_link_tag(:rss, :action => "feed")) => %(), %(auto_discovery_link_tag(:rss, "http://localhost/feed")) => %(), + %(auto_discovery_link_tag(:rss, "//localhost/feed")) => %(), %(auto_discovery_link_tag(:rss, {:action => "feed"}, {:title => "My RSS"})) => %(), %(auto_discovery_link_tag(:rss, {}, {:title => "My RSS"})) => %(), %(auto_discovery_link_tag(nil, {}, {:type => "text/html"})) => %(), @@ -100,6 +101,7 @@ class AssetTagHelperTest < ActionView::TestCase %(javascript_include_tag("http://example.com/all")) => %(), %(javascript_include_tag("http://example.com/all.js")) => %(), + %(javascript_include_tag("//example.com/all.js")) => %(), } StylePathToTag = { @@ -129,6 +131,7 @@ class AssetTagHelperTest < ActionView::TestCase %(stylesheet_link_tag("http://www.example.com/styles/style")) => %(), %(stylesheet_link_tag("http://www.example.com/styles/style.css")) => %(), + %(stylesheet_link_tag("//www.example.com/styles/style.css")) => %(), } ImagePathToTag = { @@ -157,6 +160,7 @@ class AssetTagHelperTest < ActionView::TestCase %(image_tag("slash..png")) => %(Slash.), %(image_tag(".pdf.png")) => %(.pdf), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(Rails), + %(image_tag("//www.rubyonrails.com/images/rails.png")) => %(Rails), %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(Mouse), %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(Mouse), %(image_tag("mouse.png", :alt => nil)) => %() @@ -195,6 +199,7 @@ class AssetTagHelperTest < ActionView::TestCase %(video_tag("error.avi", "size" => "100 x 100")) => %( -NOTE: The generated migration file for destructive migration will be created using the old-style migration with +up+ and +down+ method. This because Rails doesn't know the original data type defined when you added the column. - You are not limited to one magically generated column, for example @@ -221,6 +217,8 @@ end As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit. +NOTE: The generated migration file for destructive migrations will still be old-style using the +up+ and +down+ methods. This is because Rails doesn't know the original data types defined when you made the original changes. + h3. Writing a Migration Once you have created your migration using one of the generators it's time to get to work! @@ -342,14 +340,14 @@ For more details and examples of individual methods check the API documentation, h4. Writing Your +change+ Method -The +change+ method of your migration reduce the need for you having to write both +up+ and +down+ method in some case that Rails knows how to revert it. Rails will revert the changes automatically when you rollback your change. Currently, +change+ method only support these migration definitions: +The +change+ method removes the need to write both +up+ and +down+ methods in those cases that Rails know how to revert the changes automatically. Currently, the +change+ method supports only these migration definitions: * +create_table+ * +add_column+ * +rename_column+ * +add_index+ -If you're going to use another methods, you'll have to write both +up+ and +down+ method normally. +If you're going to use other methods, you'll have to write the +up+ and +down+ methods normally. h4. Writing Your +down+ Method -- cgit v1.2.3 From 0421002d78b0b41c5a978568910f6dacacc6888f Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 26 Apr 2011 14:58:06 -0300 Subject: Fix #update_attributes api format error --- activerecord/lib/active_record/persistence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 998d237ada..787ac977e0 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -138,7 +138,7 @@ module ActiveRecord # will fail and false will be returned. # # When updating model attributes, mass-assignment security protection is respected. - # If no +:as+ option is supplied then the :default scope will be used. + # If no +:as+ option is supplied then the +:default+ scope will be used. # If you want to bypass the protection given by +attr_protected+ and # +attr_accessible+ then you can do so using the +:without_protection+ option. def update_attributes(attributes, options = {}) -- cgit v1.2.3 From c1132f7093b29e6acb95be4d262a07d5b04082d3 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Tue, 26 Apr 2011 23:40:47 +0530 Subject: added the list of reversible commands in the newer migrations --- railties/guides/source/migrations.textile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/migrations.textile b/railties/guides/source/migrations.textile index 18ae8a2251..27f8a9303e 100644 --- a/railties/guides/source/migrations.textile +++ b/railties/guides/source/migrations.textile @@ -342,10 +342,14 @@ h4. Writing Your +change+ Method The +change+ method removes the need to write both +up+ and +down+ methods in those cases that Rails know how to revert the changes automatically. Currently, the +change+ method supports only these migration definitions: -* +create_table+ * +add_column+ -* +rename_column+ * +add_index+ +* +add_timestamp+ +* +create_table+ +* +remove_timestamps+ +* +rename_column+ +* +rename_index+ +* +rename_table+ If you're going to use other methods, you'll have to write the +up+ and +down+ methods normally. -- cgit v1.2.3 From c57320d769346288326534999f2ed6e10368e525 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 26 Apr 2011 12:46:28 -0600 Subject: Prem fixes 1.9 engine test issue --- railties/test/railties/engine_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index b3cf9ad449..0c588ba773 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -624,7 +624,7 @@ module RailtiesTest boot_rails require "#{rails_root}/config/environment" - methods = Bukkits::Engine.helpers.public_instance_methods.sort + methods = Bukkits::Engine.helpers.public_instance_methods.collect(&:to_s).sort expected = ["bar", "baz"] assert_equal expected, methods end -- cgit v1.2.3 From c15108ce063f6396a6e93a54e973b858d0280d41 Mon Sep 17 00:00:00 2001 From: Chad Krsek Date: Tue, 26 Apr 2011 11:50:08 -0700 Subject: moving check fo scheme-relative URI into is_uri? --- actionpack/lib/action_view/helpers/asset_paths.rb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 0429e60cd5..cb6737b94e 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -18,7 +18,7 @@ module ActionView # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) source = source.to_s - return source if is_uri?(source) || is_scheme_relative_uri?(source) + return source if is_uri?(source) source = rewrite_extension(source, dir, ext) if ext source = "/#{dir}/#{source}" unless source[0] == ?/ @@ -33,14 +33,7 @@ module ActionView end def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:} - end - - # A URI relative to a base URI's scheme? - # See http://labs.apache.org/webarch/uri/rfc/rfc3986.html#relative-normal - # "//g" => "http://g" - def is_scheme_relative_uri?(path) - path =~ %r{^//} + path =~ %r{^[-a-z]+://|^cid:|^//} end private -- cgit v1.2.3 From c8102817ca0d355ece8b60007d230ab9292e7031 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Wed, 27 Apr 2011 00:57:50 +0530 Subject: Re-factored database.rake to allow Jdbcpostgresql. #jruby --- activerecord/lib/active_record/railties/databases.rake | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 7d76d7a19f..1fc69130c7 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -244,7 +244,7 @@ db_namespace = namespace :db do when /mysql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.charset - when 'postgresql' + when /postgresql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding when 'sqlite3' @@ -362,7 +362,7 @@ db_namespace = namespace :db do when /mysql/, "oci", "oracle" ActiveRecord::Base.establish_connection(abcs[Rails.env]) File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } - when "postgresql" + 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"] ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"] @@ -413,7 +413,7 @@ db_namespace = namespace :db do IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end - when "postgresql" + 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"] @@ -444,7 +444,7 @@ db_namespace = namespace :db do when /mysql/ ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) - when "postgresql" + when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) create_database(abcs['test']) @@ -534,7 +534,7 @@ def drop_database(config) file = path.absolute? ? path.to_s : File.join(Rails.root, path) FileUtils.rm(file) - when 'postgresql' + when /postgresql/ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.drop_database config['database'] end -- cgit v1.2.3 From 74634d452b3087575fe7b912da9827021aed4a70 Mon Sep 17 00:00:00 2001 From: Jared McFarland Date: Tue, 26 Apr 2011 14:46:52 -0700 Subject: fixing Array#from to return consistent results --- activesupport/lib/active_support/core_ext/array/access.rb | 4 ++-- activesupport/test/core_ext/array_ext_test.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index c69a015f12..a2086fa3e5 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -3,10 +3,10 @@ class Array # # %w( a b c d ).from(0) # => %w( a b c d ) # %w( a b c d ).from(2) # => %w( c d ) - # %w( a b c d ).from(10) # => nil + # %w( a b c d ).from(10) # => %w() # %w().from(0) # => %w() def from(position) - self[position..-1] + position > length ? [] : self[position..-1] end # Returns the beginning of the array up to +position+. diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index d7ab3ce605..0e5407bc35 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -10,7 +10,7 @@ class ArrayExtAccessTests < Test::Unit::TestCase def test_from assert_equal %w( a b c d ), %w( a b c d ).from(0) assert_equal %w( c d ), %w( a b c d ).from(2) - assert_nil %w( a b c d ).from(10) + assert_equal %w(), %w( a b c d ).from(10) end def test_to -- cgit v1.2.3 From fee69cb02150d3ce4c94b4b47751fd8959a7bf13 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 27 Apr 2011 00:28:00 +0200 Subject: fix mass-assignment security tests, this was due to a string column limit which doesn't cause issues on sqlite --- .../test/cases/mass_assignment_security_test.rb | 18 +++++++++--------- activerecord/test/cases/persistence_test.rb | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 43016df479..2c051bff84 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -35,10 +35,10 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase p = LoosePerson.new p.assign_attributes(attributes_hash) - assert_equal nil, p.id + assert_equal nil, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender - assert_equal nil, p.comments + assert_equal 'm', p.gender + assert_equal nil, p.comments end def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used @@ -47,7 +47,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_equal 5, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender + assert_equal 'm', p.gender assert_equal 'rides a sweet bike', p.comments end @@ -57,7 +57,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_equal nil, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender + assert_equal 'm', p.gender assert_equal nil, p.comments end @@ -67,7 +67,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_equal nil, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender + assert_equal 'm', p.gender assert_equal 'rides a sweet bike', p.comments end @@ -77,7 +77,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_equal nil, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender + assert_equal 'm', p.gender assert_equal nil, p.comments end @@ -87,7 +87,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase assert_equal nil, p.id assert_equal 'Josh', p.first_name - assert_equal 'male', p.gender + assert_equal 'm', p.gender assert_equal 'rides a sweet bike', p.comments end @@ -107,7 +107,7 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase { :id => 5, :first_name => 'Josh', - :gender => 'male', + :gender => 'm', :comments => 'rides a sweet bike' } end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 2044bc6e3f..7891ccfeef 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -493,21 +493,21 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_attributes_as_admin person = TightPerson.create - person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin) + person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) person.reload - assert_equal 'Josh', person.first_name - assert_equal 'male', person.gender + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender assert_equal 'from NZ', person.comments end def test_update_attributes_as_without_protection person = TightPerson.create - person.update_attributes({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true) + person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) person.reload - assert_equal 'Josh', person.first_name - assert_equal 'male', person.gender + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender assert_equal 'from NZ', person.comments end -- cgit v1.2.3 From 24e4ea582baa5f21b7262b893cbb4d852c329e75 Mon Sep 17 00:00:00 2001 From: "Paolo \"Nusco\" Perrotta" Date: Tue, 26 Apr 2011 16:15:20 -0700 Subject: Fixed minor typo in comment --- activeresource/test/connection_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 6e79845aa0..7c36393cf2 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -50,7 +50,7 @@ class ConnectionTest < Test::Unit::TestCase # 404 is a missing resource. assert_response_raises ActiveResource::ResourceNotFound, 404 - # 405 is a missing not allowed error + # 405 is a method not allowed error assert_response_raises ActiveResource::MethodNotAllowed, 405 # 409 is an optimistic locking error -- cgit v1.2.3 From a2bfa5fa305d7eebfee1bcb2c556112a3e03f2c2 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 26 Apr 2011 23:26:10 -0300 Subject: Better formatting on ActiveModel::Translation --- activemodel/lib/active_model/translation.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 920a133159..6d64c81b5f 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -18,12 +18,12 @@ module ActiveModel # # This also provides the required class methods for hooking into the # Rails internationalization API, including being able to define a - # class based i18n_scope and lookup_ancestors to find translations in + # class based +i18n_scope+ and +lookup_ancestors+ to find translations in # parent classes. module Translation include ActiveModel::Naming - # Returns the i18n_scope for the class. Overwrite if you want custom lookup. + # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup. def i18n_scope :activemodel end -- cgit v1.2.3 From 4554983ab18ec60a3575f469125c3b0d98df4e33 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 26 Apr 2011 23:29:11 -0300 Subject: Removed extra whitespace --- activemodel/lib/active_model/naming.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 315ccd40e9..74708692af 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -68,7 +68,7 @@ module ActiveModel # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover" # # Providing the functionality that ActiveModel::Naming provides in your object - # is required to pass the Active Model Lint test. So either extending the provided + # is required to pass the Active Model Lint test. So either extending the provided # method below, or rolling your own is required. module Naming # Returns an ActiveModel::Name object for module. It can be -- cgit v1.2.3 From c4b494bdc44a7aaa6b38b512d0bf6cfb8f1cfb15 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Tue, 26 Apr 2011 23:37:11 -0300 Subject: Rephrased Dirty#changed? docs --- activemodel/lib/active_model/dirty.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 5ede78617a..3b412d3dd7 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -93,7 +93,7 @@ module ActiveModel attribute_method_affix :prefix => 'reset_', :suffix => '!' end - # Do any attributes have unsaved changes? + # Returns true if any attribute have unsaved changes, false otherwise. # person.changed? # => false # person.name = 'bob' # person.changed? # => true -- cgit v1.2.3 From 8fd998284db30d26f210e220b546b64bb4a3b380 Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Wed, 27 Apr 2011 13:51:07 +0530 Subject: Allowing jdbcsqlite3 to drop database, clone structure. #jruby --- activerecord/lib/active_record/railties/databases.rake | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 1fc69130c7..3bf2ba38c9 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -247,7 +247,7 @@ db_namespace = namespace :db do when /postgresql/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding - when 'sqlite3' + when /sqlite/ ActiveRecord::Base.establish_connection(config) puts ActiveRecord::Base.connection.encoding else @@ -372,9 +372,9 @@ db_namespace = namespace :db do end `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}` raise "Error dumping database" if $?.exitstatus == 1 - when "sqlite", "sqlite3" + when /sqlite/ dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"] - `#{abcs[Rails.env]["adapter"]} #{dbfile} .schema > db/#{Rails.env}_structure.sql` + `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` when "sqlserver" `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r` `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r` @@ -418,9 +418,9 @@ db_namespace = namespace :db do 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 #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}` - when "sqlite", "sqlite3" + when /sqlite/ dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` + `sqlite3 #{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" @@ -448,7 +448,7 @@ db_namespace = namespace :db do ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) create_database(abcs['test']) - when "sqlite","sqlite3" + when /sqlite/ dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] File.delete(dbfile) if File.exist?(dbfile) when "sqlserver" @@ -528,7 +528,7 @@ def drop_database(config) when /mysql/ ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config['database'] - when /^sqlite/ + when /sqlite/ require 'pathname' path = Pathname.new(config['database']) file = path.absolute? ? path.to_s : File.join(Rails.root, path) -- cgit v1.2.3 From bd2444d1a887b310489a0e562d65dca309c1a141 Mon Sep 17 00:00:00 2001 From: Ganesh Kumar Date: Wed, 27 Apr 2011 19:14:42 +0800 Subject: enable uglifier gem as new version of execjs is relased --- railties/lib/rails/generators/rails/app/templates/Gemfile | 2 +- .../generators/rails/app/templates/config/environments/production.rb.tt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 9f2346028a..8ad64e38ed 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -8,7 +8,7 @@ source 'http://rubygems.org' <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> gem 'sass' gem 'coffee-script' -# gem 'uglifier' +gem 'uglifier' # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index ce28e41b91..b00f10c545 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -16,7 +16,7 @@ config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx # Compress both stylesheets and JavaScripts - # config.assets.js_compressor = :uglifier + config.assets.js_compressor = :uglifier config.assets.css_compressor = :scss # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. -- cgit v1.2.3 From d56befbd318967e28b44d17ba4d64f770030107e Mon Sep 17 00:00:00 2001 From: Jared McFarland Date: Wed, 27 Apr 2011 06:48:53 +0800 Subject: using @tenderlove's suggested implementation to speed things up --- activesupport/lib/active_support/core_ext/array/access.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index a2086fa3e5..2bba0974b3 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -6,7 +6,7 @@ class Array # %w( a b c d ).from(10) # => %w() # %w().from(0) # => %w() def from(position) - position > length ? [] : self[position..-1] + [position, length] || [] end # Returns the beginning of the array up to +position+. -- cgit v1.2.3 From a009c3b4b9aff278b6263b5fb697ed0440b8dbdf Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 27 Apr 2011 21:50:21 +0800 Subject: minor git sources formatting change in railties app_base.rb --- railties/lib/rails/generators/app_base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 3cc3762cee..520d2c6a3a 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -135,14 +135,14 @@ module Rails gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git" + gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git" + gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE else <<-GEMFILE.strip_heredoc @@ -152,7 +152,7 @@ module Rails # gem 'rails', :git => 'git://github.com/rails/rails.git' # gem 'arel', :git => 'git://github.com/rails/arel.git' # gem 'rack', :git => 'git://github.com/rack/rack.git' - # gem 'sprockets', :git => "git://github.com/sstephenson/sprockets.git" + # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE end end -- cgit v1.2.3 From f76dd271c6c6a3ae613a37c5a3b8b13b945b5d66 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 27 Apr 2011 00:30:31 +0530 Subject: minor changes --- activesupport/lib/active_support/deprecation/reporting.rb | 4 +--- activesupport/lib/active_support/notifications/fanout.rb | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index ced08b8783..5d7e241d1a 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -5,10 +5,8 @@ module ActiveSupport # Outputs a deprecation warning to the output configured by ActiveSupport::Deprecation.behavior # - # Example: - # # ActiveSupport::Deprecation.warn("something broke!") - # #=> "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" def warn(message = nil, callstack = caller) return if silenced deprecation_message(callstack, message).tap do |m| diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index adc34f3286..a9aa5464e9 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,7 +1,7 @@ module ActiveSupport module Notifications - # This is a default queue implementation that ships with Notifications. It - # just pushes events to all registered log subscribers. + # This is a default queue implementation that ships with Notifications. + # It just pushes events to all registered log subscribers. class Fanout def initialize @subscribers = [] @@ -33,7 +33,7 @@ module ActiveSupport listeners_for(name).any? end - # This is a sync queue, so there is not waiting. + # This is a sync queue, so there is no waiting. def wait end -- cgit v1.2.3 From e84788e60c179ad0cd4a13d54e7c7a50c68d748a Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 27 Apr 2011 08:55:26 -0600 Subject: Relevant options closer together --- .../rails/app/templates/config/environments/production.rb.tt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index b00f10c545..9553f3bdde 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -11,14 +11,14 @@ # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false - # Specifies the header that your server uses for sending files - # (comment out if your front-end server doesn't support this) - config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx - # Compress both stylesheets and JavaScripts config.assets.js_compressor = :uglifier config.assets.css_compressor = :scss + # Specifies the header that your server uses for sending files + # (comment out if your front-end server doesn't support this) + config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true -- cgit v1.2.3 From 11537c57cbf91c0514fac1c40bdf362ba5deef33 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 27 Apr 2011 17:35:00 +0200 Subject: fixed a small bug with Array#from core_ext --- activesupport/lib/active_support/core_ext/array/access.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 2bba0974b3..2df4fd1da1 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -6,7 +6,7 @@ class Array # %w( a b c d ).from(10) # => %w() # %w().from(0) # => %w() def from(position) - [position, length] || [] + self[position, length] || [] end # Returns the beginning of the array up to +position+. -- cgit v1.2.3 From 089ee31aadd881a59ad60b6dcb406600dc331bfd Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 27 Apr 2011 22:21:50 +0530 Subject: update the Array#from behaviour changes - returns [] if index exceeds array length --- railties/guides/source/active_support_core_extensions.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 7bda1760a9..f2170e120b 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -1996,11 +1996,11 @@ Active Support augments the API of arrays to ease certain ways of accessing them [].to(7) # => [] -Similarly, +from+ returns the tail from the element at the passed index on: +Similarly, +from+ returns the tail from the element at the passed index to the end. If the index is greater than the length of the array, it returns an empty array. %w(a b c d).from(2) # => %w(c d) -%w(a b c d).from(10) # => nil +%w(a b c d).from(10) # => [] [].from(0) # => [] -- cgit v1.2.3 From ffc9374afd47c9bc14bce45a134b62eb24b2263c Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Wed, 27 Apr 2011 23:09:45 +0530 Subject: Minor fixes in databases.rake --- .../lib/active_record/railties/databases.rake | 142 ++++++++++----------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 3bf2ba38c9..0502f51170 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -4,11 +4,11 @@ db_namespace = namespace :db do task :load_config => :rails_env do require 'active_record' ActiveRecord::Base.configurations = Rails.application.config.database_configuration - ActiveRecord::Migrator.migrations_paths = Rails.application.paths["db/migrate"].to_a + ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - if engine.paths["db/migrate"].existent - ActiveRecord::Migrator.migrations_paths += engine.paths["db/migrate"].to_a + if engine.paths['db/migrate'].existent + ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a end end end @@ -143,7 +143,7 @@ db_namespace = namespace :db do end def local_database?(config, &block) - if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank? + if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank? yield else $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host." @@ -161,35 +161,35 @@ db_namespace = namespace :db do namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' task :redo => [:environment, :load_config] do - if ENV["VERSION"] - db_namespace["migrate:down"].invoke - db_namespace["migrate:up"].invoke + if ENV['VERSION'] + db_namespace['migrate:down'].invoke + db_namespace['migrate:up'].invoke else - db_namespace["rollback"].invoke - db_namespace["migrate"].invoke + db_namespace['rollback'].invoke + db_namespace['migrate'].invoke end end # desc 'Resets your database using your migrations for the current environment' - task :reset => ["db:drop", "db:create", "db:migrate"] + task :reset => ['db:drop', 'db:create', 'db:migrate'] # desc 'Runs the "up" for a given migration VERSION.' task :up => [:environment, :load_config] do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version + version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil + raise 'VERSION is required' unless version ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Runs the "down" for a given migration VERSION.' task :down => [:environment, :load_config] do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - raise "VERSION is required" unless version + version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil + raise 'VERSION is required' unless version ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end - desc "Display status of migrations" + desc 'Display status of migrations' task :status => [:environment, :load_config] do config = ActiveRecord::Base.configurations[Rails.env || 'development'] ActiveRecord::Base.establish_connection(config) @@ -208,7 +208,7 @@ db_namespace = namespace :db do end # output puts "\ndatabase: #{config['database']}\n\n" - puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" puts "-" * 50 file_list.each do |file| puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}" @@ -224,14 +224,14 @@ db_namespace = namespace :db do task :rollback => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' task :forward => [:environment, :load_config] do step = ENV['STEP'] ? ENV['STEP'].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' @@ -267,7 +267,7 @@ db_namespace = namespace :db do end end - desc "Retrieves the current schema version number" + desc 'Retrieves the current schema version number' task :version => :environment do puts "Current version: #{ActiveRecord::Migrator.current_version}" end @@ -313,8 +313,8 @@ db_namespace = namespace :db do task :identify => :environment do require 'active_record/fixtures' - label, id = ENV["LABEL"], ENV["ID"] - raise "LABEL or ID required" if label.blank? && id.blank? + label, id = ENV['LABEL'], ENV['ID'] + raise 'LABEL or ID required' if label.blank? && id.blank? puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label @@ -334,16 +334,16 @@ db_namespace = namespace :db do end namespace :schema do - desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" + desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' task :dump => :load_config do require 'active_record/schema_dumper' File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end - db_namespace["schema:dump"].reenable + db_namespace['schema:dump'].reenable end - desc "Load a schema.rb file into the database" + desc 'Load a schema.rb file into the database' task :load => :environment do file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" if File.exists?(file) @@ -355,29 +355,29 @@ db_namespace = namespace :db do end namespace :structure do - desc "Dump the database structure to an SQL file" + desc 'Dump the database structure to an SQL file' task :dump => :environment do abcs = ActiveRecord::Base.configurations - case abcs[Rails.env]["adapter"] - when /mysql/, "oci", "oracle" + case abcs[Rails.env]['adapter'] + when /mysql/, 'oci', 'oracle' ActiveRecord::Base.establish_connection(abcs[Rails.env]) File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } when /postgresql/ - ENV['PGHOST'] = abcs[Rails.env]["host"] if abcs[Rails.env]["host"] - ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]["port"] - ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"] - search_path = abcs[Rails.env]["schema_search_path"] + ENV['PGHOST'] = abcs[Rails.env]['host'] if abcs[Rails.env]['host'] + ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]['port'] + ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password'] + search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") end - `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}` - raise "Error dumping database" if $?.exitstatus == 1 + `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}` + raise 'Error dumping database' if $?.exitstatus == 1 when /sqlite/ - dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"] + dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r` + when 'sqlserver' + `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` + `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -397,16 +397,16 @@ db_namespace = namespace :db do task :load => 'db:test:purge' do ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false - db_namespace["schema:load"].invoke + db_namespace['schema:load'].invoke end # desc "Recreate the test database from the current environment's database schema" task :clone => %w(db:schema:dump db:test:load) # desc "Recreate the test databases from the development structure" - task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do + task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] + case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') @@ -414,64 +414,64 @@ db_namespace = namespace :db do 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 #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}` + 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 #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}` when /sqlite/ - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + dbfile = abcs['test']['database'] || abcs['test']['dbfile'] `sqlite3 #{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" + 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("#{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"]) + when 'firebird' + set_firebird_env(abcs['test']) + db_string = firebird_db_string(abcs['test']) sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}" else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" + raise "Task not supported by '#{abcs['test']['adapter']}'" end end # desc "Empty the test database" task :purge => :environment do abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] + case abcs['test']['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"]) + ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test']) when /postgresql/ ActiveRecord::Base.clear_active_connections! drop_database(abcs['test']) create_database(abcs['test']) when /sqlite/ - dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] + dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) - when "sqlserver" - dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql` + when 'sqlserver' + dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') + `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` + `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end - when "firebird" + when 'firebird' ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.recreate_database! else - raise "Task not supported by '#{abcs["test"]["adapter"]}'" + raise "Task not supported by '#{abcs['test']['adapter']}'" end end # desc 'Check for pending migrations and load the test schema' task :prepare => 'db:abort_if_pending_migrations' do if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank? - db_namespace[{ :sql => "test:clone_structure", :ruby => "test:load" }[ActiveRecord::Base.schema_format]].invoke + db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke end end end @@ -479,11 +479,11 @@ db_namespace = namespace :db do namespace :sessions do # desc "Creates a sessions migration for use with ActiveRecord::SessionStore" task :create => :environment do - raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations? + raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations? require 'rails/generators' Rails::Generators.configure! require 'rails/generators/rails/session_migration/session_migration_generator' - Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ] + Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ] end # desc "Clear the sessions table" @@ -496,13 +496,13 @@ end namespace :railties do namespace :install do # desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2" - task :migrations => :"db:load_config" do - to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip } + task :migrations => :'db:load_config' do + to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } railties = {} Rails.application.railties.all do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) - if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first) + if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) railties[railtie.railtie_name] = path end end @@ -545,8 +545,8 @@ def session_table_name end def set_firebird_env(config) - ENV["ISC_USER"] = config["username"].to_s if config["username"] - ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"] + ENV['ISC_USER'] = config['username'].to_s if config['username'] + ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] end def firebird_db_string(config) -- cgit v1.2.3 From e85dbd8f06873185d08ca43647e8485e7d4eddf2 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 27 Apr 2011 20:57:56 +0200 Subject: final corrections to the mass-assignment security tests --- activerecord/test/cases/persistence_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 7891ccfeef..5567602a26 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -534,21 +534,21 @@ class PersistencesTest < ActiveRecord::TestCase def test_update_attributes_as_admin person = TightPerson.create - person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :as => :admin) + person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) person.reload assert_equal 'Josh', person.first_name - assert_equal 'male', person.gender + assert_equal 'm', person.gender assert_equal 'from NZ', person.comments end def test_update_attributes_as_without_protection person = TightPerson.create - person.update_attributes!({ "first_name" => 'Josh', "gender" => 'male', "comments" => 'from NZ' }, :without_protection => true) + person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) person.reload assert_equal 'Josh', person.first_name - assert_equal 'male', person.gender + assert_equal 'm', person.gender assert_equal 'from NZ', person.comments end -- cgit v1.2.3 From 9f6cafd5fd43b551f30b28d276713791c5098b3c Mon Sep 17 00:00:00 2001 From: misfo Date: Tue, 8 Feb 2011 12:08:35 +0800 Subject: prevent errors when passing a frozen string as a param to ActionController::TestCase#process since ActionDispatch::Http::Parameters#encode_params will force encoding on all params strings (when using an encoding aware Ruby), dup all strings passed into process. This prevents modification of params passed in and, more importantly, doesn't barf when a frozen string is passed thanks and high fives to kinsteronline --- actionpack/lib/action_controller/test_case.rb | 4 +++- actionpack/test/controller/test_test.rb | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index bc4f8bb9ce..0085f542aa 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -147,7 +147,9 @@ module ActionController if value.is_a? Fixnum value = value.to_s elsif value.is_a? Array - value = Result.new(value) + value = Result.new(value.map { |v| v.is_a?(String) ? v.dup : v }) + elsif value.is_a? String + value = value.dup end if extra_keys.include?(key.to_sym) diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index edda0d0a30..5896222a0a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -493,6 +493,18 @@ XML ) end + def test_params_passing_with_frozen_values + assert_nothing_raised do + get :test_params, :frozen => 'icy'.freeze, :frozens => ['icy'.freeze].freeze + end + parsed_params = eval(@response.body) + assert_equal( + {'controller' => 'test_test/test', 'action' => 'test_params', + 'frozen' => 'icy', 'frozens' => ['icy']}, + parsed_params + ) + end + def test_id_converted_to_string get :test_params, :id => 20, :foo => Object.new assert_kind_of String, @request.path_parameters['id'] -- cgit v1.2.3 From 001a5a649609840bd14bd4ae6a12be0c074e7f67 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 28 Apr 2011 01:50:00 +0530 Subject: document the reorder method(fb215110401c70cfc7013c6e2ad5753fa4e374e9) --- .../guides/source/active_record_querying.textile | 35 +++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index 2f0a51e868..579a323d57 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -56,6 +56,7 @@ The methods are: * +select+ * +group+ * +order+ +* +reorder+ * +limit+ * +offset+ * +joins+ @@ -495,9 +496,9 @@ This will return single order objects for each day, but only for the last month. h3. Overriding Conditions -You can specify certain conditions to be excepted by using the +except+ method. +h4. +except+ -For example: +You can specify certain conditions to be excepted by using the +except+ method. For example: Post.where('id > 10').limit(20).order('id asc').except(:order) @@ -509,9 +510,9 @@ The SQL that would be executed: SELECT * FROM posts WHERE id > 10 LIMIT 20 -You can also override conditions using the +only+ method. +h4. +only+ -For example: +You can also override conditions using the +only+ method. For example: Post.where('id > 10').limit(20).order('id desc').only(:order, :where) @@ -523,6 +524,32 @@ The SQL that would be executed: SELECT * FROM posts WHERE id > 10 ORDER BY id DESC +h4. +reorder+ + +The +reorder+ method overrides the default scope order. For example: + + +class Post < ActiveRecord::Base + .. + .. + has_many :comments, :order => 'posted_at DESC' +end + +Post.find(10).comments.reorder('name') + + +The SQL that would be executed: + + +SELECT * FROM posts WHERE id = 10 ORDER BY name + + +In case the +reorder+ clause is not used, the SQL executed would be: + + +SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC + + h3. Readonly Objects Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. -- cgit v1.2.3 From 78e581bd2f415b7a49e0fb7538ff6309b5ef2fdb Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Wed, 27 Apr 2011 23:25:27 +0200 Subject: some test renaming to avoid collisions, and some annoying issues between dbs --- activerecord/test/cases/persistence_test.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 5567602a26..b066575af8 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -492,7 +492,7 @@ class PersistencesTest < ActiveRecord::TestCase end def test_update_attributes_as_admin - person = TightPerson.create + person = TightPerson.create({ "first_name" => 'Joshua' }) person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) person.reload @@ -501,8 +501,8 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal 'from NZ', person.comments end - def test_update_attributes_as_without_protection - person = TightPerson.create + def test_update_attributes_without_protection + person = TightPerson.create({ "first_name" => 'Joshua' }) person.update_attributes({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) person.reload @@ -532,8 +532,8 @@ class PersistencesTest < ActiveRecord::TestCase Reply.reset_callbacks(:validate) end - def test_update_attributes_as_admin - person = TightPerson.create + def test_update_attributes_with_bang_as_admin + person = TightPerson.create({ "first_name" => 'Joshua' }) person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :as => :admin) person.reload @@ -542,8 +542,8 @@ class PersistencesTest < ActiveRecord::TestCase assert_equal 'from NZ', person.comments end - def test_update_attributes_as_without_protection - person = TightPerson.create + def test_update_attributestes_with_bang_without_protection + person = TightPerson.create({ "first_name" => 'Joshua' }) person.update_attributes!({ "first_name" => 'Josh', "gender" => 'm', "comments" => 'from NZ' }, :without_protection => true) person.reload -- cgit v1.2.3 From bed6a775df086c58e7129805dec4cbe755856df2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 27 Apr 2011 14:44:28 -0700 Subject: requiring a newer version of the mysql2 gem --- Gemfile | 2 +- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 98272c7261..8b6ddbd7c9 100644 --- a/Gemfile +++ b/Gemfile @@ -54,7 +54,7 @@ platforms :ruby do group :db do gem "pg", ">= 0.11.0" gem "mysql", ">= 2.8.1" - gem "mysql2", :git => "git://github.com/brianmario/mysql2.git" + gem "mysql2", ">= 0.3.0" end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 7ac72acd58..cf68ddc2da 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,5 +1,6 @@ # encoding: utf-8 +gem 'mysql2', '~> 0.3.0' require 'mysql2' module ActiveRecord -- cgit v1.2.3 From 07bbaaa3b14d7048a3e43851aed199b8b515a820 Mon Sep 17 00:00:00 2001 From: Diego Carrion Date: Wed, 27 Apr 2011 20:29:03 -0300 Subject: added a :prefix option to number_to_human_size --- actionpack/lib/action_view/helpers/number_helper.rb | 9 ++++++--- actionpack/test/template/number_helper_test.rb | 11 +++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index b545031fcc..63d13a0f0b 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -304,6 +304,7 @@ module ActionView # * :separator - Sets the separator between the fractional and integer digits (defaults to "."). # * :delimiter - Sets the thousands delimiter (defaults to ""). # * :strip_insignificant_zeros - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) + # * :prefix - If +:si+ formats the number using the SI prefix (defaults to :binary) # ==== Examples # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.21 KB @@ -341,15 +342,17 @@ module ActionView options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) + + base = options[:prefix] == :si ? 1000 : 1024 - if number.to_i < 1024 + if number.to_i < base unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true) storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe else max_exp = STORAGE_UNITS.size - 1 - exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024 + exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit - number /= 1024 ** exponent + number /= base ** exponent unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 23a7e17e65..63b92aadf4 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -172,6 +172,17 @@ class NumberHelperTest < ActionView::TestCase assert_equal '10 Bytes', number_to_human_size(10) end + def test_number_to_human_size_with_si_prefix + assert_equal '3 Bytes', number_to_human_size(3.14159265, :prefix => :si) + assert_equal '123 Bytes', number_to_human_size(123.0, :prefix => :si) + assert_equal '123 Bytes', number_to_human_size(123, :prefix => :si) + assert_equal '1.23 KB', number_to_human_size(1234, :prefix => :si) + assert_equal '12.3 KB', number_to_human_size(12345, :prefix => :si) + assert_equal '1.23 MB', number_to_human_size(1234567, :prefix => :si) + assert_equal '1.23 GB', number_to_human_size(1234567890, :prefix => :si) + assert_equal '1.23 TB', number_to_human_size(1234567890123, :prefix => :si) + end + def test_number_to_human_size_with_options_hash assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) -- cgit v1.2.3 From 5d20c0a64aafa4d35f5aa0613cc16170c2a2ec03 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Sun, 3 Apr 2011 02:43:11 +0800 Subject: Wrap line that is over 200 characters long. Now it's much easier to read. --- activemodel/lib/active_model/observing.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 3c80d584fe..4e9a305d9d 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -76,7 +76,11 @@ module ActiveModel elsif observer.respond_to?(:instance) observer.instance else - raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance" + raise ArgumentError, + "#{observer} must be a lowercase, underscored class name (or an " + + "instance of the class itself) responding to the instance " + + "method. Example: Person.observers = :big_brother # calls " + + "BigBrother.instance" end end -- cgit v1.2.3 From 1f8cc446d9a7ab751a2def65309ac4bc71e85cd3 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 02:07:42 +0800 Subject: Allow observers to be enabled and disabled. This is useful in situations like model unit tests and the occasional rake task to backfill old data. --- activemodel/lib/active_model/observer_array.rb | 98 ++++++++++++++++++++ activemodel/lib/active_model/observing.rb | 27 +++++- activemodel/test/cases/observer_array_test.rb | 122 +++++++++++++++++++++++++ activemodel/test/cases/observing_test.rb | 5 + activemodel/test/models/observers.rb | 27 ++++++ 5 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 activemodel/lib/active_model/observer_array.rb create mode 100644 activemodel/test/cases/observer_array_test.rb create mode 100644 activemodel/test/models/observers.rb diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb new file mode 100644 index 0000000000..b8aa9cc1e2 --- /dev/null +++ b/activemodel/lib/active_model/observer_array.rb @@ -0,0 +1,98 @@ +require 'set' + +module ActiveModel + # Stores the enabled/disabled state of individual observers for + # a particular model classes. + class ObserverArray < Array + INSTANCES = Hash.new do |hash, model_class| + hash[model_class] = new(model_class) + end + + def self.for(model_class) + return nil unless model_class < ActiveModel::Observing + INSTANCES[model_class] + end + + # returns false if: + # - the ObserverArray for the given model's class has the given observer + # in its disabled_observers set. + # - or that is the case at any level of the model's superclass chain. + def self.observer_enabled?(observer, model) + klass = model.class + observer_class = observer.class + + loop do + break unless array = self.for(klass) + return false if array.disabled_observers.include?(observer_class) + klass = klass.superclass + end + + true # observers are enabled by default + end + + def disabled_observers + @disabled_observers ||= Set.new + end + + attr_reader :model_class + def initialize(model_class, *args) + @model_class = model_class + super(*args) + end + + def disable(*observers, &block) + set_enablement(false, observers, &block) + end + + def enable(*observers, &block) + set_enablement(true, observers, &block) + end + + private + + def observer_class_for(observer) + return observer if observer.is_a?(Class) + + if observer.respond_to?(:to_sym) # string/symbol + observer.to_s.camelize.constantize + else + raise ArgumentError, "#{observer} was not a class or a " + + "lowercase, underscored class name as expected." + end + end + + def transaction + orig_disabled_observers = disabled_observers.dup + + begin + yield + ensure + @disabled_observers = orig_disabled_observers + end + end + + def set_enablement(enabled, observers) + if block_given? + transaction do + set_enablement(enabled, observers) + yield + end + else + observers = ActiveModel::Observer.all_observers if observers == [:all] + observers.each do |obs| + klass = observer_class_for(obs) + + unless klass < ActiveModel::Observer + raise ArgumentError.new("#{obs} does not refer to a valid observer") + end + + if enabled + disabled_observers.delete(klass) + else + disabled_observers << klass + end + end + end + end + end +end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 4e9a305d9d..e1a2ce218d 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -1,8 +1,10 @@ require 'singleton' +require 'active_model/observer_array' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/enumerable' module ActiveModel module Observing @@ -30,12 +32,12 @@ module ActiveModel # +instantiate_observers+ is called during startup, and before # each development request. def observers=(*values) - @observers = values.flatten + observers.replace(values.flatten) end # Gets the current observers. def observers - @observers ||= [] + @observers ||= ObserverArray.for(self) end # Gets the current observer instances. @@ -201,6 +203,23 @@ module ActiveModel nil end end + + def subclasses + @subclasses ||= [] + end + + # List of all observer subclasses, sub-subclasses, etc. + # Necessary so we can disable or enable all observers. + def all_observers + subclasses.each_with_object(subclasses.dup) do |subclass, array| + array.concat(subclass.all_observers) + end + end + end + + def self.inherited(subclass) + subclasses << subclass + super end # Start observing the declared classes and their subclasses. @@ -214,7 +233,9 @@ module ActiveModel # Send observed_method(object) if the method exists. def update(observed_method, object) #:nodoc: - send(observed_method, object) if respond_to?(observed_method) + if respond_to?(observed_method) && ObserverArray.observer_enabled?(self, object) + send(observed_method, object) + end end # Special method sent by the observed class when it is inherited. diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb new file mode 100644 index 0000000000..215ca80bb4 --- /dev/null +++ b/activemodel/test/cases/observer_array_test.rb @@ -0,0 +1,122 @@ +require 'cases/helper' +require 'models/observers' + +class ObserverArrayTest < ActiveModel::TestCase + def teardown + ORM.observers.enable :all + Budget.observers.enable :all + Widget.observers.enable :all + end + + def assert_observer_notified(model_class, observer_class) + observer_class.instance.before_save_invocations.clear + model_instance = model_class.new + model_instance.save + assert_equal [model_instance], observer_class.instance.before_save_invocations + end + + def assert_observer_not_notified(model_class, observer_class) + observer_class.instance.before_save_invocations.clear + model_instance = model_class.new + model_instance.save + assert_equal [], observer_class.instance.before_save_invocations + end + + test "all observers are enabled by default" do + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable individual observers using a class constant" do + ORM.observers.disable WidgetObserver + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable individual observers using a symbol" do + ORM.observers.disable :budget_observer + + assert_observer_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable all observers using :all" do + ORM.observers.disable :all + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_not_notified Budget, AuditTrail + end + + test "can disable observers on individual models without affecting observers on other models" do + Widget.observers.disable :all + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable observers for the duration of a block" do + yielded = false + ORM.observers.disable :budget_observer do + yielded = true + assert_observer_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + assert yielded + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can enable observers for the duration of a block" do + yielded = false + Widget.observers.disable :all + + Widget.observers.enable :all do + yielded = true + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + assert yielded + assert_observer_not_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "raises an appropriate error when a developer accidentally enables or disables the wrong class (i.e. Widget instead of WidgetObserver)" do + assert_raise ArgumentError do + ORM.observers.enable :widget + end + + assert_raise ArgumentError do + ORM.observers.enable Widget + end + + assert_raise ArgumentError do + ORM.observers.disable :widget + end + + assert_raise ArgumentError do + ORM.observers.disable Widget + end + end +end + diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb index 63686843b6..99b1f407ae 100644 --- a/activemodel/test/cases/observing_test.rb +++ b/activemodel/test/cases/observing_test.rb @@ -43,6 +43,11 @@ class ObservingTest < ActiveModel::TestCase assert ObservedModel.observers.include?(:bar), ":bar not in #{ObservedModel.observers.inspect}" end + test "uses an ObserverArray so observers can be disabled" do + ObservedModel.observers = [:foo, :bar] + assert ObservedModel.observers.is_a?(ActiveModel::ObserverArray) + end + test "instantiates observer names passed as strings" do ObservedModel.observers << 'foo_observer' FooObserver.expects(:instance) diff --git a/activemodel/test/models/observers.rb b/activemodel/test/models/observers.rb new file mode 100644 index 0000000000..3729b3435e --- /dev/null +++ b/activemodel/test/models/observers.rb @@ -0,0 +1,27 @@ +class ORM + include ActiveModel::Observing + + def save + notify_observers :before_save + end + + class Observer < ActiveModel::Observer + def before_save_invocations + @before_save_invocations ||= [] + end + + def before_save(record) + before_save_invocations << record + end + end +end + +class Widget < ORM; end +class Budget < ORM; end +class WidgetObserver < ORM::Observer; end +class BudgetObserver < ORM::Observer; end +class AuditTrail < ORM::Observer + observe :widget, :budget +end + +ORM.instantiate_observers -- cgit v1.2.3 From ad62f1928768bd2676958a4a08512bad342fe469 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 07:35:55 +0800 Subject: Handle enabling/disabling observers at different levels of the class hierarchy. Last call wins. --- activemodel/lib/active_model/observer_array.rb | 34 ++++++++++++++++++++-- activemodel/lib/active_model/observing.rb | 5 ++++ activemodel/test/cases/observer_array_test.rb | 39 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index b8aa9cc1e2..f3b5811b81 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -48,7 +48,7 @@ module ActiveModel set_enablement(true, observers, &block) end - private + protected def observer_class_for(observer) return observer if observer.is_a?(Class) @@ -61,13 +61,37 @@ module ActiveModel end end + def start_transaction + disabled_observer_stack.push(disabled_observers.dup) + each_subclass_array do |array| + array.start_transaction + end + end + + def disabled_observer_stack + @disabled_observer_stack ||= [] + end + + def end_transaction + @disabled_observers = disabled_observer_stack.pop + each_subclass_array do |array| + array.end_transaction + end + end + def transaction - orig_disabled_observers = disabled_observers.dup + start_transaction begin yield ensure - @disabled_observers = orig_disabled_observers + end_transaction + end + end + + def each_subclass_array + model_class.subclasses.each do |subclass| + yield self.class.for(subclass) end end @@ -92,6 +116,10 @@ module ActiveModel disabled_observers << klass end end + + each_subclass_array do |array| + array.set_enablement(enabled, observers) + end end end end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index e1a2ce218d..ba6be46670 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -70,6 +70,10 @@ module ActiveModel observer_instances.size end + def subclasses + @subclasses ||= [] + end + protected def instantiate_observer(observer) #:nodoc: # string/symbol @@ -89,6 +93,7 @@ module ActiveModel # Notify observers when the observed class is subclassed. def inherited(subclass) super + subclasses << subclass notify_observers :observed_class_inherited, subclass end end diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb index 215ca80bb4..38e4fd59fc 100644 --- a/activemodel/test/cases/observer_array_test.rb +++ b/activemodel/test/cases/observer_array_test.rb @@ -118,5 +118,44 @@ class ObserverArrayTest < ActiveModel::TestCase ORM.observers.disable Widget end end + + test "allows #enable at the superclass level to override #disable at the subclass level when called last" do + Widget.observers.disable :all + ORM.observers.enable :all + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "allows #disable at the superclass level to override #enable at the subclass level when called last" do + Budget.observers.enable :audit_trail + ORM.observers.disable :audit_trail + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_not_notified Budget, AuditTrail + end + + test "can use the block form at different levels of the hierarchy" do + yielded = false + Widget.observers.disable :all + + ORM.observers.enable :all do + yielded = true + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + assert yielded + assert_observer_not_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end end -- cgit v1.2.3 From a07cee62c7d23ec587ef961beb35ce3ac63df340 Mon Sep 17 00:00:00 2001 From: Jason Garber Date: Wed, 27 Apr 2011 21:00:27 -0600 Subject: Allow a label with the value option to have value-specific translations. --- actionpack/lib/action_view/helpers/form_helper.rb | 3 ++- actionpack/test/template/form_helper_test.rb | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 440acafa88..efe30441b1 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -947,7 +947,8 @@ module ActionView label_tag(name_and_id["id"], options, &block) else content = if text.blank? - I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence + method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name + I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence else text.to_s end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 7afab3179c..c25c850eb3 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -24,7 +24,10 @@ class FormHelperTest < ActionView::TestCase :helpers => { :label => { :post => { - :body => "Write entire text here" + :body => "Write entire text here", + :color => { + :red => "Rojo" + } } } } @@ -141,6 +144,13 @@ class FormHelperTest < ActionView::TestCase I18n.locale = old_locale end + def test_label_with_locales_and_value + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('', label(:post, :color, :value => "red")) + ensure + I18n.locale = old_locale + end + def test_label_with_for_attribute_as_symbol assert_dom_equal('', label(:post, :title, nil, :for => "my_for")) end -- cgit v1.2.3 From 4c50a0f80034a8f7f111ad88709d6fa0c407f82e Mon Sep 17 00:00:00 2001 From: Stefan Rohlfing Date: Thu, 28 Apr 2011 00:25:03 -0700 Subject: Edited railties/guides/source/routing.textile via GitHub --- railties/guides/source/routing.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile index 43c08165dc..99fdcee68a 100644 --- a/railties/guides/source/routing.textile +++ b/railties/guides/source/routing.textile @@ -391,7 +391,7 @@ NOTE: You can't use +namespace+ or +:module+ with a +:controller+ path segment. match ':controller(/:action(/:id))', :controller => /admin\/[^\/]+/ -TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id => /[^\/]/+ allows anything except a slash. +TIP: By default dynamic segments don't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within a dynamic segment add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash. h4. Static Segments @@ -660,7 +660,7 @@ end NOTE: Of course, you can use the more advanced constraints available in non-resourceful routes in this context. -TIP: By default the +:id+ parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an +:id+ add a constraint which overrides this - for example +:id => /[^\/]+/+ allows anything except a slash. +TIP: By default the +:id+ parameter doesn't accept dots - this is because the dot is used as a separator for formatted routes. If you need to use a dot within an +:id+ add a constraint which overrides this - for example +:id+ => /[^\/]+/ allows anything except a slash. h4. Overriding the Named Helpers -- cgit v1.2.3 From 2a25c5818b03d7d6cd63aad180bff23479dbd861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 28 Apr 2011 09:32:57 +0200 Subject: Revert "Handle enabling/disabling observers at different levels of the class hierarchy." This reverts commit ad62f1928768bd2676958a4a08512bad342fe469 because the current subclasses implementation leaks memory in development. Instead of keeping an array of subclasses, the better solution is to ask the parent if an observer is being disabled or not. --- activemodel/lib/active_model/observer_array.rb | 34 ++-------------------- activemodel/lib/active_model/observing.rb | 5 ---- activemodel/test/cases/observer_array_test.rb | 39 -------------------------- 3 files changed, 3 insertions(+), 75 deletions(-) diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index f3b5811b81..b8aa9cc1e2 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -48,7 +48,7 @@ module ActiveModel set_enablement(true, observers, &block) end - protected + private def observer_class_for(observer) return observer if observer.is_a?(Class) @@ -61,37 +61,13 @@ module ActiveModel end end - def start_transaction - disabled_observer_stack.push(disabled_observers.dup) - each_subclass_array do |array| - array.start_transaction - end - end - - def disabled_observer_stack - @disabled_observer_stack ||= [] - end - - def end_transaction - @disabled_observers = disabled_observer_stack.pop - each_subclass_array do |array| - array.end_transaction - end - end - def transaction - start_transaction + orig_disabled_observers = disabled_observers.dup begin yield ensure - end_transaction - end - end - - def each_subclass_array - model_class.subclasses.each do |subclass| - yield self.class.for(subclass) + @disabled_observers = orig_disabled_observers end end @@ -116,10 +92,6 @@ module ActiveModel disabled_observers << klass end end - - each_subclass_array do |array| - array.set_enablement(enabled, observers) - end end end end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index ba6be46670..e1a2ce218d 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -70,10 +70,6 @@ module ActiveModel observer_instances.size end - def subclasses - @subclasses ||= [] - end - protected def instantiate_observer(observer) #:nodoc: # string/symbol @@ -93,7 +89,6 @@ module ActiveModel # Notify observers when the observed class is subclassed. def inherited(subclass) super - subclasses << subclass notify_observers :observed_class_inherited, subclass end end diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb index 38e4fd59fc..215ca80bb4 100644 --- a/activemodel/test/cases/observer_array_test.rb +++ b/activemodel/test/cases/observer_array_test.rb @@ -118,44 +118,5 @@ class ObserverArrayTest < ActiveModel::TestCase ORM.observers.disable Widget end end - - test "allows #enable at the superclass level to override #disable at the subclass level when called last" do - Widget.observers.disable :all - ORM.observers.enable :all - - assert_observer_notified Widget, WidgetObserver - assert_observer_notified Budget, BudgetObserver - assert_observer_notified Widget, AuditTrail - assert_observer_notified Budget, AuditTrail - end - - test "allows #disable at the superclass level to override #enable at the subclass level when called last" do - Budget.observers.enable :audit_trail - ORM.observers.disable :audit_trail - - assert_observer_notified Widget, WidgetObserver - assert_observer_notified Budget, BudgetObserver - assert_observer_not_notified Widget, AuditTrail - assert_observer_not_notified Budget, AuditTrail - end - - test "can use the block form at different levels of the hierarchy" do - yielded = false - Widget.observers.disable :all - - ORM.observers.enable :all do - yielded = true - assert_observer_notified Widget, WidgetObserver - assert_observer_notified Budget, BudgetObserver - assert_observer_notified Widget, AuditTrail - assert_observer_notified Budget, AuditTrail - end - - assert yielded - assert_observer_not_notified Widget, WidgetObserver - assert_observer_notified Budget, BudgetObserver - assert_observer_not_notified Widget, AuditTrail - assert_observer_notified Budget, AuditTrail - end end -- cgit v1.2.3 From bb44cd727ff4df0c6baac52ec008289626b874e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 28 Apr 2011 09:47:07 +0200 Subject: Tidy up previous commit. --- activesupport/lib/active_support/cache/mem_cache_store.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index a4b20719cd..7ef1497ac2 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -4,7 +4,9 @@ rescue LoadError => e $stderr.puts "You don't have memcache-client installed in your application. Please add it to your Gemfile and run bundle install" raise e end + require 'digest/md5' +require 'active_support/core_ext/string/encoding' module ActiveSupport module Cache @@ -157,11 +159,14 @@ module ActiveSupport end private + + # Memcache keys are binaries. So we need to force their encoding to binary + # before applying the regular expression to ensure we are escaping all + # characters properly. def escape_key(key) - # Fix for EncodedKeyCacheBehavior failing tests in caching_test.rb. key = key.to_s.dup - key = key.force_encoding(ESCAPE_KEY_CHARS.encoding) if key.respond_to?(:encoding) && key.encoding != ESCAPE_KEY_CHARS.encoding - key = key.gsub(ESCAPE_KEY_CHARS){|match| "%#{match.getbyte(0).to_s(16).upcase}"} + key = key.force_encoding("BINARY") if key.encoding_aware? + key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key end -- cgit v1.2.3 From 502aaec92c9e15f1b784de4d2eee97f7c659069e Mon Sep 17 00:00:00 2001 From: Neeraj Singh Date: Thu, 28 Apr 2011 06:35:58 -0400 Subject: upgrade rack-mount Newer version is a few minor performance fixes --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index f771737779..0d667a76a7 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6.0beta1') s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') - s.add_dependency('rack-mount', '~> 0.7.1') + s.add_dependency('rack-mount', '~> 0.7.2') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.7.0') end -- cgit v1.2.3 From 05d4653cef1c1d8d77228de26d55cf6d6a0ce20b Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 07:58:58 -0700 Subject: Revert "Revert "Handle enabling/disabling observers at different levels of the class hierarchy."" This reverts commit 2a25c5818b03d7d6cd63aad180bff23479dbd861. I'm going to add another commit that keeps the same behavior of fixes the problems of leaking memory in development. --- activemodel/lib/active_model/observer_array.rb | 34 ++++++++++++++++++++-- activemodel/lib/active_model/observing.rb | 5 ++++ activemodel/test/cases/observer_array_test.rb | 39 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index b8aa9cc1e2..f3b5811b81 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -48,7 +48,7 @@ module ActiveModel set_enablement(true, observers, &block) end - private + protected def observer_class_for(observer) return observer if observer.is_a?(Class) @@ -61,13 +61,37 @@ module ActiveModel end end + def start_transaction + disabled_observer_stack.push(disabled_observers.dup) + each_subclass_array do |array| + array.start_transaction + end + end + + def disabled_observer_stack + @disabled_observer_stack ||= [] + end + + def end_transaction + @disabled_observers = disabled_observer_stack.pop + each_subclass_array do |array| + array.end_transaction + end + end + def transaction - orig_disabled_observers = disabled_observers.dup + start_transaction begin yield ensure - @disabled_observers = orig_disabled_observers + end_transaction + end + end + + def each_subclass_array + model_class.subclasses.each do |subclass| + yield self.class.for(subclass) end end @@ -92,6 +116,10 @@ module ActiveModel disabled_observers << klass end end + + each_subclass_array do |array| + array.set_enablement(enabled, observers) + end end end end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index e1a2ce218d..ba6be46670 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -70,6 +70,10 @@ module ActiveModel observer_instances.size end + def subclasses + @subclasses ||= [] + end + protected def instantiate_observer(observer) #:nodoc: # string/symbol @@ -89,6 +93,7 @@ module ActiveModel # Notify observers when the observed class is subclassed. def inherited(subclass) super + subclasses << subclass notify_observers :observed_class_inherited, subclass end end diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb index 215ca80bb4..38e4fd59fc 100644 --- a/activemodel/test/cases/observer_array_test.rb +++ b/activemodel/test/cases/observer_array_test.rb @@ -118,5 +118,44 @@ class ObserverArrayTest < ActiveModel::TestCase ORM.observers.disable Widget end end + + test "allows #enable at the superclass level to override #disable at the subclass level when called last" do + Widget.observers.disable :all + ORM.observers.enable :all + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "allows #disable at the superclass level to override #enable at the subclass level when called last" do + Budget.observers.enable :audit_trail + ORM.observers.disable :audit_trail + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_not_notified Budget, AuditTrail + end + + test "can use the block form at different levels of the hierarchy" do + yielded = false + Widget.observers.disable :all + + ORM.observers.enable :all do + yielded = true + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + assert yielded + assert_observer_not_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end end -- cgit v1.2.3 From 9a385394acd6599ff588daad491e9e07ee716091 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 08:07:08 -0700 Subject: Fix dev env memory leaks by using AS::DescendantsTracker rather than keeping track of subclasses manually. There's also no need to keep track of all ObserverArray instances in a hash, as this is likely to leak memory, too. --- activemodel/lib/active_model/observer_array.rb | 18 +++++----------- activemodel/lib/active_model/observing.rb | 30 ++++++-------------------- 2 files changed, 12 insertions(+), 36 deletions(-) diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index f3b5811b81..d501215dd6 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -4,15 +4,6 @@ module ActiveModel # Stores the enabled/disabled state of individual observers for # a particular model classes. class ObserverArray < Array - INSTANCES = Hash.new do |hash, model_class| - hash[model_class] = new(model_class) - end - - def self.for(model_class) - return nil unless model_class < ActiveModel::Observing - INSTANCES[model_class] - end - # returns false if: # - the ObserverArray for the given model's class has the given observer # in its disabled_observers set. @@ -22,7 +13,8 @@ module ActiveModel observer_class = observer.class loop do - break unless array = self.for(klass) + break unless klass.respond_to?(:observers) + array = klass.observers return false if array.disabled_observers.include?(observer_class) klass = klass.superclass end @@ -90,8 +82,8 @@ module ActiveModel end def each_subclass_array - model_class.subclasses.each do |subclass| - yield self.class.for(subclass) + model_class.descendants.each do |subclass| + yield subclass.observers end end @@ -102,7 +94,7 @@ module ActiveModel yield end else - observers = ActiveModel::Observer.all_observers if observers == [:all] + observers = ActiveModel::Observer.descendants if observers == [:all] observers.each do |obs| klass = observer_class_for(obs) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index ba6be46670..9ffcda8dd7 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -5,11 +5,16 @@ require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/enumerable' +require 'active_support/descendants_tracker' module ActiveModel module Observing extend ActiveSupport::Concern + included do + extend ActiveSupport::DescendantsTracker + end + module ClassMethods # == Active Model Observers Activation # @@ -37,7 +42,7 @@ module ActiveModel # Gets the current observers. def observers - @observers ||= ObserverArray.for(self) + @observers ||= ObserverArray.new(self) end # Gets the current observer instances. @@ -70,10 +75,6 @@ module ActiveModel observer_instances.size end - def subclasses - @subclasses ||= [] - end - protected def instantiate_observer(observer) #:nodoc: # string/symbol @@ -93,7 +94,6 @@ module ActiveModel # Notify observers when the observed class is subclassed. def inherited(subclass) super - subclasses << subclass notify_observers :observed_class_inherited, subclass end end @@ -176,6 +176,7 @@ module ActiveModel # class Observer include Singleton + extend ActiveSupport::DescendantsTracker class << self # Attaches the observer to the supplied model classes. @@ -208,23 +209,6 @@ module ActiveModel nil end end - - def subclasses - @subclasses ||= [] - end - - # List of all observer subclasses, sub-subclasses, etc. - # Necessary so we can disable or enable all observers. - def all_observers - subclasses.each_with_object(subclasses.dup) do |subclass, array| - array.concat(subclass.all_observers) - end - end - end - - def self.inherited(subclass) - subclasses << subclass - super end # Start observing the declared classes and their subclasses. -- cgit v1.2.3 From fef22157b07f101229d29544d578bfe2cb9fedfe Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 08:27:15 -0700 Subject: Fix bug with AM::Observer disablement. Now that we propagate the enabling/disabling to descendants, we no longer have to check the disabled_observer Set on each superclass of the model class. This was causing a bug when disabling all observers at a superclass level and then enabling an individual observer at a subclass level. Plus the logic is simpler now :). --- activemodel/lib/active_model/observer_array.rb | 30 +++++++------------------- activemodel/lib/active_model/observing.rb | 12 ++++++++--- activemodel/test/cases/observer_array_test.rb | 10 +++++++++ 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb index d501215dd6..ab7f86007f 100644 --- a/activemodel/lib/active_model/observer_array.rb +++ b/activemodel/lib/active_model/observer_array.rb @@ -4,34 +4,16 @@ module ActiveModel # Stores the enabled/disabled state of individual observers for # a particular model classes. class ObserverArray < Array - # returns false if: - # - the ObserverArray for the given model's class has the given observer - # in its disabled_observers set. - # - or that is the case at any level of the model's superclass chain. - def self.observer_enabled?(observer, model) - klass = model.class - observer_class = observer.class - - loop do - break unless klass.respond_to?(:observers) - array = klass.observers - return false if array.disabled_observers.include?(observer_class) - klass = klass.superclass - end - - true # observers are enabled by default - end - - def disabled_observers - @disabled_observers ||= Set.new - end - attr_reader :model_class def initialize(model_class, *args) @model_class = model_class super(*args) end + def disabled_for?(observer) + disabled_observers.include?(observer.class) + end + def disable(*observers, &block) set_enablement(false, observers, &block) end @@ -42,6 +24,10 @@ module ActiveModel protected + def disabled_observers + @disabled_observers ||= Set.new + end + def observer_class_for(observer) return observer if observer.is_a?(Class) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 9ffcda8dd7..c1ac4eb4af 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -222,9 +222,9 @@ module ActiveModel # Send observed_method(object) if the method exists. def update(observed_method, object) #:nodoc: - if respond_to?(observed_method) && ObserverArray.observer_enabled?(self, object) - send(observed_method, object) - end + return unless respond_to?(observed_method) + return if disabled_for?(object) + send(observed_method, object) end # Special method sent by the observed class when it is inherited. @@ -238,5 +238,11 @@ module ActiveModel def add_observer!(klass) #:nodoc: klass.add_observer(self) end + + def disabled_for?(object) + klass = object.class + return false unless klass.respond_to?(:observers) + klass.observers.disabled_for?(self) + end end end diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb index 38e4fd59fc..3ede5682b4 100644 --- a/activemodel/test/cases/observer_array_test.rb +++ b/activemodel/test/cases/observer_array_test.rb @@ -65,6 +65,16 @@ class ObserverArrayTest < ActiveModel::TestCase assert_observer_notified Budget, AuditTrail end + test "can enable observers on individual models without affecting those observers on other models" do + ORM.observers.disable :all + Budget.observers.enable AuditTrail + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + test "can disable observers for the duration of a block" do yielded = false ORM.observers.disable :budget_observer do -- cgit v1.2.3 From 7db7aa505375db75c2205f35df743becc7647c95 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Thu, 28 Apr 2011 08:32:22 -0700 Subject: Add additional tests for AM::ObserverArray that I had missed yesterday. --- activemodel/test/cases/observer_array_test.rb | 51 ++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/activemodel/test/cases/observer_array_test.rb b/activemodel/test/cases/observer_array_test.rb index 3ede5682b4..fc5f18008b 100644 --- a/activemodel/test/cases/observer_array_test.rb +++ b/activemodel/test/cases/observer_array_test.rb @@ -38,6 +38,16 @@ class ObserverArrayTest < ActiveModel::TestCase assert_observer_notified Budget, AuditTrail end + test "can enable individual observers using a class constant" do + ORM.observers.disable :all + ORM.observers.enable AuditTrail + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + test "can disable individual observers using a symbol" do ORM.observers.disable :budget_observer @@ -47,6 +57,35 @@ class ObserverArrayTest < ActiveModel::TestCase assert_observer_notified Budget, AuditTrail end + test "can enable individual observers using a symbol" do + ORM.observers.disable :all + ORM.observers.enable :audit_trail + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable multiple observers at a time" do + ORM.observers.disable :widget_observer, :budget_observer + + assert_observer_not_notified Widget, WidgetObserver + assert_observer_not_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can enable multiple observers at a time" do + ORM.observers.disable :all + ORM.observers.enable :widget_observer, :budget_observer + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_not_notified Widget, AuditTrail + assert_observer_not_notified Budget, AuditTrail + end + test "can disable all observers using :all" do ORM.observers.disable :all @@ -56,7 +95,17 @@ class ObserverArrayTest < ActiveModel::TestCase assert_observer_not_notified Budget, AuditTrail end - test "can disable observers on individual models without affecting observers on other models" do + test "can enable all observers using :all" do + ORM.observers.disable :all + ORM.observers.enable :all + + assert_observer_notified Widget, WidgetObserver + assert_observer_notified Budget, BudgetObserver + assert_observer_notified Widget, AuditTrail + assert_observer_notified Budget, AuditTrail + end + + test "can disable observers on individual models without affecting those observers on other models" do Widget.observers.disable :all assert_observer_not_notified Widget, WidgetObserver -- cgit v1.2.3 From 5164c50d7ff42faa801e827dc8761ff11e61f3e2 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Thu, 28 Apr 2011 18:46:26 +0200 Subject: removed the default_scope deprecations and updated the docs and tests to reflect its use cases --- activerecord/lib/active_record/base.rb | 53 ++++++++---------------- activerecord/test/cases/relation_scoping_test.rb | 20 +++------ activerecord/test/models/developer.rb | 24 ++++++++--- 3 files changed, 41 insertions(+), 56 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 04c12f86b6..8d17e3e2c6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1180,19 +1180,15 @@ MSG # Use this macro in your model to set a default scope for all operations on # the model. # - # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # class Article < ActiveRecord::Base + # default_scope where(:published => true) # end # - # Person.all # => SELECT * FROM people ORDER BY last_name, first_name + # Article.all # => SELECT * FROM articles WHERE published = true # # The default_scope is also applied while creating/building a record. It is not # applied while updating a record. # - # class Article < ActiveRecord::Base - # default_scope where(:published => true) - # end - # # Article.new.published # => true # Article.create.published # => true # @@ -1205,6 +1201,19 @@ MSG # (You can also pass any object which responds to call to the default_scope # macro, and it will be called when building the default scope.) # + # If you use multiple default_scope declarations in your model then they will + # be merged together: + # + # class Article < ActiveRecord::Base + # default_scope where(:published => true) + # default_scope where(:rating => 'G') + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the parent or module + # defines a default_scope and the child or including class defines a second one. + # # If you need to do more complex things with a default scope, you can alternatively # define it as a class method: # @@ -1214,36 +1223,8 @@ MSG # end # end def default_scope(scope = {}) - if default_scopes.length != 0 - ActiveSupport::Deprecation.warn <<-WARN -Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together: - -class Post < ActiveRecord::Base # Rails 3.1 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:published => true, :hidden => false) -end - -In Rails 3.2, the behavior will be changed to overwrite previous scopes: - -class Post < ActiveRecord::Base # Rails 3.2 - default_scope where(:published => true) - default_scope where(:hidden => false) - # The default scope is now: where(:hidden => false) -end - -If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.): - -class Post < ActiveRecord::Base - def self.default_scope - where(:published => true).where(:hidden => false) - end -end - WARN - end - scope = Proc.new if block_given? - self.default_scopes = default_scopes.dup << scope + self.default_scopes = default_scopes + [scope] end def build_default_scope #:nodoc: diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 2ed676fe69..864b3d4846 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -355,6 +355,12 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 50000, wheres[:salary] end + def test_default_scope_with_module_includes + wheres = ModuleIncludedPoorDeveloperCalledJamis.scoped.where_values_hash + assert_equal "Jamis", wheres[:name] + assert_equal 50000, wheres[:salary] + end + def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash assert_equal "Jamis", wheres[:name] @@ -456,18 +462,4 @@ class DefaultScopingTest < ActiveRecord::TestCase assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis)) assert_equal 10, DeveloperCalledJamis.unscoped.poor.length end - - def test_multiple_default_scope_calls_are_deprecated - klass = Class.new(ActiveRecord::Base) - - assert_not_deprecated do - klass.send(:default_scope, :foo => :bar) - end - - assert_deprecated do - klass.send(:default_scope, :foo => :bar) - end - - assert_equal 2, klass.default_scopes.length - end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 10701dd6fd..152f804e16 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -129,28 +129,40 @@ end class DeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis') scope :poor, where('salary < 150000') end class PoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis', :salary => 50000) end class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis self.table_name = 'developers' - ActiveSupport::Deprecation.silence do - default_scope where(:salary => 50000) - end + default_scope where(:salary => 50000) end class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' + default_scope where(:name => 'Jamis') + default_scope where(:salary => 50000) +end - ActiveSupport::Deprecation.silence do - default_scope where(:salary => 50000) - end +module SalaryDefaultScope + extend ActiveSupport::Concern + + included { default_scope where(:salary => 50000) } end + +class ModuleIncludedPoorDeveloperCalledJamis < DeveloperCalledJamis + self.table_name = 'developers' + + include SalaryDefaultScope +end + + -- cgit v1.2.3 From fc343d26ffec073c7df64a8a4c2508104f78e9d4 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 28 Apr 2011 09:48:16 -0700 Subject: clearing statement from cache on exception in order to support older versions of mysql --- .../active_record/connection_adapters/mysql_adapter.rb | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 2c05ff21f9..1136dbc45e 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -413,9 +413,19 @@ module ActiveRecord stmt = cache[:stmt] end - stmt.execute(*binds.map { |col, val| - type_cast(val, col) - }) + + begin + stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + rescue Mysql::Error => e + # Older versions of MySQL leave the prepared statement in a bad + # place when an error occurs. To support older mysql versions, we + # need to close the statement and delete the statement from the + # cache. + stmt.close + @statements.delete sql + raise e + end + if metadata = stmt.result_metadata cols = cache[:cols] ||= metadata.fetch_fields.map { |field| field.name -- cgit v1.2.3 From a869382a9fa241f72a7a73d4a9738531c4c37ba5 Mon Sep 17 00:00:00 2001 From: Aditya Sanghi Date: Fri, 29 Apr 2011 01:49:45 +0530 Subject: Allow AM/PM in datetime selectors --- actionpack/lib/action_view/helpers/date_helper.rb | 35 +++++++++++++++ actionpack/test/template/date_helper_test.rb | 55 +++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 9277359d5c..7e8ad54d5f 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -468,6 +468,9 @@ module ActionView # # generic prompt. # select_hour(13, :prompt => 'Choose hour') # + # # Generate a select field for hours in the AM/PM format + # select_hour(my_time, :ampm => true) + # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end @@ -600,6 +603,18 @@ module ActionView POSITION = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze + ampm = ["AM","PM"].map do |s| + ["12 #{s}"] + (1..11).map{|x| "#{sprintf('%02d',x)} #{s}"} + end.flatten + + AMPM_TRANSLATION = Hash[ + [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], + [4, "04 AM"], [5, "05 AM"], [6, "06 AM"], [7, "07 AM"], + [8, "08 AM"], [9, "09 AM"], [10, "10 AM"], [11, "11 AM"], + [12, "12 PM"], [13, "01 PM"], [14, "02 PM"], [15, "03 PM"], + [16, "04 PM"], [17, "05 PM"], [18, "06 PM"], [19, "07 PM"], + [20, "08 PM"], [21, "09 PM"], [22, "10 PM"], [23, "11 PM"]] + ].freeze def initialize(datetime, options = {}, html_options = {}) @options = options.dup @@ -691,6 +706,8 @@ module ActionView def select_hour if @options[:use_hidden] || @options[:discard_hour] build_hidden(:hour, hour) + elsif @options[:ampm] + build_select(:hour, build_ampm_options(hour, :end => 23)) else build_options_and_select(:hour, hour, :end => 23) end @@ -801,6 +818,24 @@ module ActionView build_select(type, build_options(selected, options)) end + def build_ampm_options(selected, options = {}) + start = options.delete(:start) || 0 + stop = options.delete(:end) || 23 + step = options.delete(:step) || 1 + options.reverse_merge!({:leading_zeros => true}) + leading_zeros = options.delete(:leading_zeros) + + select_options = [] + start.step(stop, step) do |i| + text = AMPM_TRANSLATION[i] + value = leading_zeros ? sprintf("%02d", i) : i + tag_options = { :value => value } + tag_options[:selected] = "selected" if selected == i + select_options << content_tag(:option, text, tag_options) + end + (select_options.join("\n") + "\n").html_safe + end + # Build select option html from date value and options # build_options(15, :start => 1, :end => 31) # => " diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 12d2410f49..a47a7a9b1f 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -416,6 +416,14 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18)) end + def test_select_hour_with_ampm + expected = %(\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :ampm => true) + end + def test_select_hour_with_disabled expected = %(\n) + expected << %(\n\n\n) + expected << "\n" + + expected << %(\n" + + expected << %(\n" + + expected << " — " + + expected << %(\n" + + expected << " : " + + expected << %(\n" + + assert_dom_equal expected, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :ampm => true) + end + + def test_select_datetime_with_separators expected = %(\n) + expected << %(\n) + expected << %(\n) + + expected << %(\n" + + expected << " : " + + expected << %(\n" + + assert_dom_equal expected, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => false, :ampm => true) + end + def test_select_time_with_separator expected = %(\n) expected << %(\n) -- cgit v1.2.3 From 610e4d9f24b8fe0c372104481a733b48d2270c3f Mon Sep 17 00:00:00 2001 From: Aditya Sanghi Date: Fri, 29 Apr 2011 02:03:56 +0530 Subject: add more documentation; remove unused assignment --- actionpack/lib/action_view/helpers/date_helper.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 7e8ad54d5f..9e1be05e6c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -207,7 +207,8 @@ module ActionView # 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 :include_seconds. + # +object+). You can include the seconds with :include_seconds. You can get hours in the AM/PM format + # with :ampm option. # # This method will also generate 3 input hidden tags, for the actual year, month and day unless the option # :ignore_date is set to +true+. @@ -230,6 +231,9 @@ module ActionView # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours # time_select("post", "written_on", :prompt => true) # generic prompts for all # + # # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM. + # time_select 'game', 'game_time', {:ampm => true} + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # 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 @@ -257,6 +261,9 @@ module ActionView # # be stored in the trip variable in the departing attribute. # datetime_select("trip", "departing", :default => 3.days.from_now) # + # # Generate a datetime select with hours in the AM/PM format + # datetime_select("post", "written_on", :ampm => true) + # # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) @@ -306,6 +313,9 @@ module ActionView # # my_date_time (four days after today) # select_datetime(my_date_time, :discard_type => true) # + # # Generate a datetime field with hours in the AM/PM format + # select_datetime(my_date_time, :ampm => true) + # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') @@ -387,6 +397,9 @@ module ActionView # # separated by ':' and includes an input for seconds # select_time(my_time, :time_separator => ':', :include_seconds => true) # + # # Generate a time select field with hours in the AM/PM format + # select_time(my_time, :ampm => true) + # # # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts. # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours @@ -603,9 +616,6 @@ module ActionView POSITION = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }.freeze - ampm = ["AM","PM"].map do |s| - ["12 #{s}"] + (1..11).map{|x| "#{sprintf('%02d',x)} #{s}"} - end.flatten AMPM_TRANSLATION = Hash[ [[0, "12 AM"], [1, "01 AM"], [2, "02 AM"], [3, "03 AM"], -- cgit v1.2.3 From bf5cf5db86fa0aeb818d32541a35310de992f426 Mon Sep 17 00:00:00 2001 From: Aditya Sanghi Date: Fri, 29 Apr 2011 02:54:37 +0530 Subject: :if should not fire on validations when not in context with :on --- activemodel/lib/active_model/validations.rb | 2 +- activemodel/lib/active_model/validations/callbacks.rb | 4 ++-- activemodel/test/cases/validations_test.rb | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index d968609e67..5e567307f3 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -133,7 +133,7 @@ module ActiveModel if options.key?(:on) options = options.dup options[:if] = Array.wrap(options[:if]) - options[:if] << "validation_context == :#{options[:on]}" + options[:if].unshift("validation_context == :#{options[:on]}") end args << options set_callback(:validate, *args, &block) diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index adc2867ad0..f187d6f378 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -31,7 +31,7 @@ module ActiveModel options = args.last if options.is_a?(Hash) && options[:on] options[:if] = Array.wrap(options[:if]) - options[:if] << "self.validation_context == :#{options[:on]}" + options[:if].unshift("self.validation_context == :#{options[:on]}") end set_callback(:validation, :before, *args, &block) end @@ -41,7 +41,7 @@ module ActiveModel options[:prepend] = true options[:if] = Array.wrap(options[:if]) options[:if] << "!halted" - options[:if] << "self.validation_context == :#{options[:on]}" if options[:on] + options[:if].unsfhit("self.validation_context == :#{options[:on]}") if options[:on] set_callback(:validation, :after, *(args << options), &block) end end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 2f36195627..0b50acf913 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -212,6 +212,20 @@ class ValidationsTest < ActiveModel::TestCase assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0] end + def test_validaton_with_if_and_on + Topic.validates_presence_of :title, :if => Proc.new{|x| x.author_name = "bad"; true }, :on => :update + + t = Topic.new(:title => "") + + # If block should not fire + assert t.valid? + assert t.author_name.nil? + + # If block should fire + assert t.invalid?(:update) + assert t.author_name == "bad" + end + def test_invalid_should_be_the_opposite_of_valid Topic.validates_presence_of :title -- cgit v1.2.3 From 36ee2bafec9f38fc3de404d7708f27a66960ee10 Mon Sep 17 00:00:00 2001 From: Aditya Sanghi Date: Fri, 29 Apr 2011 09:53:02 +0530 Subject: fix typo --- activemodel/lib/active_model/validations/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index f187d6f378..22a77320dc 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -41,7 +41,7 @@ module ActiveModel options[:prepend] = true options[:if] = Array.wrap(options[:if]) options[:if] << "!halted" - options[:if].unsfhit("self.validation_context == :#{options[:on]}") if options[:on] + options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on] set_callback(:validation, :after, *(args << options), &block) end end -- cgit v1.2.3 From 8bce6e761d52afd68bb06ca9ff8222f072e06cc8 Mon Sep 17 00:00:00 2001 From: Aditya Sanghi Date: Fri, 29 Apr 2011 10:21:16 +0530 Subject: DRY this baby up --- actionpack/lib/action_view/helpers/date_helper.rb | 27 ++++------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 9e1be05e6c..080cf0a866 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -716,10 +716,8 @@ module ActionView def select_hour if @options[:use_hidden] || @options[:discard_hour] build_hidden(:hour, hour) - elsif @options[:ampm] - build_select(:hour, build_ampm_options(hour, :end => 23)) else - build_options_and_select(:hour, hour, :end => 23) + build_options_and_select(:hour, hour, :end => 23, :ampm => @options[:ampm]) end end @@ -828,24 +826,6 @@ module ActionView build_select(type, build_options(selected, options)) end - def build_ampm_options(selected, options = {}) - start = options.delete(:start) || 0 - stop = options.delete(:end) || 23 - step = options.delete(:step) || 1 - options.reverse_merge!({:leading_zeros => true}) - leading_zeros = options.delete(:leading_zeros) - - select_options = [] - start.step(stop, step) do |i| - text = AMPM_TRANSLATION[i] - value = leading_zeros ? sprintf("%02d", i) : i - tag_options = { :value => value } - tag_options[:selected] = "selected" if selected == i - select_options << content_tag(:option, text, tag_options) - end - (select_options.join("\n") + "\n").html_safe - end - # Build select option html from date value and options # build_options(15, :start => 1, :end => 31) # => " @@ -855,7 +835,7 @@ module ActionView start = options.delete(:start) || 0 stop = options.delete(:end) || 59 step = options.delete(:step) || 1 - options.reverse_merge!({:leading_zeros => true}) + options.reverse_merge!({:leading_zeros => true, :ampm => false}) leading_zeros = options.delete(:leading_zeros) select_options = [] @@ -863,7 +843,8 @@ module ActionView value = leading_zeros ? sprintf("%02d", i) : i tag_options = { :value => value } tag_options[:selected] = "selected" if selected == i - select_options << content_tag(:option, value, tag_options) + text = options[:ampm] ? AMPM_TRANSLATION[i] : value + select_options << content_tag(:option, text, tag_options) end (select_options.join("\n") + "\n").html_safe end -- cgit v1.2.3 From 66a18855eafa71c11a37333ce1314889cbd0f742 Mon Sep 17 00:00:00 2001 From: gmile Date: Sat, 12 Feb 2011 14:42:20 +0200 Subject: Test that passing nil member of array in conditions retrieves records with nil value on a selected field. --- activerecord/test/cases/finder_test.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 655437318f..9b6363902e 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1045,6 +1045,28 @@ class FinderTest < ActiveRecord::TestCase :order => ' author_addresses_authors.id DESC ', :limit => 3).size end + def test_find_with_nil_inside_set_passed_for_attribute + client_of = Company.find( + :all, + :conditions => { + :client_of => [2, 1, nil], + :name => ['37signals', 'Summit', 'Microsoft'] }, + :order => 'client_of DESC' + ).map { |x| x.client_of } + + assert_equal [2, 1, nil], client_of + end + + def test_find_with_nil_inside_set_passed_for_attribute + client_of = Company.find( + :all, + :conditions => { :client_of => [nil] }, + :order => 'client_of DESC' + ).map { |x| x.client_of } + + assert_equal [nil], client_of + end + def test_with_limiting_with_custom_select posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id') assert_equal 3, posts.size -- cgit v1.2.3 From 850b3ca4779da201d4805a12fe29d9d9a491739e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 11:01:59 -0700 Subject: supporting nil when passed in as an IN clause --- .../lib/active_record/relation/predicate_builder.rb | 13 ++++++++++++- activerecord/test/cases/finder_test.rb | 7 ++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 982b3d7e9f..2814771002 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -25,7 +25,18 @@ module ActiveRecord values = value.to_a.map { |x| x.is_a?(ActiveRecord::Base) ? x.id : x } - attribute.in(values) + + if values.include?(nil) + values = values.compact + if values.empty? + attribute.eq nil + else + attribute.in(values.compact).or attribute.eq(nil) + end + else + attribute.in(values) + end + when Range, Arel::Relation attribute.in(value) when ActiveRecord::Base diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 9b6363902e..be4ba18555 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1045,7 +1045,7 @@ class FinderTest < ActiveRecord::TestCase :order => ' author_addresses_authors.id DESC ', :limit => 3).size end - def test_find_with_nil_inside_set_passed_for_attribute + def test_find_with_nil_inside_set_passed_for_one_attribute client_of = Company.find( :all, :conditions => { @@ -1054,7 +1054,8 @@ class FinderTest < ActiveRecord::TestCase :order => 'client_of DESC' ).map { |x| x.client_of } - assert_equal [2, 1, nil], client_of + assert client_of.include?(nil) + assert_equal [2, 1].sort, client_of.compact.sort end def test_find_with_nil_inside_set_passed_for_attribute @@ -1064,7 +1065,7 @@ class FinderTest < ActiveRecord::TestCase :order => 'client_of DESC' ).map { |x| x.client_of } - assert_equal [nil], client_of + assert_equal [], client_of.compact end def test_with_limiting_with_custom_select -- cgit v1.2.3 From 245017765644745b0e550ceaf815ea4e73a12681 Mon Sep 17 00:00:00 2001 From: Bradley Harris Date: Fri, 29 Apr 2011 14:09:06 -0400 Subject: Added test for empty CDATA bug in from_xml --- activesupport/test/core_ext/hash_ext_test.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 012b956d7f..20b1362bcf 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -897,7 +897,13 @@ class HashToXmlTest < Test::Unit::TestCase hash = Hash.from_xml(xml) assert_equal "bacon is the best", hash['blog']['name'] end - + + def test_empty_cdata_from_xml + xml = "" + + assert_equal "", Hash.from_xml(xml)["content"] + end + def test_xsd_like_types_from_xml bacon_xml = <<-EOT @@ -940,7 +946,7 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"] end - + def test_should_use_default_value_for_unknown_key hash_wia = HashWithIndifferentAccess.new(3) assert_equal 3, hash_wia[:new_key] -- cgit v1.2.3 From f1df6b2dce8bda94186d66b9edd5e727e0d05878 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 28 Apr 2011 15:35:19 -0700 Subject: postgresql supports prepare statement deletes --- .../abstract/database_statements.rb | 11 ++++- .../connection_adapters/postgresql_adapter.rb | 53 +++++++++++++--------- activerecord/lib/active_record/persistence.rb | 10 +++- activerecord/lib/active_record/relation.rb | 4 +- 4 files changed, 52 insertions(+), 26 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 70da9d5f1e..6d52cc344d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -62,6 +62,13 @@ module ActiveRecord exec_query(sql, name, binds) end + # Executes delete +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is the logged along with + # the executed +sql+ statement. + def exec_delete(sql, name, binds) + exec_query(sql, name, binds) + end + # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in @@ -82,8 +89,8 @@ module ActiveRecord end # Executes the delete statement and returns the number of rows affected. - def delete(sql, name = nil) - delete_sql(sql, name) + def delete(sql, name = nil, binds = []) + exec_delete(sql, name, binds) end # Checks whether there is currently no transaction active. This is done diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 0c2afc180b..255da7d183 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -543,30 +543,26 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = []) - return exec_no_cache(sql, name) if binds.empty? - log(sql, name, binds) do - unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" - @connection.prepare nextkey, sql - @statements[sql] = nextkey - end + result = binds.empty? ? exec_no_cache(sql, binds) : + exec_cache(sql, binds) - key = @statements[sql] - - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - result = @connection.get_last_result ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) result.clear return ret end end + def exec_delete(sql, name = 'SQL', binds = []) + log(sql, name, binds) do + result = binds.empty? ? exec_no_cache(sql, binds) : + exec_cache(sql, binds) + affected = result.cmd_tuples + result.clear + affected + end + end + def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk _, table = extract_schema_and_table(sql.split(" ", 4)[2]) @@ -980,13 +976,26 @@ module ActiveRecord end private - def exec_no_cache(sql, name) - log(sql, name) do - result = @connection.async_exec(sql) - ret = ActiveRecord::Result.new(result.fields, result_as_array(result)) - result.clear - ret + def exec_no_cache(sql, binds) + @connection.async_exec(sql) + end + + def exec_cache(sql, binds) + unless @statements.key? sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey end + + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result end # The internal PostgreSQL identifier of the money data type. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 787ac977e0..b4531ed35f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -77,7 +77,15 @@ module ActiveRecord def destroy if persisted? IdentityMap.remove(self) if IdentityMap.enabled? - self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all + pk = self.class.primary_key + column = self.class.columns_hash[pk] + substitute = connection.substitute_at(column, 0) + + relation = self.class.unscoped.where( + self.class.arel_table[pk].eq(substitute)) + + relation.bind_values = [[column, id]] + relation.delete_all end @destroyed = true diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8e5f66ec1d..658a949331 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -338,7 +338,9 @@ module ActiveRecord where(conditions).delete_all else statement = arel.compile_delete - affected = @klass.connection.delete statement.to_sql + affected = @klass.connection.delete( + statement.to_sql, 'SQL', bind_values) + reset affected end -- cgit v1.2.3 From 453bd2f39dfc59641a2d9028088c99e0fd529242 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 28 Apr 2011 15:42:16 -0700 Subject: sqlite3 supports prepared statement deletes --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ed5006dcec..ad511a0731 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -178,6 +178,11 @@ module ActiveRecord end end + def exec_delete(sql, name = 'SQL', binds = []) + exec_query(sql, name, binds) + @connection.changes + end + def last_inserted_id(result) @connection.last_insert_row_id end -- cgit v1.2.3 From 4c41be9b83792e6b5a4d06c260add1d773ad2f33 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 09:06:37 -0700 Subject: statement cache for deletes working on mysql --- .../connection_adapters/mysql_adapter.rb | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1136dbc45e..f173bab51d 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -490,6 +490,45 @@ module ActiveRecord @connection.affected_rows end + def exec_delete(sql, name, binds) + log(sql, name, binds) do + result = nil + + cache = {} + if binds.empty? + stmt = @connection.prepare(sql) + else + cache = @statements[sql] ||= { + :stmt => @connection.prepare(sql) + } + stmt = cache[:stmt] + end + + + begin + stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + rescue Mysql::Error => e + # Older versions of MySQL leave the prepared statement in a bad + # place when an error occurs. To support older mysql versions, we + # need to close the statement and delete the statement from the + # cache. + stmt.close + @statements.delete sql + raise e + end + + if metadata = stmt.result_metadata + metadata.free + end + + result = stmt.affected_rows + stmt.free_result + stmt.close if binds.empty? + + result + end + end + def begin_db_transaction #:nodoc: exec_without_stmt "BEGIN" rescue Mysql::Error -- cgit v1.2.3 From ed775c66bcf20dbc1de9654ace58c2d30e4d0427 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 09:22:01 -0700 Subject: refactor exec_delete to reuse the statement cache from exec_query --- .../connection_adapters/mysql_adapter.rb | 114 ++++++++------------- 1 file changed, 44 insertions(+), 70 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index f173bab51d..1fd286622b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -401,44 +401,9 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) log(sql, name, binds) do - result = nil - - cache = {} - if binds.empty? - stmt = @connection.prepare(sql) - else - cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) - } - stmt = cache[:stmt] - end - - - begin - stmt.execute(*binds.map { |col, val| type_cast(val, col) }) - rescue Mysql::Error => e - # Older versions of MySQL leave the prepared statement in a bad - # place when an error occurs. To support older mysql versions, we - # need to close the statement and delete the statement from the - # cache. - stmt.close - @statements.delete sql - raise e - end - - if metadata = stmt.result_metadata - cols = cache[:cols] ||= metadata.fetch_fields.map { |field| - field.name - } - - metadata.free - result = ActiveRecord::Result.new(cols, stmt.to_a) + exec_stmt(sql, name, binds) do |cols, stmt| + ActiveRecord::Result.new(cols, stmt.to_a) if cols end - - stmt.free_result - stmt.close if binds.empty? - - result end end @@ -492,40 +457,9 @@ module ActiveRecord def exec_delete(sql, name, binds) log(sql, name, binds) do - result = nil - - cache = {} - if binds.empty? - stmt = @connection.prepare(sql) - else - cache = @statements[sql] ||= { - :stmt => @connection.prepare(sql) - } - stmt = cache[:stmt] - end - - - begin - stmt.execute(*binds.map { |col, val| type_cast(val, col) }) - rescue Mysql::Error => e - # Older versions of MySQL leave the prepared statement in a bad - # place when an error occurs. To support older mysql versions, we - # need to close the statement and delete the statement from the - # cache. - stmt.close - @statements.delete sql - raise e - end - - if metadata = stmt.result_metadata - metadata.free + exec_stmt(sql, name, binds) do |cols, stmt| + stmt.affected_rows end - - result = stmt.affected_rows - stmt.free_result - stmt.close if binds.empty? - - result end end @@ -882,6 +816,46 @@ module ActiveRecord end private + def exec_stmt(sql, name, binds) + cache = {} + if binds.empty? + stmt = @connection.prepare(sql) + else + cache = @statements[sql] ||= { + :stmt => @connection.prepare(sql) + } + stmt = cache[:stmt] + end + + + begin + stmt.execute(*binds.map { |col, val| type_cast(val, col) }) + rescue Mysql::Error => e + # Older versions of MySQL leave the prepared statement in a bad + # place when an error occurs. To support older mysql versions, we + # need to close the statement and delete the statement from the + # cache. + stmt.close + @statements.delete sql + raise e + end + + cols = nil + if metadata = stmt.result_metadata + cols = cache[:cols] ||= metadata.fetch_fields.map { |field| + field.name + } + end + + result = yield [cols, stmt] + + stmt.result_metadata.free if cols + stmt.free_result + stmt.close if binds.empty? + + result + end + def connect encoding = @config[:encoding] if encoding -- cgit v1.2.3 From 00f030c6e107f92fc9ef13a8c77e31b5e5299e6e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 09:27:19 -0700 Subject: supporting delete with bind parameters in mysql2 --- .../lib/active_record/connection_adapters/mysql2_adapter.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index cf68ddc2da..199bf7d494 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -290,6 +290,14 @@ module ActiveRecord execute sql.gsub('?') { quote(*binds.shift.reverse) }, name end + def exec_delete(sql, name, binds) + binds = binds.dup + + # Pretend to support bind parameters + execute sql.gsub('?') { quote(*binds.shift.reverse) }, name + @connection.affected_rows + end + def last_inserted_id(result) @connection.last_id end -- cgit v1.2.3 From 146474256cf2f09f9706980032688b2c7d484e24 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 09:41:37 -0700 Subject: lean on rubygems to provide error messages about missing gems --- .../lib/active_record/connection_adapters/mysql_adapter.rb | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1fd286622b..07608ea6a7 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -3,15 +3,8 @@ require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/object/blank' require 'set' -begin - require 'mysql' -rescue LoadError - raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'" -end - -unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash) - raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'" -end +gem 'mysql', '~> 2.8.1' +require 'mysql' class Mysql class Time -- cgit v1.2.3 From 58ad5e1859caa5e384cc3df2360efc3cfd66c48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 29 Apr 2011 19:55:33 +0200 Subject: Make postgresql faster on development (thanks to @tapajos). --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 255da7d183..b1f2f93462 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -122,6 +122,14 @@ module ActiveRecord # Extracts the value from a PostgreSQL column default definition. def self.extract_value_from_default(default) case default + # This is a performance optimization for Ruby 1.9.2 in development. + # If the value is nil, we return nil straight away without checking + # the regular expressions. If we check each regular expression, + # Regexp#=== will call NilClass#to_str, which will trigger + # method_missing (defined by whiny nil in ActiveSupport) which + # makes this method very very slow. + when NilClass + nil # Numeric types when /\A\(?(-?\d+(\.\d*)?\)?)\z/ $1 -- cgit v1.2.3 From 0c76eb1106dc82bb0e3cc50498383d6f992da4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 29 Apr 2011 20:15:19 +0200 Subject: No need for &block here. --- activesupport/lib/active_support/whiny_nil.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index bcedbfd57a..577db5018e 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -41,7 +41,7 @@ class NilClass end private - def method_missing(method, *args, &block) + def method_missing(method, *args) if klass = METHOD_CLASS_MAP[method] raise_nil_warning_for klass, method, caller else -- cgit v1.2.3 From 0eea560b485b6aac0e13119b7a6b925c24051f00 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 11:43:05 -0700 Subject: requring pg ~> 0.11, so remove conditional code for supporting older versions of the gem --- .../connection_adapters/postgresql_adapter.rb | 68 ++++------------------ 1 file changed, 12 insertions(+), 56 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b1f2f93462..ce0cfa904a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -245,7 +245,6 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil @table_alias_length = nil - @postgresql_version = nil @statements = {} connect @@ -267,28 +266,16 @@ module ActiveRecord # Is this connection alive and ready for queries? def active? - if @connection.respond_to?(:status) - @connection.status == PGconn::CONNECTION_OK - else - # We're asking the driver, not Active Record, so use @connection.query instead of #query - @connection.query 'SELECT 1' - true - end - # postgres-pr raises a NoMethodError when querying if no connection is available. - rescue PGError, NoMethodError + @connection.status == PGconn::CONNECTION_OK + rescue PGError false end # Close then reopen the connection. def reconnect! - if @connection.respond_to?(:reset) - clear_cache! - @connection.reset - configure_connection - else - disconnect! - connect - end + clear_cache! + @connection.reset + configure_connection end def reset! @@ -441,17 +428,17 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== - def supports_disable_referential_integrity?() #:nodoc: + def supports_disable_referential_integrity? #:nodoc: true end def disable_referential_integrity #:nodoc: - if supports_disable_referential_integrity?() then + if supports_disable_referential_integrity? then execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) end yield ensure - if supports_disable_referential_integrity?() then + if supports_disable_referential_integrity? then execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) end end @@ -525,11 +512,7 @@ module ActiveRecord # Queries the database and returns the results in an Array-like object def query(sql, name = nil) #:nodoc: log(sql, name) do - if @async - res = @connection.async_exec(sql) - else - res = @connection.exec(sql) - end + @connection.async_exec(sql) return result_as_array(res) end end @@ -538,11 +521,7 @@ module ActiveRecord # or raising a PGError exception otherwise. def execute(sql, name = nil) log(sql, name) do - if @async - @connection.async_exec(sql) - else - @connection.exec(sql) - end + @connection.async_exec(sql) end end @@ -954,22 +933,7 @@ module ActiveRecord protected # Returns the version of the connected PostgreSQL version. def postgresql_version - @postgresql_version ||= - if @connection.respond_to?(:server_version) - @connection.server_version - else - # Mimic PGconn.server_version behavior - begin - if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/ - major, minor, tiny = $1.split(".") - (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i - else - 0 - end - rescue - 0 - end - end + @connection.server_version end def translate_exception(exception, message) @@ -1015,10 +979,6 @@ module ActiveRecord # connected server's characteristics. def connect @connection = PGconn.connect(*@connection_parameters) - PGconn.translate_results = false if PGconn.respond_to?(:translate_results=) - - # Ignore async_exec and async_query when using postgres-pr. - @async = @connection.respond_to?(:async_exec) # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision @@ -1032,11 +992,7 @@ module ActiveRecord # This is called by #connect and should not be called manually. def configure_connection if @config[:encoding] - if @connection.respond_to?(:set_client_encoding) - @connection.set_client_encoding(@config[:encoding]) - else - execute("SET client_encoding TO '#{@config[:encoding]}'") - end + @connection.set_client_encoding(@config[:encoding]) end self.client_min_messages = @config[:min_messages] if @config[:min_messages] self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] -- cgit v1.2.3 From 60c877c43baa588db277fb4eca317e2f7884be70 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 11:47:04 -0700 Subject: convert query results to a list of lists --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ce0cfa904a..f3e0248e39 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -512,8 +512,7 @@ module ActiveRecord # Queries the database and returns the results in an Array-like object def query(sql, name = nil) #:nodoc: log(sql, name) do - @connection.async_exec(sql) - return result_as_array(res) + result_as_array @connection.async_exec(sql) end end -- cgit v1.2.3 From aba149d702e4dcd999ce1b71688ec15b0c637875 Mon Sep 17 00:00:00 2001 From: Bradley Harris Date: Fri, 29 Apr 2011 14:47:49 -0400 Subject: Fix bug with empty CDATA not being handled in Hash.from_xml --- activesupport/lib/active_support/core_ext/hash/conversions.rb | 3 ++- activesupport/test/core_ext/hash_ext_test.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 61a1d88b0e..102378a029 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -108,7 +108,8 @@ class Hash raise "can't typecast #{entries.inspect}" end end - elsif value['type'] == 'file' || value["__content__"].present? + elsif value['type'] == 'file' || + (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) content = value["__content__"] if parser = ActiveSupport::XmlMini::PARSING[value["type"]] parser.arity == 1 ? parser.call(content) : parser.call(content, value) diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 20b1362bcf..3ef080e1cb 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -899,9 +899,9 @@ class HashToXmlTest < Test::Unit::TestCase end def test_empty_cdata_from_xml - xml = "" + xml = "" - assert_equal "", Hash.from_xml(xml)["content"] + assert_equal "", Hash.from_xml(xml)["data"] end def test_xsd_like_types_from_xml -- cgit v1.2.3 From cc35d5c9e7049e62517642daf67d756312c5a5d2 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 29 Apr 2011 22:14:22 +0200 Subject: JavaScript expansions are ignored if the application uses the asset pipeline, warn the user --- .../lib/rails/generators/rails/app/templates/config/application.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index ff8e6e5f3e..6b2aa97faa 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -39,6 +39,10 @@ module <%= app_const_base %> # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + # Please note that JavaScript expansions are *ignored altogether* if the asset + # pipeline is enabled (see config.asset.enabled below). Put your defaults in + # app/assets/javascripts/application.js in that case. + # # JavaScript files you want as :defaults (application.js is always included). <% if options[:skip_javascript] -%> config.action_view.javascript_expansions[:defaults] = %w() -- cgit v1.2.3 From 564fe7f633c07cf6f0d960dfa2cd9e713a58d784 Mon Sep 17 00:00:00 2001 From: Brian Cardarella Date: Fri, 29 Apr 2011 14:17:40 -0700 Subject: Fixed comment 'config.asset' => 'config.assets' --- railties/lib/rails/generators/rails/app/templates/config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 6b2aa97faa..46ccb7e078 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -40,7 +40,7 @@ module <%= app_const_base %> # config.i18n.default_locale = :de # Please note that JavaScript expansions are *ignored altogether* if the asset - # pipeline is enabled (see config.asset.enabled below). Put your defaults in + # pipeline is enabled (see config.assets.enabled below). Put your defaults in # app/assets/javascripts/application.js in that case. # # JavaScript files you want as :defaults (application.js is always included). -- cgit v1.2.3 From 85c8d73642f03410b86ee842edcfe0eba1d4252c Mon Sep 17 00:00:00 2001 From: Dan Pickett Date: Fri, 29 Apr 2011 19:17:34 -0400 Subject: update contributors guide to reflect GitHub use * outlines how to review pull requests * outlines how to issue pull requests * changed all references from LH => GH --- .../source/contributing_to_ruby_on_rails.textile | 67 ++++++++++------------ 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index cbc4acfeca..95457578b4 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -2,7 +2,7 @@ h2. Contributing to Ruby on Rails This guide covers ways in which _you_ can become a part of the ongoing development of Ruby on Rails. After reading it, you should be familiar with: -* Using Lighthouse to report issues +* Using GitHub to report issues * Cloning master and running the test suite * Helping to resolve existing issues * Contributing to the Ruby on Rails documentation @@ -14,29 +14,29 @@ endprologue. h3. Reporting an Issue -Ruby on Rails uses a "Lighthouse project":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/ to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) Lighthouse account in order to comment on issues or to upload patches. +Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to comment on issues or to upload patches. NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing. h4. Creating a Bug Report -If you've found a problem in Ruby on Rails which is not a security risk do a search in Lighthouse in case it was already reported. If you find no ticket addressing it you can "add a new one":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new. (See the next section for reporting security issues.) +If you've found a problem in Ruby on Rails which is not a security risk do a search in GitHub Issues in case it was already reported. If you find no issue addressing it you can "add a new one":https://github.com/rails/rails/issues/new. (See the next section for reporting security issues.) -At the minimum, your ticket needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. +At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. You shouldn't assign the bug to a particular core developer unless you know for sure which developer will be handling that issue. The core team periodically reviews issues and assigns developers and milestones to them. You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the "Choose some tags" textbox), rather than creating new tags. -Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. +Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating a issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. h4. Special Treatment for Security Issues -WARNING: Please do not report security vulnerabilities on public Lighthouse tickets. The "Rails security policy page":http://rubyonrails.org/security details the procedure to follow for security issues. +WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The "Rails security policy page":http://rubyonrails.org/security details the procedure to follow for security issues. h4. What About Feature Requests? -Please don't put "feature request" tickets into Lighthouse. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in Lighthouse with no code, you can expect it to be marked "invalid" as soon as it's reviewed. +Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. h3. Running the Test Suite @@ -216,11 +216,11 @@ TIP: You may want to "put your git branch name in your shell prompt":http://qugs h3. Helping to Resolve Existing Issues -As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "open tickets":https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets?q=state%3Aopen list in Lighthouse, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: +As a next step beyond reporting issues, you can help the core team resolve existing issues. If you check the "Everyone's Issues":https://github.com/rails/rails/issues?sort=created&direction=desc&state=open&page=1 list in GitHub Issues, you'll find lots of issues already requiring attention. What can you do for these? Quite a bit, actually: h4. Verifying Bug Reports -For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the ticket saying that you're seeing the same thing. +For starters, it helps to just verify bug reports. Can you reproduce the reported issue on your own computer? If so, you can add a comment to the issue saying that you're seeing the same thing. If something is very vague, can you help squish it down into something specific? Maybe you can provide additional information to help reproduce a bug, or eliminate needless steps that aren't required to help demonstrate the problem. @@ -230,26 +230,27 @@ Anything you can do to make bug reports more succinct or easier to reproduce is h4. Testing Patches -You can also help out by examining patches that have been submitted to Ruby on Rails via Lighthouse. To apply someone's changes you need to first create a dedicated branch: +You can also help out by examining pull requests that have been submitted to Ruby on Rails via GitHub. To apply someone's changes you need to first create a dedicated branch: $ git checkout -b testing_branch -Then you can apply their patch: +Then you can use their remote to update your codebase. For example, let's say the github user JohnSmith has forked and pushed to the master branch located at http://github.com/JohnSmith/rails. -$ git am their-patch-file.diff +$ git remote add JohnSmith git://github.com/JohnSmith/rails.git +$ git pull JohnSmith master -After applying a patch, test it out! Here are some things to think about: +After applying their branch, test it out! Here are some things to think about: -* Does the patch actually work? +* Does the change actually work? * Are you happy with the tests? Can you follow what they're testing? Are there any tests missing? * Does it have proper documentation coverage? Should documentation elsewhere be updated? * Do you like the implementation? Can you think of a nicer or faster way to implement a part of their change? -Once you're happy that the patch contains a good change, comment on the Lighthouse ticket indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: +Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like:
I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too. @@ -261,7 +262,7 @@ h3. Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: The guides help you to learn Ruby on Rails, and the API is a reference. -You can create a ticket in Lighthouse to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":https://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +You can create an issue in GitHub issues to fix or expand documentation. However, if you're confident about your changes you can push them yourself directly via "docrails":https://github.com/lifo/docrails/tree/master. docrails is a branch with an *open commit policy* and public write access. Commits to docrails are still reviewed, but that happens after they are pushed. docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. When working with documentation, please take into account the "API Documentation Guidelines":api_documentation_guidelines.html and the "Ruby on Rails Guides Guidelines":ruby_on_rails_guides_guidelines.html. @@ -314,10 +315,6 @@ You should not be the only person who looks at the code before you submit it. Yo You might also want to check out the "RailsBridge BugMash":http://wiki.railsbridge.org/projects/railsbridge/wiki/BugMash as a way to get involved in a group effort to improve Rails. This can help you get started and help check your code when you're writing your first patches. -h4. Create a Lighthouse Ticket - -Now create a ticket for your patch. Go to the "new ticket":http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/new page at Lighthouse. Fill in a reasonable title and description, as well as tag the ticket with the ‘patch’ tag and whatever other subject area tags make sense. Write down your ticket number, for you will need it in the following step. - h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: @@ -346,33 +343,29 @@ $ git rebase master No conflicts? Tests still pass? Change still seems reasonable to you? Then move on. -h4. Create a Patch - -Now you can create a patch file to share with other developers (and with the core team). Still in your branch, run +h4. Fork - -$ git commit -a -$ git format-patch master --stdout > my_new_patch.diff - +Navigate to the Rails "GitHub repository":https://github.com/rails/rails and press "Fork" in the upper right hand corner. -Open the diff file in your text editor of choice to sanity check the results, and make sure that no unintended changes crept in. - -You can also perform an extra check by applying the patch to a different dedicated branch: +Add the new remote to your local repository on your local machine: -$ git checkout -b testing_branch -$ git apply --check my_new_patch.diff +$ git remote add mine https://<your user name>@github.com/<your user name>/rails.git -Please make sure the patch does not introduce whitespace errors: +Push to your remote: -$ git apply --whitespace=error-all my_new_patch.diff +$ git push mine master -h4. Attach your Patch to the Lighthouse Ticket +h4. Issue a Pull Request -Now you need to update the ticket by attaching the patch file you just created. +Navigate to the Rails repository you just pushed to (e.g. https://github.com/<your user name>/rails) and press "Pull Request" in the upper right hand corner. + +Ensure the changesets you introduced are included in the "Commits" tab and that the "Files Changed" incorporate all of your changes. + +Fill in some details about your potential patch including a meaningful title. When finished, press "Send pull request." Rails Core will be notified about your submission. h4. Get Some Feedback @@ -390,9 +383,9 @@ All contributions, either via master or docrails, get credit in "Rails Contribut h3. Changelog +* April 29, 2011: Reflect GitHub Issues and Pull Request workflow by "Dan Pickett":http://www.enlightsolutions.com * April 14, 2001: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com * December 28, 2010: Complete revision by "Xavier Noria":credits.html#fxn * April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy * March 2, 2009: Initial draft by "Mike Gunderloy":credits.html#mgunderloy - -- cgit v1.2.3 From 9c0196a6bb14919dbafb9da0ba8190e8fb2a7b0f Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 29 Apr 2011 21:18:49 -0300 Subject: Upload patches is for LH, we use pull requests on GitHub now --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 95457578b4..2ba088a549 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -14,7 +14,7 @@ endprologue. h3. Reporting an Issue -Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to comment on issues or to upload patches. +Ruby on Rails uses "GitHub Issue Tracking":https://github.com/rails/rails/issues to track issues (primarily bugs and contributions of new code). If you've found a bug in Ruby on Rails, this is the place to start. You'll need to create a (free) GitHub account in order to either submit an issue, comment on them or create pull requests. NOTE: Bugs in the most recent released version of Ruby on Rails are likely to get the most attention. Also, the Rails core team is always interested in feedback from those who can take the time to test _edge Rails_ (the code for the version of Rails that is currently under development). Later in this guide you'll find out how to get edge Rails for testing. -- cgit v1.2.3 From 651836a4bd217d7d9992f4d9b34fa855aa7c0229 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 17:20:56 -0700 Subject: table name needs to be classified when instantiating fixtures --- activerecord/lib/active_record/fixtures.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 0939ec2626..96fea741e0 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -509,7 +509,9 @@ class Fixtures # FIXME: Apparently JK uses this. connection = block_given? ? yield : ActiveRecord::Base.connection - files_to_read = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) } + files_to_read = table_names.reject { |table_name| + fixture_is_cached?(connection, table_name) + } unless files_to_read.empty? connection.disable_referential_integrity do @@ -521,7 +523,7 @@ class Fixtures fixtures_map[path] = Fixtures.new( connection, table_name, - class_names[table_name.to_sym], + class_names[table_name.to_sym] || table_name.classify, File.join(fixtures_directory, path)) end -- cgit v1.2.3 From e350641d021829748bfdc08c4e03ddc6607ff79c Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 28 Apr 2011 22:17:10 -0600 Subject: Include CSRF token in remote:true calls --- .../rails/app/templates/vendor/assets/javascripts/jquery_ujs.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js index 4dcb3779a2..8618ac5958 100644 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js +++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js @@ -31,7 +31,12 @@ } else { method = element.attr('data-method'); url = element.attr('href'); - data = null; + + csrf_token = $('meta[name=csrf-token]').attr('content'); + csrf_param = $('meta[name=csrf-param]').attr('content'); + + data = {}; + data[csrf_param] = csrf_token; } $.ajax({ -- cgit v1.2.3 From 1302bf295a2f6cbe01cae6d9fabd76437d596a28 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Fri, 29 Apr 2011 21:22:33 -0300 Subject: Added missing word on guide --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 2ba088a549..6bf078cfde 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -236,7 +236,7 @@ You can also help out by examining pull requests that have been submitted to Rub $ git checkout -b testing_branch -Then you can use their remote to update your codebase. For example, let's say the github user JohnSmith has forked and pushed to the master branch located at http://github.com/JohnSmith/rails. +Then you can use their remote branch to update your codebase. For example, let's say the github user JohnSmith has forked and pushed to the master branch located at http://github.com/JohnSmith/rails. $ git remote add JohnSmith git://github.com/JohnSmith/rails.git -- cgit v1.2.3 From 507626416304b4d64221a3c5652fc0fd5d29e2bf Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 29 Apr 2011 21:44:05 -0300 Subject: Make update_ujs task update the right files --- railties/Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/Rakefile b/railties/Rakefile index 26fa0bf6a5..8ef4f4b53d 100755 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -46,8 +46,8 @@ task :generate_guides do end task :update_ujs do - system "curl https://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/public/javascripts/prototype_ujs.js" - system "curl https://github.com/rails/jquery-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/public/javascripts/jquery_ujs.js" + system "curl https://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js" + system "curl https://github.com/rails/jquery-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js" end # Validate guides ------------------------------------------------------------------------- -- cgit v1.2.3 From 9ef4b70b208c4c55b4fb981e4ec1ff7396a22a1a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 29 Apr 2011 21:44:40 -0300 Subject: Update jquery-ujs and prototype-ujs --- .../vendor/assets/javascripts/jquery_ujs.js | 424 ++++++++++++++------- .../vendor/assets/javascripts/prototype_ujs.js | 26 +- 2 files changed, 290 insertions(+), 160 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js index 8618ac5958..46b2e40e69 100644 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js +++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js @@ -3,151 +3,287 @@ * * Requires jQuery 1.4.3 or later. * https://github.com/rails/jquery-ujs + + * Uploading file using rails.js + * ============================= + * + * By default, browsers do not allow files to be uploaded via AJAX. As a result, if there are any non-blank file fields + * in the remote form, this adapter aborts the AJAX submission and allows the form to submit through standard means. + * + * The `ajax:aborted:file` event allows you to bind your own handler to process the form submission however you wish. + * + * Ex: + * $('form').live('ajax:aborted:file', function(event, elements){ + * // Implement own remote file-transfer handler here for non-blank file inputs passed in `elements`. + * // Returning false in this handler tells rails.js to disallow standard form submission + * return false; + * }); + * + * The `ajax:aborted:file` event is fired when a file-type input is detected with a non-blank value. + * + * Third-party tools can use this hook to detect when an AJAX file upload is attempted, and then use + * techniques like the iframe method to upload the file instead. + * + * Required fields in rails.js + * =========================== + * + * If any blank required inputs (required="required") are detected in the remote form, the whole form submission + * is canceled. Note that this is unlike file inputs, which still allow standard (non-AJAX) form submission. + * + * The `ajax:aborted:required` event allows you to bind your own handler to inform the user of blank required inputs. + * + * !! Note that Opera does not fire the form's submit event if there are blank required inputs, so this event may never + * get fired in Opera. This event is what causes other browsers to exhibit the same submit-aborting behavior. + * + * Ex: + * $('form').live('ajax:aborted:required', function(event, elements){ + * // Returning false in this handler tells rails.js to submit the form anyway. + * // The blank required inputs are passed to this function in `elements`. + * return ! confirm("Would you like to submit the form with missing info?"); + * }); */ (function($) { - // Triggers an event on an element and returns the event result - function fire(obj, name, data) { - var event = new $.Event(name); - obj.trigger(event, data); - return event.result !== false; - } - - // Submits "remote" forms and links with ajax - function handleRemote(element) { - var method, url, data, - dataType = element.attr('data-type') || ($.ajaxSettings && $.ajaxSettings.dataType); - - if (element.is('form')) { - method = element.attr('method'); - url = element.attr('action'); - data = element.serializeArray(); - // memoized value from clicked submit button - var button = element.data('ujs:submit-button'); - if (button) { - data.push(button); - element.data('ujs:submit-button', null); - } - } else { - method = element.attr('data-method'); - url = element.attr('href'); - - csrf_token = $('meta[name=csrf-token]').attr('content'); - csrf_param = $('meta[name=csrf-param]').attr('content'); - - data = {}; - data[csrf_param] = csrf_token; - } - - $.ajax({ - url: url, type: method || 'GET', data: data, dataType: dataType, - // stopping the "ajax:beforeSend" event will cancel the ajax request - beforeSend: function(xhr, settings) { - if (settings.dataType === undefined) { - xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); - } - return fire(element, 'ajax:beforeSend', [xhr, settings]); - }, - success: function(data, status, xhr) { - element.trigger('ajax:success', [data, status, xhr]); - }, - complete: function(xhr, status) { - element.trigger('ajax:complete', [xhr, status]); - }, - error: function(xhr, status, error) { - element.trigger('ajax:error', [xhr, status, error]); - } - }); - } - - // Handles "data-method" on links such as: - // Delete - function handleMethod(link) { - var href = link.attr('href'), - method = link.attr('data-method'), - csrf_token = $('meta[name=csrf-token]').attr('content'), - csrf_param = $('meta[name=csrf-param]').attr('content'), - form = $('
'), - metadata_input = ''; - - if (csrf_param !== undefined && csrf_token !== undefined) { - metadata_input += ''; - } - - form.hide().append(metadata_input).appendTo('body'); - form.submit(); - } - - function disableFormElements(form) { - form.find('input[data-disable-with]').each(function() { - var input = $(this); - input.data('ujs:enable-with', input.val()) - .val(input.attr('data-disable-with')) - .attr('disabled', 'disabled'); - }); - } - - function enableFormElements(form) { - form.find('input[data-disable-with]').each(function() { - var input = $(this); - input.val(input.data('ujs:enable-with')).removeAttr('disabled'); - }); - } - - function allowAction(element) { - var message = element.attr('data-confirm'); - return !message || (fire(element, 'confirm') && confirm(message)); - } - - function requiredValuesMissing(form) { - var missing = false; - form.find('input[name][required]').each(function() { - if (!$(this).val()) missing = true; - }); - return missing; - } - - $('a[data-confirm], a[data-method], a[data-remote]').live('click.rails', function(e) { - var link = $(this); - if (!allowAction(link)) return false; - - if (link.attr('data-remote') != undefined) { - handleRemote(link); - return false; - } else if (link.attr('data-method')) { - handleMethod(link); - return false; - } - }); - - $('form').live('submit.rails', function(e) { - var form = $(this), remote = form.attr('data-remote') != undefined; - if (!allowAction(form)) return false; - - // skip other logic when required values are missing - if (requiredValuesMissing(form)) return !remote; - - if (remote) { - handleRemote(form); - return false; - } else { - disableFormElements(form); - } - }); - - $('form input[type=submit], form button[type=submit], form button:not([type])').live('click.rails', function() { - var button = $(this); - if (!allowAction(button)) return false; - // register the pressed submit button - var name = button.attr('name'), data = name ? {name:name, value:button.val()} : null; - button.closest('form').data('ujs:submit-button', data); - }); - - $('form').live('ajax:beforeSend.rails', function(event) { - if (this == event.target) disableFormElements($(this)); - }); - - $('form').live('ajax:complete.rails', function(event) { - if (this == event.target) enableFormElements($(this)); - }); + // Shorthand to make it a little easier to call public rails functions from within rails.js + var rails; + + $.rails = rails = { + // Link elements bound by jquery-ujs + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]', + + // Form elements bound by jquery-ujs + formSubmitSelector: 'form', + + // Form input elements bound by jquery-ujs + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', + + // Form input elements disabled during form submission + disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', + + // Form input elements re-enabled after form submission + enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', + + // Form required input elements + requiredInputSelector: 'input[name][required],textarea[name][required]', + + // Form file input elements + fileInputSelector: 'input:file', + + // Make sure that every Ajax request sends the CSRF token + CSRFProtection: function(xhr) { + var token = $('meta[name="csrf-token"]').attr('content'); + if (token) xhr.setRequestHeader('X-CSRF-Token', token); + }, + + // Triggers an event on an element and returns false if the event result is false + fire: function(obj, name, data) { + var event = $.Event(name); + obj.trigger(event, data); + return event.result !== false; + }, + + // Submits "remote" forms and links with ajax + handleRemote: function(element) { + var method, url, data, + dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); + + if (rails.fire(element, 'ajax:before')) { + + if (element.is('form')) { + method = element.attr('method'); + url = element.attr('action'); + data = element.serializeArray(); + // memoized value from clicked submit button + var button = element.data('ujs:submit-button'); + if (button) { + data.push(button); + element.data('ujs:submit-button', null); + } + } else { + method = element.data('method'); + url = element.attr('href'); + data = null; + } + + $.ajax({ + url: url, type: method || 'GET', data: data, dataType: dataType, + // stopping the "ajax:beforeSend" event will cancel the ajax request + beforeSend: function(xhr, settings) { + if (settings.dataType === undefined) { + xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); + } + return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); + }, + success: function(data, status, xhr) { + element.trigger('ajax:success', [data, status, xhr]); + }, + complete: function(xhr, status) { + element.trigger('ajax:complete', [xhr, status]); + }, + error: function(xhr, status, error) { + element.trigger('ajax:error', [xhr, status, error]); + } + }); + } + }, + + // Handles "data-method" on links such as: + // Delete + handleMethod: function(link) { + var href = link.attr('href'), + method = link.data('method'), + csrf_token = $('meta[name=csrf-token]').attr('content'), + csrf_param = $('meta[name=csrf-param]').attr('content'), + form = $('
'), + metadata_input = ''; + + if (csrf_param !== undefined && csrf_token !== undefined) { + metadata_input += ''; + } + + form.hide().append(metadata_input).appendTo('body'); + form.submit(); + }, + + /* Disables form elements: + - Caches element value in 'ujs:enable-with' data store + - Replaces element text with value of 'data-disable-with' attribute + - Adds disabled=disabled attribute + */ + disableFormElements: function(form) { + form.find(rails.disableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + element.data('ujs:enable-with', element[method]()); + element[method](element.data('disable-with')); + element.attr('disabled', 'disabled'); + }); + }, + + /* Re-enables disabled form elements: + - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) + - Removes disabled attribute + */ + enableFormElements: function(form) { + form.find(rails.enableSelector).each(function() { + var element = $(this), method = element.is('button') ? 'html' : 'val'; + if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); + element.removeAttr('disabled'); + }); + }, + + // If message provided in 'data-confirm' attribute, fires `confirm` event and returns result of confirm dialog. + // Attaching a handler to the element's `confirm` event that returns false cancels the confirm dialog. + allowAction: function(element) { + var message = element.data('confirm'); + return !message || (rails.fire(element, 'confirm') && confirm(message)); + }, + + // Helper function which checks for blank inputs in a form that match the specified CSS selector + blankInputs: function(form, specifiedSelector, nonBlank) { + var inputs = $(), input, + selector = specifiedSelector || 'input,textarea'; + form.find(selector).each(function() { + input = $(this); + // Collect non-blank inputs if nonBlank option is true, otherwise, collect blank inputs + if (nonBlank ? input.val() : !input.val()) { + inputs = inputs.add(input); + } + }); + return inputs.length ? inputs : false; + }, + + // Helper function which checks for non-blank inputs in a form that match the specified CSS selector + nonBlankInputs: function(form, specifiedSelector) { + return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank + }, + + // Helper function, needed to provide consistent behavior in IE + stopEverything: function(e) { + e.stopImmediatePropagation(); + return false; + }, + + // find all the submit events directly bound to the form and + // manually invoke them. If anyone returns false then stop the loop + callFormSubmitBindings: function(form) { + var events = form.data('events'), continuePropagation = true; + if (events !== undefined && events['submit'] !== undefined) { + $.each(events['submit'], function(i, obj){ + if (typeof obj.handler === 'function') return continuePropagation = obj.handler(obj.data); + }); + } + return continuePropagation; + } + }; + + // ajaxPrefilter is a jQuery 1.5 feature + if ('ajaxPrefilter' in $) { + $.ajaxPrefilter(function(options, originalOptions, xhr){ rails.CSRFProtection(xhr); }); + } else { + $(document).ajaxSend(function(e, xhr){ rails.CSRFProtection(xhr); }); + } + + $(rails.linkClickSelector).live('click.rails', function(e) { + var link = $(this); + if (!rails.allowAction(link)) return rails.stopEverything(e); + + if (link.data('remote') !== undefined) { + rails.handleRemote(link); + return false; + } else if (link.data('method')) { + rails.handleMethod(link); + return false; + } + }); + + $(rails.formSubmitSelector).live('submit.rails', function(e) { + var form = $(this), + remote = form.data('remote') !== undefined, + blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), + nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); + + if (!rails.allowAction(form)) return rails.stopEverything(e); + + // skip other logic when required values are missing or file upload is present + if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { + return !remote; + } + + if (remote) { + if (nonBlankFileInputs) { + return rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); + } + + // If browser does not support submit bubbling, then this live-binding will be called before direct + // bindings. Therefore, we should directly call any direct bindings before remotely submitting form. + if (!$.support.submitBubbles && rails.callFormSubmitBindings(form) === false) return rails.stopEverything(e); + + rails.handleRemote(form); + return false; + } else { + // slight timeout so that the submit button gets properly serialized + setTimeout(function(){ rails.disableFormElements(form); }, 13); + } + }); + + $(rails.formInputClickSelector).live('click.rails', function(event) { + var button = $(this); + + if (!rails.allowAction(button)) return rails.stopEverything(event); + + // register the pressed submit button + var name = button.attr('name'), + data = name ? {name:name, value:button.val()} : null; + + button.closest('form').data('ujs:submit-button', data); + }); + + $(rails.formSubmitSelector).live('ajax:beforeSend.rails', function(event) { + if (this == event.target) rails.disableFormElements($(this)); + }); + + $(rails.formSubmitSelector).live('ajax:complete.rails', function(event) { + if (this == event.target) rails.enableFormElements($(this)); + }); + })( jQuery ); diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js index 2cd1220786..88eac6e458 100644 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js +++ b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js @@ -1,4 +1,14 @@ (function() { + Ajax.Responders.register({ + onCreate: function(request) { + var token = $$('meta[name=csrf-token]')[0]; + if (token) { + if (!request.options.requestHeaders) request.options.requestHeaders = {}; + request.options.requestHeaders['X-CSRF-Token'] = token.readAttribute('content'); + } + } + }); + // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ function isEventSupported(eventName) { @@ -189,20 +199,4 @@ document.on('ajax:complete', 'form', function(event, form) { if (form == event.findElement()) enableFormElements(form); }); - - Ajax.Responders.register({ - onCreate: function(request) { - var csrf_meta_tag = $$('meta[name=csrf-token]')[0]; - - if (csrf_meta_tag) { - var header = 'X-CSRF-Token', - token = csrf_meta_tag.readAttribute('content'); - - if (!request.options.requestHeaders) { - request.options.requestHeaders = {}; - } - request.options.requestHeaders[header] = token; - } - } - }); })(); -- cgit v1.2.3 From bf3a3c22c06b7f2f38d0d53af7508d1b3a0aa341 Mon Sep 17 00:00:00 2001 From: Mike Gehard Date: Fri, 29 Apr 2011 19:19:25 -0600 Subject: Removed so info about assigning to specific devs and adding tags. I don't believe that you can do that in GitHub issues. Remove state:committed notes as they also don't exist in GitHub issues. --- railties/guides/source/contributing_to_ruby_on_rails.textile | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 6bf078cfde..d7090ef675 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -24,10 +24,6 @@ If you've found a problem in Ruby on Rails which is not a security risk do a sea At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need to at least post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. -You shouldn't assign the bug to a particular core developer unless you know for sure which developer will be handling that issue. The core team periodically reviews issues and assigns developers and milestones to them. - -You should set tags for your issue. Use the "bug" tag for a bug report, and add the "patch" tag if you are attaching a patch. Try to find some relevant tags from the existing tag list (which will appear as soon as you start typing in the "Choose some tags" textbox), rather than creating new tags. - Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating a issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with a "I'm having this problem too" comment. h4. Special Treatment for Security Issues @@ -320,11 +316,9 @@ h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: -$ git commit -a -m "Here is a commit message [#ticket_number state:committed]" +$ git commit -a -m "Here is a commit message on what I changed in this commit" -NOTE: By adding '[#ticket_number state:committed]' at the end of your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository. - h4. Update master It’s pretty likely that other changes to master have happened while you were working. Go get them: @@ -384,7 +378,7 @@ All contributions, either via master or docrails, get credit in "Rails Contribut h3. Changelog * April 29, 2011: Reflect GitHub Issues and Pull Request workflow by "Dan Pickett":http://www.enlightsolutions.com -* April 14, 2001: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com +* April 14, 2011: Modified Contributing to the Rails Code section to add '[#ticket_number state:commited]' on patches commit messages by "Sebastian Martinez":http://wyeworks.com * December 28, 2010: Complete revision by "Xavier Noria":credits.html#fxn * April 6, 2010: Fixed document to validate XHTML 1.0 Strict. "Jaime Iniesta":http://jaimeiniesta.com * August 1, 2009: Updates/amplifications by "Mike Gunderloy":credits.html#mgunderloy -- cgit v1.2.3 From 1f5249bf937ec07ca644e500630e8c196394f252 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 17:28:34 -0700 Subject: shorten up our fixture path resolving code --- activerecord/lib/active_record/railties/databases.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 7d76d7a19f..fae88943ab 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -301,8 +301,8 @@ db_namespace = namespace :db do require 'active_record/fixtures' ActiveRecord::Base.establish_connection(Rails.env) - base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures') - fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir + base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}] + fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| Fixtures.create_fixtures(fixtures_dir, fixture_file) -- cgit v1.2.3 From 2987115656dd3590e7034271aade26dc006cd158 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 17:30:01 -0700 Subject: make sure we flatten the fixture path list --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index fae88943ab..182b5c9210 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -301,7 +301,7 @@ db_namespace = namespace :db do require 'active_record/fixtures' ActiveRecord::Base.establish_connection(Rails.env) - base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}] + base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| -- cgit v1.2.3 From 7ee5f2eee514f5e9fb53753b830e0caf3dafc2aa Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Apr 2011 18:21:07 -0700 Subject: making sure the `create_fixtures` method works to prevent regressions --- activerecord/test/cases/fixtures_test.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index fa40fad56d..3e20155210 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -45,6 +45,11 @@ class FixturesTest < ActiveRecord::TestCase end end + def test_create_fixtures + Fixtures.create_fixtures(FIXTURES_ROOT, "parrots") + assert Parrot.find_by_name('Curious George'), 'George is in the database' + end + def test_multiple_clean_fixtures fixtures_array = nil assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) } -- cgit v1.2.3 From b2aacc346c8e975cd9ab8c1b0a6b90adbb98debc Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 30 Apr 2011 03:52:06 +0200 Subject: Prototype and Scriptaculous are no longer vendored, but provided by prototype-rails from now on, also the -j option of the application generator is removed --- .../asset_tag_helpers/javascript_tag_helpers.rb | 7 +- railties/CHANGELOG | 5 + railties/Rakefile | 1 - .../guides/source/action_view_overview.textile | 179 +- .../guides/source/layouts_and_rendering.textile | 6 +- railties/lib/rails/generators/app_base.rb | 3 - .../rails/generators/rails/app/app_generator.rb | 14 +- .../rails/generators/rails/app/templates/Gemfile | 3 + .../app/assets/javascripts/application.js.tt | 9 +- .../rails/app/templates/config/application.rb | 4 +- .../vendor/assets/javascripts/controls.js | 965 ---- .../vendor/assets/javascripts/dragdrop.js | 974 ---- .../templates/vendor/assets/javascripts/effects.js | 1123 ---- .../vendor/assets/javascripts/prototype.js | 6082 -------------------- .../vendor/assets/javascripts/prototype_ujs.js | 202 - .../rails/plugin_new/plugin_new_generator.rb | 17 +- railties/test/generators/app_generator_test.rb | 21 +- .../test/generators/plugin_new_generator_test.rb | 10 - 18 files changed, 27 insertions(+), 9598 deletions(-) delete mode 100644 railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js delete mode 100644 railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js delete mode 100644 railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js delete mode 100644 railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js delete mode 100644 railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index 3d815b5e1f..e1ee0d0e1a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -99,10 +99,9 @@ module ActionView # # When passing paths, the ".js" extension is optional. # - # To include the default JavaScript expansion pass :defaults as source. - # By default, :defaults loads jQuery. If the application was generated - # with "-j prototype" the libraries Prototype and Scriptaculous are loaded instead. - # In any case, the defaults can be overridden in config/application.rb: + # If the application is not using the asset pipeline, to include the default JavaScript + # expansion pass :defaults as source. By default, :defaults loads jQuery, + # and that can be overridden in config/application.rb: # # config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) # diff --git a/railties/CHANGELOG b/railties/CHANGELOG index a4f0d31971..83c501c656 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,5 +1,10 @@ *Rails 3.1.0 (unreleased)* +* Application generation no longer supports the -j option. [fxn] + +* Prototype and Scriptaculous are no longer vendored, they are provided from now on +by the prototype-rails gem. [fxn] + * The scaffold controller will now produce SCSS file if Sass is available [Prem Sichanugrist] * The controller and resource generators will now automatically produce asset stubs (this can be turned off with --skip-assets). These stubs will use Coffee and Sass, if those libraries are available. [DHH] diff --git a/railties/Rakefile b/railties/Rakefile index 8ef4f4b53d..1affa6dd39 100755 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -46,7 +46,6 @@ task :generate_guides do end task :update_ujs do - system "curl https://github.com/rails/prototype-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js" system "curl https://github.com/rails/jquery-ujs/raw/master/src/rails.js > lib/rails/generators/rails/app/templates/vendor/assets/javascripts/jquery_ujs.js" end diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index 172932fdab..9e59383315 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -526,7 +526,7 @@ javascript_include_tag "common" # => -To include the Prototype and Scriptaculous JavaScript libraries in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well. +If the application does not use the asset pipeline, to include the jQuery JavaScript library in your application, pass +:defaults+ as the source. When using +:defaults+, if an +application.js+ file exists in your +public/javascripts+ directory, it will be included as well. javascript_include_tag :defaults @@ -1301,7 +1301,7 @@ h4. JavaScriptHelper Provides functionality for working with JavaScript in your views. -Rails includes the Prototype JavaScript framework and the Scriptaculous JavaScript controls and visual effects library. If you wish to use these libraries and their helpers, make sure +<%= javascript_include_tag :defaults, :cache => true %>+ is in the HEAD section of your page. This function will include the necessary JavaScript files Rails generated in the +public/javascripts+ directory. +Rails includes by default the jQuery JavaScript library. If you wish to use these libraries and they are in your asset pipeline, or otherwise make sure +<%= javascript_include_tag :defaults, :cache => true %>+ is in the HEAD section of your page. This function will include the necessary JavaScript files Rails generated in the +public/javascripts+ directory. h5. button_to_function @@ -1402,16 +1402,6 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 -h4. PrototypeHelper - -Prototype is a JavaScript library that provides DOM manipulation, Ajax 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. - -To be able to use these helpers, you must first include the Prototype JavaScript framework in the HEAD of the pages with Prototype functions. - - -javascript_include_tag 'prototype' - - h5. evaluate_remote_response Returns +eval(request.responseText)+ which is the JavaScript function that form_remote_tag can call in +:complete+ to evaluate a multiple update return document using +update_element_function+ calls. @@ -1508,171 +1498,6 @@ would generate: return false;" type="button" value="Create" /> -h5. update_page - -Yields a JavaScriptGenerator and returns the generated JavaScript code. Use this to update multiple elements on a page in an Ajax response. - - -update_page do |page| - page.hide 'spinner' -end - - -h5. update_page_tag - -Works like update_page but wraps the generated JavaScript in a +script+ tag. Use this to include generated JavaScript in an ERB template. - -h4. PrototypeHelper::JavaScriptGenerator::GeneratorMethods - -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". - -h5(#push). << - -Writes raw JavaScript to the page. - - -page << "alert('JavaScript with Prototype.');" - - -h5(#at). [] - -Returns a element reference by finding it through it's id in the DOM. - - -page['blank_slate'].show # => $('blank_slate').show(); - - -h5. alert - -Displays an alert dialog with the given message. - - -page.alert('This message is from Rails!') - - -h5. assign - -Assigns the JavaScript variable the given value. - - -page.assign 'tabulated_total', @total_from_cart - - -h5. call - -Calls the JavaScript function, optionally with the given arguments. - - -page.call 'Element.replace', 'my_element', "My content to replace with." - - -h5. delay - -Executes the content of the block after a delay of the number of seconds provided. - - -page.delay(20) do - page.visual_effect :fade, 'notice' -end - - -h5. draggable - -Creates a script.aculo.us draggable element. See ActionView::Helpers::ScriptaculousHelper for more information. - -h5. drop_receiving - -Creates a script.aculo.us drop receiving element. See ActionView::Helpers::ScriptaculousHelper for more information. - -h5. hide - -Hides the visible DOM elements with the given ids. - - -page.hide 'person_29', 'person_9', 'person_0' - - -h5. insert_html - -Inserts HTML at the specified position relative to the DOM element identified by the given id. - - -page.insert_html :bottom, 'my_list', '
  • Last item
  • ' -
    - -h5. literal - -Returns an object whose to_json evaluates to the code provided. Use this to pass a literal JavaScript expression as an argument to another JavaScriptGenerator method. - -h5. redirect_to - -Redirects the browser to the given location using JavaScript, in the same form as +url_for+. - - -page.redirect_to(:controller => 'accounts', :action => 'new') - - -h5. remove - -Removes the DOM elements with the given ids from the page. - - -page.remove 'person_23', 'person_9', 'person_2' - - -h5. replace - -Replaces the "outer HTML" (i.e., the entire element, not just its contents) of the DOM element with the given id. - - -page.replace 'person-45', :partial => 'person', :object => @person - - -h5. replace_html - -Replaces the inner HTML of the DOM element with the given id. - - -page.replace_html 'person-45', :partial => 'person', :object => @person - - -h5(#prototype-select). select - -Returns a collection reference by finding it through a CSS pattern in the DOM. - - -page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); - - -h5. show - -Shows hidden DOM elements with the given ids. - - -page.show 'person_6', 'person_13', 'person_223' - - -h5. sortable - -Creates a script.aculo.us sortable element. Useful to recreate sortable elements after items get added or deleted. See ActionView::Helpers::ScriptaculousHelper for more information. - -h5. toggle - -Toggles the visibility of the DOM elements with the given ids. Example: - - -page.toggle 'person_14', 'person_12', 'person_23' # Hides the elements -page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements - - -h5. visual_effect - -Starts a script.aculo.us visual effect. See ActionView::Helpers::ScriptaculousHelper for more information. - - -TODO start from RecordIdentificationHelper - - h3. Localized Views Action View has the ability render different templates depending on the current locale. diff --git a/railties/guides/source/layouts_and_rendering.textile b/railties/guides/source/layouts_and_rendering.textile index 620df970dc..d67668df91 100644 --- a/railties/guides/source/layouts_and_rendering.textile +++ b/railties/guides/source/layouts_and_rendering.textile @@ -695,13 +695,13 @@ To include +http://example.com/main.js+: <%= javascript_include_tag "http://example.com/main.js" %> -The +:defaults+ option loads jQuery by default: +If the application does not use the asset pipeline, the +:defaults+ option loads jQuery by default: <%= javascript_include_tag :defaults %> -If the application was generated with "-j prototype" :defaults loads Prototype and Scriptaculous. And you can in any case override the expansion in config/application.rb: +And you can in any case override the expansion in config/application.rb: config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) @@ -709,7 +709,7 @@ config.action_view.javascript_expansions[:defaults] = %w(foo.js bar.js) When using :defaults, if an application.js file exists in public/javascripts it will be included as well at then end. -The +:all+ option loads every JavaScript file in +public/javascripts+: +Also, the +:all+ option loads every JavaScript file in +public/javascripts+: <%= javascript_include_tag :all %> diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 520d2c6a3a..324199e71c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -38,9 +38,6 @@ module Rails class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :javascript, :type => :string, :aliases => "-j", :default => "jquery", - :desc => "Preconfigure for selected JavaScript library (options: #{JAVASCRIPTS.join('/')})" - class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, :desc => "Skip JavaScript files" diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index bc55efa261..559f61e265 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -120,14 +120,8 @@ module Rails if options[:skip_javascript] empty_directory_with_gitkeep "vendor/assets/javascripts" else - copy_file "vendor/assets/javascripts/#{options[:javascript]}.js" - copy_file "vendor/assets/javascripts/#{options[:javascript]}_ujs.js" - - if options[:javascript] == "prototype" - copy_file "vendor/assets/javascripts/controls.js" - copy_file "vendor/assets/javascripts/dragdrop.js" - copy_file "vendor/assets/javascripts/effects.js" - end + copy_file "vendor/assets/javascripts/jquery.js" + copy_file "vendor/assets/javascripts/jquery_ujs.js" end end @@ -162,10 +156,6 @@ module Rails if !options[:skip_active_record] && !DATABASES.include?(options[:database]) raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end - - if !options[:skip_javascript] && !JAVASCRIPTS.include?(options[:javascript]) - raise Error, "Invalid value for --javascript option. Supported for preconfiguration are: #{JAVASCRIPTS.join(", ")}." - end end public_task :create_root diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 8ad64e38ed..de7d51d030 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -10,6 +10,9 @@ gem 'sass' gem 'coffee-script' gem 'uglifier' +# Prototype, Scriptaculous, and RJS. +# gem 'prototype-rails' + # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index fb5e91caf4..c04c33e321 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -1,11 +1,8 @@ // FIXME: Tell people that this is a manifest file, real code should go into discrete files // FIXME: Tell people how Sprockets and CoffeeScript works // -//= require <%= options[:javascript] %> -//= require <%= options[:javascript] %>_ujs -<% if options[:javascript] == "prototype" %> -//= require controls -//= require dragdrop -//= require effects +<% unless options[:skip_javascript] -%> +//= require jquery +//= require jquery_ujs <% end -%> //= require_tree . diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 46ccb7e078..7bf4c779a0 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -45,9 +45,7 @@ module <%= app_const_base %> # # JavaScript files you want as :defaults (application.js is always included). <% if options[:skip_javascript] -%> - config.action_view.javascript_expansions[:defaults] = %w() -<% elsif options[:javascript] == 'prototype' -%> - config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails) + # config.action_view.javascript_expansions[:defaults] = %w() <% else -%> # config.action_view.javascript_expansions[:defaults] = %w(prototype effects dragdrop controls rails) <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js deleted file mode 100644 index 7392fb664c..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/controls.js +++ /dev/null @@ -1,965 +0,0 @@ -// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 - -// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) -// Contributors: -// Richard Livsey -// Rahul Bhargava -// Rob Wills -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -// Autocompleter.Base handles all the autocompletion functionality -// that's independent of the data source for autocompletion. This -// includes drawing the autocompletion menu, observing keyboard -// and mouse events, and similar. -// -// Specific autocompleters need to provide, at the very least, -// a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method -// should get the text for which to provide autocompletion by -// invoking this.getToken(), NOT by directly accessing -// this.element.value. This is to allow incremental tokenized -// autocompletion. Specific auto-completion logic (AJAX, etc) -// belongs in getUpdatedChoices. -// -// Tokenized incremental autocompletion is enabled automatically -// when an autocompleter is instantiated with the 'tokens' option -// in the options parameter, e.g.: -// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); -// will incrementally autocomplete with a comma as the token. -// Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it -// allows smart autocompletion after linebreaks. - -if(typeof Effect == 'undefined') - throw("controls.js requires including script.aculo.us' effects.js library"); - -var Autocompleter = { }; -Autocompleter.Base = Class.create({ - baseInitialize: function(element, update, options) { - element = $(element); - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; - this.entryCount = 0; - this.oldElementValue = this.element.value; - - if(this.setOptions) - this.setOptions(options); - else - this.options = options || { }; - - this.options.paramName = this.options.paramName || this.element.name; - this.options.tokens = this.options.tokens || []; - this.options.frequency = this.options.frequency || 0.4; - this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; - Position.clone(element, update, { - setHeight: false, - offsetTop: element.offsetHeight - }); - } - Effect.Appear(update,{duration:0.15}); - }; - this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(typeof(this.options.tokens) == 'string') - this.options.tokens = new Array(this.options.tokens); - // Force carriage returns as token delimiters anyway - if (!this.options.tokens.include('\n')) - this.options.tokens.push('\n'); - - this.observer = null; - - this.element.setAttribute('autocomplete','off'); - - Element.hide(this.update); - - Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); - Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); - }, - - show: function() { - if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && - (Prototype.Browser.IE) && - (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, - ''); - this.iefix = $(this.update.id+'_iefix'); - } - if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); - }, - - fixIEOverlapping: function() { - Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); - this.iefix.style.zIndex = 1; - this.update.style.zIndex = 2; - Element.show(this.iefix); - }, - - hide: function() { - this.stopIndicator(); - if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); - if(this.iefix) Element.hide(this.iefix); - }, - - startIndicator: function() { - if(this.options.indicator) Element.show(this.options.indicator); - }, - - stopIndicator: function() { - if(this.options.indicator) Element.hide(this.options.indicator); - }, - - onKeyPress: function(event) { - if(this.active) - switch(event.keyCode) { - case Event.KEY_TAB: - case Event.KEY_RETURN: - this.selectEntry(); - Event.stop(event); - case Event.KEY_ESC: - this.hide(); - this.active = false; - Event.stop(event); - return; - case Event.KEY_LEFT: - case Event.KEY_RIGHT: - return; - case Event.KEY_UP: - this.markPrevious(); - this.render(); - Event.stop(event); - return; - case Event.KEY_DOWN: - this.markNext(); - this.render(); - Event.stop(event); - return; - } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || - (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; - - this.changed = true; - this.hasFocus = true; - - if(this.observer) clearTimeout(this.observer); - this.observer = - setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); - }, - - activate: function() { - this.changed = false; - this.hasFocus = true; - this.getUpdatedChoices(); - }, - - onHover: function(event) { - var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) - { - this.index = element.autocompleteIndex; - this.render(); - } - Event.stop(event); - }, - - onClick: function(event) { - var element = Event.findElement(event, 'LI'); - this.index = element.autocompleteIndex; - this.selectEntry(); - this.hide(); - }, - - onBlur: function(event) { - // needed to make click events working - setTimeout(this.hide.bind(this), 250); - this.hasFocus = false; - this.active = false; - }, - - render: function() { - if(this.entryCount > 0) { - for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : - Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { - this.show(); - this.active = true; - } - } else { - this.active = false; - this.hide(); - } - }, - - markPrevious: function() { - if(this.index > 0) this.index--; - else this.index = this.entryCount-1; - this.getEntry(this.index).scrollIntoView(true); - }, - - markNext: function() { - if(this.index < this.entryCount-1) this.index++; - else this.index = 0; - this.getEntry(this.index).scrollIntoView(false); - }, - - getEntry: function(index) { - return this.update.firstChild.childNodes[index]; - }, - - getCurrentEntry: function() { - return this.getEntry(this.index); - }, - - selectEntry: function() { - this.active = false; - this.updateElement(this.getCurrentEntry()); - }, - - updateElement: function(selectedElement) { - if (this.options.updateElement) { - this.options.updateElement(selectedElement); - return; - } - var value = ''; - if (this.options.select) { - var nodes = $(selectedElement).select('.' + this.options.select) || []; - if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); - } else - value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - - var bounds = this.getTokenBounds(); - if (bounds[0] != -1) { - var newValue = this.element.value.substr(0, bounds[0]); - var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); - if (whitespace) - newValue += whitespace[0]; - this.element.value = newValue + value + this.element.value.substr(bounds[1]); - } else { - this.element.value = value; - } - this.oldElementValue = this.element.value; - this.element.focus(); - - if (this.options.afterUpdateElement) - this.options.afterUpdateElement(this.element, selectedElement); - }, - - updateChoices: function(choices) { - if(!this.changed && this.hasFocus) { - this.update.innerHTML = choices; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.down()); - - if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = - this.update.down().childNodes.length; - for (var i = 0; i < this.entryCount; i++) { - var entry = this.getEntry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entryCount = 0; - } - - this.stopIndicator(); - this.index = 0; - - if(this.entryCount==1 && this.options.autoSelect) { - this.selectEntry(); - this.hide(); - } else { - this.render(); - } - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onObserverEvent: function() { - this.changed = false; - this.tokenBounds = null; - if(this.getToken().length>=this.options.minChars) { - this.getUpdatedChoices(); - } else { - this.active = false; - this.hide(); - } - this.oldElementValue = this.element.value; - }, - - getToken: function() { - var bounds = this.getTokenBounds(); - return this.element.value.substring(bounds[0], bounds[1]).strip(); - }, - - getTokenBounds: function() { - if (null != this.tokenBounds) return this.tokenBounds; - var value = this.element.value; - if (value.strip().empty()) return [-1, 0]; - var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); - var offset = (diff == this.oldElementValue.length ? 1 : 0); - var prevTokenPos = -1, nextTokenPos = value.length; - var tp; - for (var index = 0, l = this.options.tokens.length; index < l; ++index) { - tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); - if (tp > prevTokenPos) prevTokenPos = tp; - tp = value.indexOf(this.options.tokens[index], diff + offset); - if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; - } - return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); - } -}); - -Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { - var boundary = Math.min(newS.length, oldS.length); - for (var index = 0; index < boundary; ++index) - if (newS[index] != oldS[index]) - return index; - return boundary; -}; - -Ajax.Autocompleter = Class.create(Autocompleter.Base, { - initialize: function(element, update, url, options) { - this.baseInitialize(element, update, options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this); - this.options.defaultParams = this.options.parameters || null; - this.url = url; - }, - - getUpdatedChoices: function() { - this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + - encodeURIComponent(this.getToken()); - - this.options.parameters = this.options.callback ? - this.options.callback(this.element, entry) : entry; - - if(this.options.defaultParams) - this.options.parameters += '&' + this.options.defaultParams; - - new Ajax.Request(this.url, this.options); - }, - - onComplete: function(request) { - this.updateChoices(request.responseText); - } -}); - -// The local array autocompleter. Used when you'd prefer to -// inject an array of autocompletion options into the page, rather -// than sending out Ajax queries, which can be quite slow sometimes. -// -// The constructor takes four parameters. The first two are, as usual, -// the id of the monitored textbox, and id of the autocompletion menu. -// The third is the array you want to autocomplete from, and the fourth -// is the options block. -// -// Extra local autocompletion options: -// - choices - How many autocompletion choices to offer -// -// - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the -// autocomplete array. Defaults to true, which will -// match text at the beginning of any *word* in the -// strings in the autocomplete array. If you want to -// search anywhere in the string, additionally set -// the option fullSearch to true (default: off). -// -// - fullSsearch - Search anywhere in autocomplete array strings. -// -// - partialChars - How many characters to enter before triggering -// a partial match (unlike minChars, which defines -// how many characters are required to do any match -// at all). Defaults to 2. -// -// - ignoreCase - Whether to ignore case when autocompleting. -// Defaults to true. -// -// It's possible to pass in a custom function as the 'selector' -// option, if you prefer to write your own autocompletion logic. -// In that case, the other options above will not apply unless -// you support them. - -Autocompleter.Local = Class.create(Autocompleter.Base, { - initialize: function(element, update, array, options) { - this.baseInitialize(element, update, options); - this.options.array = array; - }, - - getUpdatedChoices: function() { - this.updateChoices(this.options.selector(this)); - }, - - setOptions: function(options) { - this.options = Object.extend({ - choices: 10, - partialSearch: true, - partialChars: 2, - ignoreCase: true, - fullSearch: false, - selector: function(instance) { - var ret = []; // Beginning matches - var partial = []; // Inside matches - var entry = instance.getToken(); - var count = 0; - - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { - - var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : - elem.indexOf(entry); - - while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + - elem.substr(entry.length) + "
  • "); - break; - } else if (entry.length >= instance.options.partialChars && - instance.options.partialSearch && foundPos != -1) { - if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { - partial.push("
  • " + elem.substr(0, foundPos) + "" + - elem.substr(foundPos, entry.length) + "" + elem.substr( - foundPos + entry.length) + "
  • "); - break; - } - } - - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : - elem.indexOf(entry, foundPos + 1); - - } - } - if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); - return "
      " + ret.join('') + "
    "; - } - }, options || { }); - } -}); - -// AJAX in-place editor and collection editor -// Full rewrite by Christophe Porteneuve (April 2007). - -// Use this if you notice weird scrolling problems on some browsers, -// the DOM might be a bit confused when this gets called so do this -// waits 1 ms (with setTimeout) until it does the activation -Field.scrollFreeActivate = function(field) { - setTimeout(function() { - Field.activate(field); - }, 1); -}; - -Ajax.InPlaceEditor = Class.create({ - initialize: function(element, url, options) { - this.url = url; - this.element = element = $(element); - this.prepareOptions(); - this._controls = { }; - arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! - Object.extend(this.options, options || { }); - if (!this.options.formId && this.element.id) { - this.options.formId = this.element.id + '-inplaceeditor'; - if ($(this.options.formId)) - this.options.formId = ''; - } - if (this.options.externalControl) - this.options.externalControl = $(this.options.externalControl); - if (!this.options.externalControl) - this.options.externalControlOnly = false; - this._originalBackground = this.element.getStyle('background-color') || 'transparent'; - this.element.title = this.options.clickToEditText; - this._boundCancelHandler = this.handleFormCancellation.bind(this); - this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); - this._boundFailureHandler = this.handleAJAXFailure.bind(this); - this._boundSubmitHandler = this.handleFormSubmission.bind(this); - this._boundWrapperHandler = this.wrapUp.bind(this); - this.registerListeners(); - }, - checkForEscapeOrReturn: function(e) { - if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; - if (Event.KEY_ESC == e.keyCode) - this.handleFormCancellation(e); - else if (Event.KEY_RETURN == e.keyCode) - this.handleFormSubmission(e); - }, - createControl: function(mode, handler, extraClasses) { - var control = this.options[mode + 'Control']; - var text = this.options[mode + 'Text']; - if ('button' == control) { - var btn = document.createElement('input'); - btn.type = 'submit'; - btn.value = text; - btn.className = 'editor_' + mode + '_button'; - if ('cancel' == mode) - btn.onclick = this._boundCancelHandler; - this._form.appendChild(btn); - this._controls[mode] = btn; - } else if ('link' == control) { - var link = document.createElement('a'); - link.href = '#'; - link.appendChild(document.createTextNode(text)); - link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; - link.className = 'editor_' + mode + '_link'; - if (extraClasses) - link.className += ' ' + extraClasses; - this._form.appendChild(link); - this._controls[mode] = link; - } - }, - createEditField: function() { - var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); - var fld; - if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { - fld = document.createElement('input'); - fld.type = 'text'; - var size = this.options.size || this.options.cols || 0; - if (0 < size) fld.size = size; - } else { - fld = document.createElement('textarea'); - fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); - fld.cols = this.options.cols || 40; - } - fld.name = this.options.paramName; - fld.value = text; // No HTML breaks conversion anymore - fld.className = 'editor_field'; - if (this.options.submitOnBlur) - fld.onblur = this._boundSubmitHandler; - this._controls.editor = fld; - if (this.options.loadTextURL) - this.loadExternalText(); - this._form.appendChild(this._controls.editor); - }, - createForm: function() { - var ipe = this; - function addText(mode, condition) { - var text = ipe.options['text' + mode + 'Controls']; - if (!text || condition === false) return; - ipe._form.appendChild(document.createTextNode(text)); - }; - this._form = $(document.createElement('form')); - this._form.id = this.options.formId; - this._form.addClassName(this.options.formClassName); - this._form.onsubmit = this._boundSubmitHandler; - this.createEditField(); - if ('textarea' == this._controls.editor.tagName.toLowerCase()) - this._form.appendChild(document.createElement('br')); - if (this.options.onFormCustomization) - this.options.onFormCustomization(this, this._form); - addText('Before', this.options.okControl || this.options.cancelControl); - this.createControl('ok', this._boundSubmitHandler); - addText('Between', this.options.okControl && this.options.cancelControl); - this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); - addText('After', this.options.okControl || this.options.cancelControl); - }, - destroy: function() { - if (this._oldInnerHTML) - this.element.innerHTML = this._oldInnerHTML; - this.leaveEditMode(); - this.unregisterListeners(); - }, - enterEditMode: function(e) { - if (this._saving || this._editing) return; - this._editing = true; - this.triggerCallback('onEnterEditMode'); - if (this.options.externalControl) - this.options.externalControl.hide(); - this.element.hide(); - this.createForm(); - this.element.parentNode.insertBefore(this._form, this.element); - if (!this.options.loadTextURL) - this.postProcessEditField(); - if (e) Event.stop(e); - }, - enterHover: function(e) { - if (this.options.hoverClassName) - this.element.addClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onEnterHover'); - }, - getText: function() { - return this.element.innerHTML.unescapeHTML(); - }, - handleAJAXFailure: function(transport) { - this.triggerCallback('onFailure', transport); - if (this._oldInnerHTML) { - this.element.innerHTML = this._oldInnerHTML; - this._oldInnerHTML = null; - } - }, - handleFormCancellation: function(e) { - this.wrapUp(); - if (e) Event.stop(e); - }, - handleFormSubmission: function(e) { - var form = this._form; - var value = $F(this._controls.editor); - this.prepareSubmission(); - var params = this.options.callback(form, value) || ''; - if (Object.isString(params)) - params = params.toQueryParams(); - params.editorId = this.element.id; - if (this.options.htmlResponse) { - var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Updater({ success: this.element }, this.url, options); - } else { - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: params, - onComplete: this._boundWrapperHandler, - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.url, options); - } - if (e) Event.stop(e); - }, - leaveEditMode: function() { - this.element.removeClassName(this.options.savingClassName); - this.removeForm(); - this.leaveHover(); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - if (this.options.externalControl) - this.options.externalControl.show(); - this._saving = false; - this._editing = false; - this._oldInnerHTML = null; - this.triggerCallback('onLeaveEditMode'); - }, - leaveHover: function(e) { - if (this.options.hoverClassName) - this.element.removeClassName(this.options.hoverClassName); - if (this._saving) return; - this.triggerCallback('onLeaveHover'); - }, - loadExternalText: function() { - this._form.addClassName(this.options.loadingClassName); - this._controls.editor.disabled = true; - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._form.removeClassName(this.options.loadingClassName); - var text = transport.responseText; - if (this.options.stripLoadedTextTags) - text = text.stripTags(); - this._controls.editor.value = text; - this._controls.editor.disabled = false; - this.postProcessEditField(); - }.bind(this), - onFailure: this._boundFailureHandler - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - postProcessEditField: function() { - var fpc = this.options.fieldPostCreation; - if (fpc) - $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); - }, - prepareOptions: function() { - this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); - Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); - [this._extraDefaultOptions].flatten().compact().each(function(defs) { - Object.extend(this.options, defs); - }.bind(this)); - }, - prepareSubmission: function() { - this._saving = true; - this.removeForm(); - this.leaveHover(); - this.showSaving(); - }, - registerListeners: function() { - this._listeners = { }; - var listener; - $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { - listener = this[pair.value].bind(this); - this._listeners[pair.key] = listener; - if (!this.options.externalControlOnly) - this.element.observe(pair.key, listener); - if (this.options.externalControl) - this.options.externalControl.observe(pair.key, listener); - }.bind(this)); - }, - removeForm: function() { - if (!this._form) return; - this._form.remove(); - this._form = null; - this._controls = { }; - }, - showSaving: function() { - this._oldInnerHTML = this.element.innerHTML; - this.element.innerHTML = this.options.savingText; - this.element.addClassName(this.options.savingClassName); - this.element.style.backgroundColor = this._originalBackground; - this.element.show(); - }, - triggerCallback: function(cbName, arg) { - if ('function' == typeof this.options[cbName]) { - this.options[cbName](this, arg); - } - }, - unregisterListeners: function() { - $H(this._listeners).each(function(pair) { - if (!this.options.externalControlOnly) - this.element.stopObserving(pair.key, pair.value); - if (this.options.externalControl) - this.options.externalControl.stopObserving(pair.key, pair.value); - }.bind(this)); - }, - wrapUp: function(transport) { - this.leaveEditMode(); - // Can't use triggerCallback due to backward compatibility: requires - // binding + direct element - this._boundComplete(transport, this.element); - } -}); - -Object.extend(Ajax.InPlaceEditor.prototype, { - dispose: Ajax.InPlaceEditor.prototype.destroy -}); - -Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { - initialize: function($super, element, url, options) { - this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; - $super(element, url, options); - }, - - createEditField: function() { - var list = document.createElement('select'); - list.name = this.options.paramName; - list.size = 1; - this._controls.editor = list; - this._collection = this.options.collection || []; - if (this.options.loadCollectionURL) - this.loadCollection(); - else - this.checkForExternalText(); - this._form.appendChild(this._controls.editor); - }, - - loadCollection: function() { - this._form.addClassName(this.options.loadingClassName); - this.showLoadingText(this.options.loadingCollectionText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - var js = transport.responseText.strip(); - if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw('Server returned an invalid collection representation.'); - this._collection = eval(js); - this.checkForExternalText(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadCollectionURL, options); - }, - - showLoadingText: function(text) { - this._controls.editor.disabled = true; - var tempOption = this._controls.editor.firstChild; - if (!tempOption) { - tempOption = document.createElement('option'); - tempOption.value = ''; - this._controls.editor.appendChild(tempOption); - tempOption.selected = true; - } - tempOption.update((text || '').stripScripts().stripTags()); - }, - - checkForExternalText: function() { - this._text = this.getText(); - if (this.options.loadTextURL) - this.loadExternalText(); - else - this.buildOptionList(); - }, - - loadExternalText: function() { - this.showLoadingText(this.options.loadingText); - var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); - Object.extend(options, { - parameters: 'editorId=' + encodeURIComponent(this.element.id), - onComplete: Prototype.emptyFunction, - onSuccess: function(transport) { - this._text = transport.responseText.strip(); - this.buildOptionList(); - }.bind(this), - onFailure: this.onFailure - }); - new Ajax.Request(this.options.loadTextURL, options); - }, - - buildOptionList: function() { - this._form.removeClassName(this.options.loadingClassName); - this._collection = this._collection.map(function(entry) { - return 2 === entry.length ? entry : [entry, entry].flatten(); - }); - var marker = ('value' in this.options) ? this.options.value : this._text; - var textFound = this._collection.any(function(entry) { - return entry[0] == marker; - }.bind(this)); - this._controls.editor.update(''); - var option; - this._collection.each(function(entry, index) { - option = document.createElement('option'); - option.value = entry[0]; - option.selected = textFound ? entry[0] == marker : 0 == index; - option.appendChild(document.createTextNode(entry[1])); - this._controls.editor.appendChild(option); - }.bind(this)); - this._controls.editor.disabled = false; - Field.scrollFreeActivate(this._controls.editor); - } -}); - -//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** -//**** This only exists for a while, in order to let **** -//**** users adapt to the new API. Read up on the new **** -//**** API and convert your code to it ASAP! **** - -Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { - if (!options) return; - function fallback(name, expr) { - if (name in options || expr === undefined) return; - options[name] = expr; - }; - fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : - options.cancelLink == options.cancelButton == false ? false : undefined))); - fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : - options.okLink == options.okButton == false ? false : undefined))); - fallback('highlightColor', options.highlightcolor); - fallback('highlightEndColor', options.highlightendcolor); -}; - -Object.extend(Ajax.InPlaceEditor, { - DefaultOptions: { - ajaxOptions: { }, - autoRows: 3, // Use when multi-line w/ rows == 1 - cancelControl: 'link', // 'link'|'button'|false - cancelText: 'cancel', - clickToEditText: 'Click to edit', - externalControl: null, // id|elt - externalControlOnly: false, - fieldPostCreation: 'activate', // 'activate'|'focus'|false - formClassName: 'inplaceeditor-form', - formId: null, // id|elt - highlightColor: '#ffff99', - highlightEndColor: '#ffffff', - hoverClassName: '', - htmlResponse: true, - loadingClassName: 'inplaceeditor-loading', - loadingText: 'Loading...', - okControl: 'button', // 'link'|'button'|false - okText: 'ok', - paramName: 'value', - rows: 1, // If 1 and multi-line, uses autoRows - savingClassName: 'inplaceeditor-saving', - savingText: 'Saving...', - size: 0, - stripLoadedTextTags: false, - submitOnBlur: false, - textAfterControls: '', - textBeforeControls: '', - textBetweenControls: '' - }, - DefaultCallbacks: { - callback: function(form) { - return Form.serialize(form); - }, - onComplete: function(transport, element) { - // For backward compatibility, this one is bound to the IPE, and passes - // the element directly. It was too often customized, so we don't break it. - new Effect.Highlight(element, { - startcolor: this.options.highlightColor, keepBackgroundImage: true }); - }, - onEnterEditMode: null, - onEnterHover: function(ipe) { - ipe.element.style.backgroundColor = ipe.options.highlightColor; - if (ipe._effect) - ipe._effect.cancel(); - }, - onFailure: function(transport, ipe) { - alert('Error communication with the server: ' + transport.responseText.stripTags()); - }, - onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. - onLeaveEditMode: null, - onLeaveHover: function(ipe) { - ipe._effect = new Effect.Highlight(ipe.element, { - startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, - restorecolor: ipe._originalBackground, keepBackgroundImage: true - }); - } - }, - Listeners: { - click: 'enterEditMode', - keydown: 'checkForEscapeOrReturn', - mouseover: 'enterHover', - mouseout: 'leaveHover' - } -}); - -Ajax.InPlaceCollectionEditor.DefaultOptions = { - loadingCollectionText: 'Loading options...' -}; - -// Delayed observer, like Form.Element.Observer, -// but waits for delay after last key input -// Ideal for live-search fields - -Form.Element.DelayedObserver = Class.create({ - initialize: function(element, delay, callback) { - this.delay = delay || 0.5; - this.element = $(element); - this.callback = callback; - this.timer = null; - this.lastValue = $F(this.element); - Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); - }, - delayedListener: function(event) { - if(this.lastValue == $F(this.element)) return; - if(this.timer) clearTimeout(this.timer); - this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); - this.lastValue = $F(this.element); - }, - onTimerEvent: function() { - this.timer = null; - this.callback(this.element, $F(this.element)); - } -}); \ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js deleted file mode 100644 index 15c6dbca68..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/dragdrop.js +++ /dev/null @@ -1,974 +0,0 @@ -// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 - -// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -if(Object.isUndefined(Effect)) - throw("dragdrop.js requires including script.aculo.us' effects.js library"); - -var Droppables = { - drops: [], - - remove: function(element) { - this.drops = this.drops.reject(function(d) { return d.element==$(element) }); - }, - - add: function(element) { - element = $(element); - var options = Object.extend({ - greedy: true, - hoverclass: null, - tree: false - }, arguments[1] || { }); - - // cache containers - if(options.containment) { - options._containers = []; - var containment = options.containment; - if(Object.isArray(containment)) { - containment.each( function(c) { options._containers.push($(c)) }); - } else { - options._containers.push($(containment)); - } - } - - if(options.accept) options.accept = [options.accept].flatten(); - - Element.makePositioned(element); // fix IE - options.element = element; - - this.drops.push(options); - }, - - findDeepestChild: function(drops) { - deepest = drops[0]; - - for (i = 1; i < drops.length; ++i) - if (Element.isParent(drops[i].element, deepest.element)) - deepest = drops[i]; - - return deepest; - }, - - isContained: function(element, drop) { - var containmentNode; - if(drop.tree) { - containmentNode = element.treeNode; - } else { - containmentNode = element.parentNode; - } - return drop._containers.detect(function(c) { return containmentNode == c }); - }, - - isAffected: function(point, element, drop) { - return ( - (drop.element!=element) && - ((!drop._containers) || - this.isContained(element, drop)) && - ((!drop.accept) || - (Element.classNames(element).detect( - function(v) { return drop.accept.include(v) } ) )) && - Position.within(drop.element, point[0], point[1]) ); - }, - - deactivate: function(drop) { - if(drop.hoverclass) - Element.removeClassName(drop.element, drop.hoverclass); - this.last_active = null; - }, - - activate: function(drop) { - if(drop.hoverclass) - Element.addClassName(drop.element, drop.hoverclass); - this.last_active = drop; - }, - - show: function(point, element) { - if(!this.drops.length) return; - var drop, affected = []; - - this.drops.each( function(drop) { - if(Droppables.isAffected(point, element, drop)) - affected.push(drop); - }); - - if(affected.length>0) - drop = Droppables.findDeepestChild(affected); - - if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); - if (drop) { - Position.within(drop.element, point[0], point[1]); - if(drop.onHover) - drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - - if (drop != this.last_active) Droppables.activate(drop); - } - }, - - fire: function(event, element) { - if(!this.last_active) return; - Position.prepare(); - - if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) - if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; - } - }, - - reset: function() { - if(this.last_active) - this.deactivate(this.last_active); - } -}; - -var Draggables = { - drags: [], - observers: [], - - register: function(draggable) { - if(this.drags.length == 0) { - this.eventMouseUp = this.endDrag.bindAsEventListener(this); - this.eventMouseMove = this.updateDrag.bindAsEventListener(this); - this.eventKeypress = this.keyPress.bindAsEventListener(this); - - Event.observe(document, "mouseup", this.eventMouseUp); - Event.observe(document, "mousemove", this.eventMouseMove); - Event.observe(document, "keypress", this.eventKeypress); - } - this.drags.push(draggable); - }, - - unregister: function(draggable) { - this.drags = this.drags.reject(function(d) { return d==draggable }); - if(this.drags.length == 0) { - Event.stopObserving(document, "mouseup", this.eventMouseUp); - Event.stopObserving(document, "mousemove", this.eventMouseMove); - Event.stopObserving(document, "keypress", this.eventKeypress); - } - }, - - activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); - } else { - window.focus(); // allows keypress events if window isn't currently focused, fails for Safari - this.activeDraggable = draggable; - } - }, - - deactivate: function() { - this.activeDraggable = null; - }, - - updateDrag: function(event) { - if(!this.activeDraggable) return; - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - // Mozilla-based browsers fire successive mousemove events with - // the same coordinates, prevent needless redrawing (moz bug?) - if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; - this._lastPointer = pointer; - - this.activeDraggable.updateDrag(event, pointer); - }, - - endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; - } - if(!this.activeDraggable) return; - this._lastPointer = null; - this.activeDraggable.endDrag(event); - this.activeDraggable = null; - }, - - keyPress: function(event) { - if(this.activeDraggable) - this.activeDraggable.keyPress(event); - }, - - addObserver: function(observer) { - this.observers.push(observer); - this._cacheObserverCallbacks(); - }, - - removeObserver: function(element) { // element instead of observer fixes mem leaks - this.observers = this.observers.reject( function(o) { return o.element==element }); - this._cacheObserverCallbacks(); - }, - - notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' - if(this[eventName+'Count'] > 0) - this.observers.each( function(o) { - if(o[eventName]) o[eventName](eventName, draggable, event); - }); - if(draggable.options[eventName]) draggable.options[eventName](draggable, event); - }, - - _cacheObserverCallbacks: function() { - ['onStart','onEnd','onDrag'].each( function(eventName) { - Draggables[eventName+'Count'] = Draggables.observers.select( - function(o) { return o[eventName]; } - ).length; - }); - } -}; - -/*--------------------------------------------------------------------------*/ - -var Draggable = Class.create({ - initialize: function(element) { - var defaults = { - handle: false, - reverteffect: function(element, top_offset, left_offset) { - var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; - new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, - queue: {scope:'_draggable', position:'end'} - }); - }, - endeffect: function(element) { - var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, - queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false - } - }); - }, - zindex: 1000, - revert: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } - delay: 0 - }; - - if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) - Object.extend(defaults, { - starteffect: function(element) { - element._opacity = Element.getOpacity(element); - Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); - } - }); - - var options = Object.extend(defaults, arguments[1] || { }); - - this.element = $(element); - - if(options.handle && Object.isString(options.handle)) - this.handle = this.element.down('.'+options.handle, 0); - - if(!this.handle) this.handle = $(options.handle); - if(!this.handle) this.handle = this.element; - - if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { - options.scroll = $(options.scroll); - this._isScrollChild = Element.childOf(this.element, options.scroll); - } - - Element.makePositioned(this.element); // fix IE - - this.options = options; - this.dragging = false; - - this.eventMouseDown = this.initDrag.bindAsEventListener(this); - Event.observe(this.handle, "mousedown", this.eventMouseDown); - - Draggables.register(this); - }, - - destroy: function() { - Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); - Draggables.unregister(this); - }, - - currentDelta: function() { - return([ - parseInt(Element.getStyle(this.element,'left') || '0'), - parseInt(Element.getStyle(this.element,'top') || '0')]); - }, - - initDrag: function(event) { - if(!Object.isUndefined(Draggable._dragging[this.element]) && - Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { - // abort on form elements, fixes a Firefox issue - var src = Event.element(event); - if((tag_name = src.tagName.toUpperCase()) && ( - tag_name=='INPUT' || - tag_name=='SELECT' || - tag_name=='OPTION' || - tag_name=='BUTTON' || - tag_name=='TEXTAREA')) return; - - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var pos = this.element.cumulativeOffset(); - this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - - Draggables.activate(this); - Event.stop(event); - } - }, - - startDrag: function(event) { - this.dragging = true; - if(!this.delta) - this.delta = this.currentDelta(); - - if(this.options.zindex) { - this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); - this.element.style.zIndex = this.options.zindex; - } - - if(this.options.ghosting) { - this._clone = this.element.cloneNode(true); - this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this._originallyAbsolute) - Position.absolutize(this.element); - this.element.parentNode.insertBefore(this._clone, this.element); - } - - if(this.options.scroll) { - if (this.options.scroll == window) { - var where = this._getWindowScroll(this.options.scroll); - this.originalScrollLeft = where.left; - this.originalScrollTop = where.top; - } else { - this.originalScrollLeft = this.options.scroll.scrollLeft; - this.originalScrollTop = this.options.scroll.scrollTop; - } - } - - Draggables.notify('onStart', this, event); - - if(this.options.starteffect) this.options.starteffect(this.element); - }, - - updateDrag: function(event, pointer) { - if(!this.dragging) this.startDrag(event); - - if(!this.options.quiet){ - Position.prepare(); - Droppables.show(pointer, this.element); - } - - Draggables.notify('onDrag', this, event); - - this.draw(pointer); - if(this.options.change) this.options.change(this); - - if(this.options.scroll) { - this.stopScrolling(); - - var p; - if (this.options.scroll == window) { - with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } - } else { - p = Position.page(this.options.scroll); - p[0] += this.options.scroll.scrollLeft + Position.deltaX; - p[1] += this.options.scroll.scrollTop + Position.deltaY; - p.push(p[0]+this.options.scroll.offsetWidth); - p.push(p[1]+this.options.scroll.offsetHeight); - } - var speed = [0,0]; - if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); - if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); - if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); - if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); - this.startScrolling(speed); - } - - // fix AppleWebKit rendering - if(Prototype.Browser.WebKit) window.scrollBy(0,0); - - Event.stop(event); - }, - - finishDrag: function(event, success) { - this.dragging = false; - - if(this.options.quiet){ - Position.prepare(); - var pointer = [Event.pointerX(event), Event.pointerY(event)]; - Droppables.show(pointer, this.element); - } - - if(this.options.ghosting) { - if (!this._originallyAbsolute) - Position.relativize(this.element); - delete this._originallyAbsolute; - Element.remove(this._clone); - this._clone = null; - } - - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; - } - if(dropped && this.options.onDropped) this.options.onDropped(this.element); - Draggables.notify('onEnd', this, event); - - var revert = this.options.revert; - if(revert && Object.isFunction(revert)) revert = revert(this.element); - - var d = this.currentDelta(); - if(revert && this.options.reverteffect) { - if (dropped == 0 || revert != 'failure') - this.options.reverteffect(this.element, - d[1]-this.delta[1], d[0]-this.delta[0]); - } else { - this.delta = d; - } - - if(this.options.zindex) - this.element.style.zIndex = this.originalZ; - - if(this.options.endeffect) - this.options.endeffect(this.element); - - Draggables.deactivate(this); - Droppables.reset(); - }, - - keyPress: function(event) { - if(event.keyCode!=Event.KEY_ESC) return; - this.finishDrag(event, false); - Event.stop(event); - }, - - endDrag: function(event) { - if(!this.dragging) return; - this.stopScrolling(); - this.finishDrag(event, true); - Event.stop(event); - }, - - draw: function(point) { - var pos = this.element.cumulativeOffset(); - if(this.options.ghosting) { - var r = Position.realOffset(this.element); - pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; - } - - var d = this.currentDelta(); - pos[0] -= d[0]; pos[1] -= d[1]; - - if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { - pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; - pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; - } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) - }.bind(this)); - - if(this.options.snap) { - if(Object.isFunction(this.options.snap)) { - p = this.options.snap(p[0],p[1],this); - } else { - if(Object.isArray(this.options.snap)) { - p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); - } else { - p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)); - } - }} - - var style = this.element.style; - if((!this.options.constraint) || (this.options.constraint=='horizontal')) - style.left = p[0] + "px"; - if((!this.options.constraint) || (this.options.constraint=='vertical')) - style.top = p[1] + "px"; - - if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering - }, - - stopScrolling: function() { - if(this.scrollInterval) { - clearInterval(this.scrollInterval); - this.scrollInterval = null; - Draggables._lastScrollPointer = null; - } - }, - - startScrolling: function(speed) { - if(!(speed[0] || speed[1])) return; - this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; - this.lastScrolled = new Date(); - this.scrollInterval = setInterval(this.scroll.bind(this), 10); - }, - - scroll: function() { - var current = new Date(); - var delta = current - this.lastScrolled; - this.lastScrolled = current; - if(this.options.scroll == window) { - with (this._getWindowScroll(this.options.scroll)) { - if (this.scrollSpeed[0] || this.scrollSpeed[1]) { - var d = delta / 1000; - this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); - } - } - } else { - this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; - this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; - } - - Position.prepare(); - Droppables.show(Draggables._lastPointer, this.element); - Draggables.notify('onDrag', this); - if (this._isScrollChild) { - Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); - Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; - Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; - if (Draggables._lastScrollPointer[0] < 0) - Draggables._lastScrollPointer[0] = 0; - if (Draggables._lastScrollPointer[1] < 0) - Draggables._lastScrollPointer[1] = 0; - this.draw(Draggables._lastScrollPointer); - } - - if(this.options.change) this.options.change(this); - }, - - _getWindowScroll: function(w) { - var T, L, W, H; - with (w.document) { - if (w.document.documentElement && documentElement.scrollTop) { - T = documentElement.scrollTop; - L = documentElement.scrollLeft; - } else if (w.document.body) { - T = body.scrollTop; - L = body.scrollLeft; - } - if (w.innerWidth) { - W = w.innerWidth; - H = w.innerHeight; - } else if (w.document.documentElement && documentElement.clientWidth) { - W = documentElement.clientWidth; - H = documentElement.clientHeight; - } else { - W = body.offsetWidth; - H = body.offsetHeight; - } - } - return { top: T, left: L, width: W, height: H }; - } -}); - -Draggable._dragging = { }; - -/*--------------------------------------------------------------------------*/ - -var SortableObserver = Class.create({ - initialize: function(element, observer) { - this.element = $(element); - this.observer = observer; - this.lastValue = Sortable.serialize(this.element); - }, - - onStart: function() { - this.lastValue = Sortable.serialize(this.element); - }, - - onEnd: function() { - Sortable.unmark(); - if(this.lastValue != Sortable.serialize(this.element)) - this.observer(this.element) - } -}); - -var Sortable = { - SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - - sortables: { }, - - _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { - if(element.id && Sortable.sortables[element.id]) return element; - element = element.parentNode; - } - }, - - options: function(element) { - element = Sortable._findRootElement($(element)); - if(!element) return; - return Sortable.sortables[element.id]; - }, - - destroy: function(element){ - element = $(element); - var s = Sortable.sortables[element.id]; - - if(s) { - Draggables.removeObserver(s.element); - s.droppables.each(function(d){ Droppables.remove(d) }); - s.draggables.invoke('destroy'); - - delete Sortable.sortables[s.element.id]; - } - }, - - create: function(element) { - element = $(element); - var options = Object.extend({ - element: element, - tag: 'li', // assumes li children, override with tag: 'tagname' - dropOnEmpty: false, - tree: false, - treeTag: 'ul', - overlap: 'vertical', // one of 'vertical', 'horizontal' - constraint: 'vertical', // one of 'vertical', 'horizontal', false - containment: element, // also takes array of elements (or id's); or false - handle: false, // or a CSS class - only: false, - delay: 0, - hoverclass: null, - ghosting: false, - quiet: false, - scroll: false, - scrollSensitivity: 20, - scrollSpeed: 15, - format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be - // used for better initialization performance - elements: false, - handles: false, - - onChange: Prototype.emptyFunction, - onUpdate: Prototype.emptyFunction - }, arguments[1] || { }); - - // clear any old sortable with same element - this.destroy(element); - - // build options for the draggables - var options_for_draggable = { - revert: true, - quiet: options.quiet, - scroll: options.scroll, - scrollSpeed: options.scrollSpeed, - scrollSensitivity: options.scrollSensitivity, - delay: options.delay, - ghosting: options.ghosting, - constraint: options.constraint, - handle: options.handle }; - - if(options.starteffect) - options_for_draggable.starteffect = options.starteffect; - - if(options.reverteffect) - options_for_draggable.reverteffect = options.reverteffect; - else - if(options.ghosting) options_for_draggable.reverteffect = function(element) { - element.style.top = 0; - element.style.left = 0; - }; - - if(options.endeffect) - options_for_draggable.endeffect = options.endeffect; - - if(options.zindex) - options_for_draggable.zindex = options.zindex; - - // build options for the droppables - var options_for_droppable = { - overlap: options.overlap, - containment: options.containment, - tree: options.tree, - hoverclass: options.hoverclass, - onHover: Sortable.onHover - }; - - var options_for_tree = { - onHover: Sortable.onEmptyHover, - overlap: options.overlap, - containment: options.containment, - hoverclass: options.hoverclass - }; - - // fix for gecko engine - Element.cleanWhitespace(element); - - options.draggables = []; - options.droppables = []; - - // drop on empty handling - if(options.dropOnEmpty || options.tree) { - Droppables.add(element, options_for_tree); - options.droppables.push(element); - } - - (options.elements || this.findElements(element, options) || []).each( function(e,i) { - var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); - options.draggables.push( - new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); - Droppables.add(e, options_for_droppable); - if(options.tree) e.treeNode = element; - options.droppables.push(e); - }); - - if(options.tree) { - (Sortable.findTreeElements(element, options) || []).each( function(e) { - Droppables.add(e, options_for_tree); - e.treeNode = element; - options.droppables.push(e); - }); - } - - // keep reference - this.sortables[element.identify()] = options; - - // for onupdate - Draggables.addObserver(new SortableObserver(element, options.onUpdate)); - - }, - - // return all suitable-for-sortable elements in a guaranteed order - findElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.tag); - }, - - findTreeElements: function(element, options) { - return Element.findChildren( - element, options.only, options.tree ? true : false, options.treeTag); - }, - - onHover: function(element, dropon, overlap) { - if(Element.isParent(dropon, element)) return; - - if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { - return; - } else if(overlap>0.5) { - Sortable.mark(dropon, 'before'); - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } else { - Sortable.mark(dropon, 'after'); - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) - Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon.parentNode).onChange(element); - } - } - }, - - onEmptyHover: function(element, dropon, overlap) { - var oldParentNode = element.parentNode; - var droponOptions = Sortable.options(dropon); - - if(!Element.isParent(dropon, element)) { - var index; - - var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); - var child = null; - - if(children) { - var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - - for (index = 0; index < children.length; index += 1) { - if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { - offset -= Element.offsetSize (children[index], droponOptions.overlap); - } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { - child = index + 1 < children.length ? children[index + 1] : null; - break; - } else { - child = children[index]; - break; - } - } - } - - dropon.insertBefore(element, child); - - Sortable.options(oldParentNode).onChange(element); - droponOptions.onChange(element); - } - }, - - unmark: function() { - if(Sortable._marker) Sortable._marker.hide(); - }, - - mark: function(dropon, position) { - // mark on ghosting only - var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; - - if(!Sortable._marker) { - Sortable._marker = - ($('dropmarker') || Element.extend(document.createElement('DIV'))). - hide().addClassName('dropmarker').setStyle({position:'absolute'}); - document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } - var offsets = dropon.cumulativeOffset(); - Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - - if(position=='after') - if(sortable.overlap == 'horizontal') - Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); - else - Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - - Sortable._marker.show(); - }, - - _tree: function(element, options, parent) { - var children = Sortable.findElements(element, options) || []; - - for (var i = 0; i < children.length; ++i) { - var match = children[i].id.match(options.format); - - if (!match) continue; - - var child = { - id: encodeURIComponent(match ? match[1] : null), - element: element, - parent: parent, - children: [], - position: parent.children.length, - container: $(children[i]).down(options.treeTag) - }; - - /* Get the element containing the children and recurse over it */ - if (child.container) - this._tree(child.container, options, child); - - parent.children.push (child); - } - - return parent; - }, - - tree: function(element) { - element = $(element); - var sortableOptions = this.options(element); - var options = Object.extend({ - tag: sortableOptions.tag, - treeTag: sortableOptions.treeTag, - only: sortableOptions.only, - name: element.id, - format: sortableOptions.format - }, arguments[1] || { }); - - var root = { - id: null, - parent: null, - children: [], - container: element, - position: 0 - }; - - return Sortable._tree(element, options, root); - }, - - /* Construct a [i] index for a particular node */ - _constructIndex: function(node) { - var index = ''; - do { - if (node.id) index = '[' + node.position + ']' + index; - } while ((node = node.parent) != null); - return index; - }, - - sequence: function(element) { - element = $(element); - var options = Object.extend(this.options(element), arguments[1] || { }); - - return $(this.findElements(element, options) || []).map( function(item) { - return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; - }); - }, - - setSequence: function(element, new_sequence) { - element = $(element); - var options = Object.extend(this.options(element), arguments[2] || { }); - - var nodeMap = { }; - this.findElements(element, options).each( function(n) { - if (n.id.match(options.format)) - nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; - n.parentNode.removeChild(n); - }); - - new_sequence.each(function(ident) { - var n = nodeMap[ident]; - if (n) { - n[1].appendChild(n[0]); - delete nodeMap[ident]; - } - }); - }, - - serialize: function(element) { - element = $(element); - var options = Object.extend(Sortable.options(element), arguments[1] || { }); - var name = encodeURIComponent( - (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - - if (options.tree) { - return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + - encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); - }).flatten().join('&'); - } else { - return Sortable.sequence(element, arguments[1]).map( function(item) { - return name + "[]=" + encodeURIComponent(item); - }).join('&'); - } - } -}; - -// Returns true if child is contained within element -Element.isParent = function(child, element) { - if (!child.parentNode || child == element) return false; - if (child.parentNode == element) return true; - return Element.isParent(child.parentNode, element); -}; - -Element.findChildren = function(element, only, recursive, tagName) { - if(!element.hasChildNodes()) return null; - tagName = tagName.toUpperCase(); - if(only) only = [only].flatten(); - var elements = []; - $A(element.childNodes).each( function(e) { - if(e.tagName && e.tagName.toUpperCase()==tagName && - (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) - elements.push(e); - if(recursive) { - var grandchildren = Element.findChildren(e, only, recursive, tagName); - if(grandchildren) elements.push(grandchildren); - } - }); - - return (elements.length>0 ? elements.flatten() : []); -}; - -Element.offsetSize = function (element, type) { - return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -}; \ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js deleted file mode 100644 index c81e6c7d5f..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/effects.js +++ /dev/null @@ -1,1123 +0,0 @@ -// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 - -// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// Contributors: -// Justin Palmer (http://encytemedia.com/) -// Mark Pilgrim (http://diveintomark.org/) -// Martin Bialasinki -// -// script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ - -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { - var color = '#'; - if (this.slice(0,4) == 'rgb(') { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if (this.slice(0,1) == '#') { - if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if (this.length==7) color = this.toLowerCase(); - } - } - return (color.length==7 ? color : (arguments[0] || this)); -}; - -/*--------------------------------------------------------------------------*/ - -Element.collectTextNodes = function(element) { - return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); - }).flatten().join(''); -}; - -Element.collectTextNodesIgnoreClass = function(element, className) { - return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? - Element.collectTextNodesIgnoreClass(node, className) : '')); - }).flatten().join(''); -}; - -Element.setContentZoom = function(element, percent) { - element = $(element); - element.setStyle({fontSize: (percent/100) + 'em'}); - if (Prototype.Browser.WebKit) window.scrollBy(0,0); - return element; -}; - -Element.getInlineOpacity = function(element){ - return $(element).style.opacity || ''; -}; - -Element.forceRerendering = function(element) { - try { - element = $(element); - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch(e) { } -}; - -/*--------------------------------------------------------------------------*/ - -var Effect = { - _elementDoesNotExistError: { - name: 'ElementDoesNotExistError', - message: 'The specified DOM element does not exist, but is required for this effect to operate' - }, - Transitions: { - linear: Prototype.K, - sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + .5; - }, - reverse: function(pos) { - return 1-pos; - }, - flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; - return pos > 1 ? 1 : pos; - }, - wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; - }, - pulse: function(pos, pulses) { - return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; - }, - spring: function(pos) { - return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); - }, - none: function(pos) { - return 0; - }, - full: function(pos) { - return 1; - } - }, - DefaultOptions: { - duration: 1.0, // seconds - fps: 100, // 100= assume 66fps max. - sync: false, // true for combining - from: 0.0, - to: 1.0, - delay: 0.0, - queue: 'parallel' - }, - tagifyText: function(element) { - var tagifyStyle = 'position:relative'; - if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; - - element = $(element); - $A(element.childNodes).each( function(child) { - if (child.nodeType==3) { - child.nodeValue.toArray().each( function(character) { - element.insertBefore( - new Element('span', {style: tagifyStyle}).update( - character == ' ' ? String.fromCharCode(160) : character), - child); - }); - Element.remove(child); - } - }); - }, - multiple: function(element, effect) { - var elements; - if (((typeof element == 'object') || - Object.isFunction(element)) && - (element.length)) - elements = element; - else - elements = $(element).childNodes; - - var options = Object.extend({ - speed: 0.1, - delay: 0.0 - }, arguments[2] || { }); - var masterDelay = options.delay; - - $A(elements).each( function(element, index) { - new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); - }); - }, - PAIRS: { - 'slide': ['SlideDown','SlideUp'], - 'blind': ['BlindDown','BlindUp'], - 'appear': ['Appear','Fade'] - }, - toggle: function(element, effect, options) { - element = $(element); - effect = (effect || 'appear').toLowerCase(); - - return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ - queue: { position:'end', scope:(element.id || 'global'), limit: 1 } - }, options || {})); - } -}; - -Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; - -/* ------------- core effects ------------- */ - -Effect.ScopedQueue = Class.create(Enumerable, { - initialize: function() { - this.effects = []; - this.interval = null; - }, - _each: function(iterator) { - this.effects._each(iterator); - }, - add: function(effect) { - var timestamp = new Date().getTime(); - - var position = Object.isString(effect.options.queue) ? - effect.options.queue : effect.options.queue.position; - - switch(position) { - case 'front': - // move unstarted effects after this effect - this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { - e.startOn += effect.finishOn; - e.finishOn += effect.finishOn; - }); - break; - case 'with-last': - timestamp = this.effects.pluck('startOn').max() || timestamp; - break; - case 'end': - // start effect after last queued effect has finished - timestamp = this.effects.pluck('finishOn').max() || timestamp; - break; - } - - effect.startOn += timestamp; - effect.finishOn += timestamp; - - if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) - this.effects.push(effect); - - if (!this.interval) - this.interval = setInterval(this.loop.bind(this), 15); - }, - remove: function(effect) { - this.effects = this.effects.reject(function(e) { return e==effect }); - if (this.effects.length == 0) { - clearInterval(this.interval); - this.interval = null; - } - }, - loop: function() { - var timePos = new Date().getTime(); - for(var i=0, len=this.effects.length;i= this.startOn) { - if (timePos >= this.finishOn) { - this.render(1.0); - this.cancel(); - this.event('beforeFinish'); - if (this.finish) this.finish(); - this.event('afterFinish'); - return; - } - var pos = (timePos - this.startOn) / this.totalTime, - frame = (pos * this.totalFrames).round(); - if (frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - } - }, - cancel: function() { - if (!this.options.sync) - Effect.Queues.get(Object.isString(this.options.queue) ? - 'global' : this.options.queue.scope).remove(this); - this.state = 'finished'; - }, - event: function(eventName) { - if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); - if (this.options[eventName]) this.options[eventName](this); - }, - inspect: function() { - var data = $H(); - for(property in this) - if (!Object.isFunction(this[property])) data.set(property, this[property]); - return '#'; - } -}); - -Effect.Parallel = Class.create(Effect.Base, { - initialize: function(effects) { - this.effects = effects || []; - this.start(arguments[1]); - }, - update: function(position) { - this.effects.invoke('render', position); - }, - finish: function(position) { - this.effects.each( function(effect) { - effect.render(1.0); - effect.cancel(); - effect.event('beforeFinish'); - if (effect.finish) effect.finish(position); - effect.event('afterFinish'); - }); - } -}); - -Effect.Tween = Class.create(Effect.Base, { - initialize: function(object, from, to) { - object = Object.isString(object) ? $(object) : object; - var args = $A(arguments), method = args.last(), - options = args.length == 5 ? args[3] : null; - this.method = Object.isFunction(method) ? method.bind(object) : - Object.isFunction(object[method]) ? object[method].bind(object) : - function(value) { object[method] = value }; - this.start(Object.extend({ from: from, to: to }, options || { })); - }, - update: function(position) { - this.method(position); - } -}); - -Effect.Event = Class.create(Effect.Base, { - initialize: function() { - this.start(Object.extend({ duration: 0 }, arguments[0] || { })); - }, - update: Prototype.emptyFunction -}); - -Effect.Opacity = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - // make this work on IE on elements without 'layout' - if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) - this.element.setStyle({zoom: 1}); - var options = Object.extend({ - from: this.element.getOpacity() || 0.0, - to: 1.0 - }, arguments[1] || { }); - this.start(options); - }, - update: function(position) { - this.element.setOpacity(position); - } -}); - -Effect.Move = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - x: 0, - y: 0, - mode: 'relative' - }, arguments[1] || { }); - this.start(options); - }, - setup: function() { - this.element.makePositioned(); - this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); - this.originalTop = parseFloat(this.element.getStyle('top') || '0'); - if (this.options.mode == 'absolute') { - this.options.x = this.options.x - this.originalLeft; - this.options.y = this.options.y - this.originalTop; - } - }, - update: function(position) { - this.element.setStyle({ - left: (this.options.x * position + this.originalLeft).round() + 'px', - top: (this.options.y * position + this.originalTop).round() + 'px' - }); - } -}); - -// for backwards compatibility -Effect.MoveBy = function(element, toTop, toLeft) { - return new Effect.Move(element, - Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); -}; - -Effect.Scale = Class.create(Effect.Base, { - initialize: function(element, percent) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - scaleX: true, - scaleY: true, - scaleContent: true, - scaleFromCenter: false, - scaleMode: 'box', // 'box' or 'contents' or { } with provided values - scaleFrom: 100.0, - scaleTo: percent - }, arguments[2] || { }); - this.start(options); - }, - setup: function() { - this.restoreAfterFinish = this.options.restoreAfterFinish || false; - this.elementPositioning = this.element.getStyle('position'); - - this.originalStyle = { }; - ['top','left','width','height','fontSize'].each( function(k) { - this.originalStyle[k] = this.element.style[k]; - }.bind(this)); - - this.originalTop = this.element.offsetTop; - this.originalLeft = this.element.offsetLeft; - - var fontSize = this.element.getStyle('font-size') || '100%'; - ['em','px','%','pt'].each( function(fontSizeType) { - if (fontSize.indexOf(fontSizeType)>0) { - this.fontSize = parseFloat(fontSize); - this.fontSizeType = fontSizeType; - } - }.bind(this)); - - this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; - - this.dims = null; - if (this.options.scaleMode=='box') - this.dims = [this.element.offsetHeight, this.element.offsetWidth]; - if (/^content/.test(this.options.scaleMode)) - this.dims = [this.element.scrollHeight, this.element.scrollWidth]; - if (!this.dims) - this.dims = [this.options.scaleMode.originalHeight, - this.options.scaleMode.originalWidth]; - }, - update: function(position) { - var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if (this.options.scaleContent && this.fontSize) - this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); - this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); - }, - finish: function(position) { - if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); - }, - setDimensions: function(height, width) { - var d = { }; - if (this.options.scaleX) d.width = width.round() + 'px'; - if (this.options.scaleY) d.height = height.round() + 'px'; - if (this.options.scaleFromCenter) { - var topd = (height - this.dims[0])/2; - var leftd = (width - this.dims[1])/2; - if (this.elementPositioning == 'absolute') { - if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; - if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; - } else { - if (this.options.scaleY) d.top = -topd + 'px'; - if (this.options.scaleX) d.left = -leftd + 'px'; - } - } - this.element.setStyle(d); - } -}); - -Effect.Highlight = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); - this.start(options); - }, - setup: function() { - // Prevent executing on elements not in the layout flow - if (this.element.getStyle('display')=='none') { this.cancel(); return; } - // Disable background image during the effect - this.oldStyle = { }; - if (!this.options.keepBackgroundImage) { - this.oldStyle.backgroundImage = this.element.getStyle('background-image'); - this.element.setStyle({backgroundImage: 'none'}); - } - if (!this.options.endcolor) - this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); - if (!this.options.restorecolor) - this.options.restorecolor = this.element.getStyle('background-color'); - // init color calculations - this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); - this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); - }, - update: function(position) { - this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ - return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); - }, - finish: function() { - this.element.setStyle(Object.extend(this.oldStyle, { - backgroundColor: this.options.restorecolor - })); - } -}); - -Effect.ScrollTo = function(element) { - var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(); - - if (options.offset) elementOffsets[1] += options.offset; - - return new Effect.Tween(null, - scrollOffsets.top, - elementOffsets[1], - options, - function(p){ scrollTo(scrollOffsets.left, p.round()); } - ); -}; - -/* ------------- combination effects ------------- */ - -Effect.Fade = function(element) { - element = $(element); - var oldOpacity = element.getInlineOpacity(); - var options = Object.extend({ - from: element.getOpacity() || 1.0, - to: 0.0, - afterFinishInternal: function(effect) { - if (effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); - } - }, arguments[1] || { }); - return new Effect.Opacity(element,options); -}; - -Effect.Appear = function(element) { - element = $(element); - var options = Object.extend({ - from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), - to: 1.0, - // force Safari to render floated elements properly - afterFinishInternal: function(effect) { - effect.element.forceRerendering(); - }, - beforeSetup: function(effect) { - effect.element.setOpacity(effect.options.from).show(); - }}, arguments[1] || { }); - return new Effect.Opacity(element,options); -}; - -Effect.Puff = function(element) { - element = $(element); - var oldStyle = { - opacity: element.getInlineOpacity(), - position: element.getStyle('position'), - top: element.style.top, - left: element.style.left, - width: element.style.width, - height: element.style.height - }; - return new Effect.Parallel( - [ new Effect.Scale(element, 200, - { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], - Object.extend({ duration: 1.0, - beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().setStyle(oldStyle); } - }, arguments[1] || { }) - ); -}; - -Effect.BlindUp = function(element) { - element = $(element); - element.makeClipping(); - return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, - restoreAfterFinish: true, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); - } - }, arguments[1] || { }) - ); -}; - -Effect.BlindDown = function(element) { - element = $(element); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, - scaleFrom: 0, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, - afterFinishInternal: function(effect) { - effect.element.undoClipping(); - } - }, arguments[1] || { })); -}; - -Effect.SwitchOff = function(element) { - element = $(element); - var oldOpacity = element.getInlineOpacity(); - return new Effect.Appear(element, Object.extend({ - duration: 0.4, - from: 0, - transition: Effect.Transitions.flicker, - afterFinishInternal: function(effect) { - new Effect.Scale(effect.element, 1, { - duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { - effect.element.makePositioned().makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); - } - }); - } - }, arguments[1] || { })); -}; - -Effect.DropOut = function(element) { - element = $(element); - var oldStyle = { - top: element.getStyle('top'), - left: element.getStyle('left'), - opacity: element.getInlineOpacity() }; - return new Effect.Parallel( - [ new Effect.Move(element, {x: 0, y: 100, sync: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 }) ], - Object.extend( - { duration: 0.5, - beforeSetup: function(effect) { - effect.effects[0].element.makePositioned(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); - } - }, arguments[1] || { })); -}; - -Effect.Shake = function(element) { - element = $(element); - var options = Object.extend({ - distance: 20, - duration: 0.5 - }, arguments[1] || {}); - var distance = parseFloat(options.distance); - var split = parseFloat(options.duration) / 10.0; - var oldStyle = { - top: element.getStyle('top'), - left: element.getStyle('left') }; - return new Effect.Move(element, - { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { - new Effect.Move(effect.element, - { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { - effect.element.undoPositioned().setStyle(oldStyle); - }}); }}); }}); }}); }}); }}); -}; - -Effect.SlideDown = function(element) { - element = $(element).cleanWhitespace(); - // SlideDown need to have the content of the element wrapped in a container element with fixed height! - var oldInnerBottom = element.down().getStyle('bottom'); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, - scaleFrom: window.opera ? 0 : 1, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makePositioned(); - effect.element.down().makePositioned(); - if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, - afterUpdateInternal: function(effect) { - effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); - }, - afterFinishInternal: function(effect) { - effect.element.undoClipping().undoPositioned(); - effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } - }, arguments[1] || { }) - ); -}; - -Effect.SlideUp = function(element) { - element = $(element).cleanWhitespace(); - var oldInnerBottom = element.down().getStyle('bottom'); - var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, window.opera ? 0 : 1, - Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'box', - scaleFrom: 100, - scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, - restoreAfterFinish: true, - afterSetup: function(effect) { - effect.element.makePositioned(); - effect.element.down().makePositioned(); - if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().show(); - }, - afterUpdateInternal: function(effect) { - effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().undoPositioned(); - effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); - } - }, arguments[1] || { }) - ); -}; - -// Bug in opera makes the TD containing this element expand for a instance after finish -Effect.Squish = function(element) { - return new Effect.Scale(element, window.opera ? 1 : 0, { - restoreAfterFinish: true, - beforeSetup: function(effect) { - effect.element.makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); - } - }); -}; - -Effect.Grow = function(element) { - element = $(element); - var options = Object.extend({ - direction: 'center', - moveTransition: Effect.Transitions.sinoidal, - scaleTransition: Effect.Transitions.sinoidal, - opacityTransition: Effect.Transitions.full - }, arguments[1] || { }); - var oldStyle = { - top: element.style.top, - left: element.style.left, - height: element.style.height, - width: element.style.width, - opacity: element.getInlineOpacity() }; - - var dims = element.getDimensions(); - var initialMoveX, initialMoveY; - var moveX, moveY; - - switch (options.direction) { - case 'top-left': - initialMoveX = initialMoveY = moveX = moveY = 0; - break; - case 'top-right': - initialMoveX = dims.width; - initialMoveY = moveY = 0; - moveX = -dims.width; - break; - case 'bottom-left': - initialMoveX = moveX = 0; - initialMoveY = dims.height; - moveY = -dims.height; - break; - case 'bottom-right': - initialMoveX = dims.width; - initialMoveY = dims.height; - moveX = -dims.width; - moveY = -dims.height; - break; - case 'center': - initialMoveX = dims.width / 2; - initialMoveY = dims.height / 2; - moveX = -dims.width / 2; - moveY = -dims.height / 2; - break; - } - - return new Effect.Move(element, { - x: initialMoveX, - y: initialMoveY, - duration: 0.01, - beforeSetup: function(effect) { - effect.element.hide().makeClipping().makePositioned(); - }, - afterFinishInternal: function(effect) { - new Effect.Parallel( - [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), - new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), - new Effect.Scale(effect.element, 100, { - scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, - sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) - ], Object.extend({ - beforeSetup: function(effect) { - effect.effects[0].element.setStyle({height: '0px'}).show(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); - } - }, options) - ); - } - }); -}; - -Effect.Shrink = function(element) { - element = $(element); - var options = Object.extend({ - direction: 'center', - moveTransition: Effect.Transitions.sinoidal, - scaleTransition: Effect.Transitions.sinoidal, - opacityTransition: Effect.Transitions.none - }, arguments[1] || { }); - var oldStyle = { - top: element.style.top, - left: element.style.left, - height: element.style.height, - width: element.style.width, - opacity: element.getInlineOpacity() }; - - var dims = element.getDimensions(); - var moveX, moveY; - - switch (options.direction) { - case 'top-left': - moveX = moveY = 0; - break; - case 'top-right': - moveX = dims.width; - moveY = 0; - break; - case 'bottom-left': - moveX = 0; - moveY = dims.height; - break; - case 'bottom-right': - moveX = dims.width; - moveY = dims.height; - break; - case 'center': - moveX = dims.width / 2; - moveY = dims.height / 2; - break; - } - - return new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), - new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), - new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) - ], Object.extend({ - beforeStartInternal: function(effect) { - effect.effects[0].element.makePositioned().makeClipping(); - }, - afterFinishInternal: function(effect) { - effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } - }, options) - ); -}; - -Effect.Pulsate = function(element) { - element = $(element); - var options = arguments[1] || { }, - oldOpacity = element.getInlineOpacity(), - transition = options.transition || Effect.Transitions.linear, - reverser = function(pos){ - return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); - }; - - return new Effect.Opacity(element, - Object.extend(Object.extend({ duration: 2.0, from: 0, - afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } - }, options), {transition: reverser})); -}; - -Effect.Fold = function(element) { - element = $(element); - var oldStyle = { - top: element.style.top, - left: element.style.left, - width: element.style.width, - height: element.style.height }; - element.makeClipping(); - return new Effect.Scale(element, 5, Object.extend({ - scaleContent: false, - scaleX: false, - afterFinishInternal: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, - scaleY: false, - afterFinishInternal: function(effect) { - effect.element.hide().undoClipping().setStyle(oldStyle); - } }); - }}, arguments[1] || { })); -}; - -Effect.Morph = Class.create(Effect.Base, { - initialize: function(element) { - this.element = $(element); - if (!this.element) throw(Effect._elementDoesNotExistError); - var options = Object.extend({ - style: { } - }, arguments[1] || { }); - - if (!Object.isString(options.style)) this.style = $H(options.style); - else { - if (options.style.include(':')) - this.style = options.style.parseStyle(); - else { - this.element.addClassName(options.style); - this.style = $H(this.element.getStyles()); - this.element.removeClassName(options.style); - var css = this.element.getStyles(); - this.style = this.style.reject(function(style) { - return style.value == css[style.key]; - }); - options.afterFinishInternal = function(effect) { - effect.element.addClassName(effect.options.style); - effect.transforms.each(function(transform) { - effect.element.style[transform.style] = ''; - }); - }; - } - } - this.start(options); - }, - - setup: function(){ - function parseColor(color){ - if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; - color = color.parseColor(); - return $R(0,2).map(function(i){ - return parseInt( color.slice(i*2+1,i*2+3), 16 ); - }); - } - this.transforms = this.style.map(function(pair){ - var property = pair[0], value = pair[1], unit = null; - - if (value.parseColor('#zzzzzz') != '#zzzzzz') { - value = value.parseColor(); - unit = 'color'; - } else if (property == 'opacity') { - value = parseFloat(value); - if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) - this.element.setStyle({zoom: 1}); - } else if (Element.CSS_LENGTH.test(value)) { - var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); - value = parseFloat(components[1]); - unit = (components.length == 3) ? components[2] : null; - } - - var originalValue = this.element.getStyle(property); - return { - style: property.camelize(), - originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), - targetValue: unit=='color' ? parseColor(value) : value, - unit: unit - }; - }.bind(this)).reject(function(transform){ - return ( - (transform.originalValue == transform.targetValue) || - ( - transform.unit != 'color' && - (isNaN(transform.originalValue) || isNaN(transform.targetValue)) - ) - ); - }); - }, - update: function(position) { - var style = { }, transform, i = this.transforms.length; - while(i--) - style[(transform = this.transforms[i]).style] = - transform.unit=='color' ? '#'+ - (Math.round(transform.originalValue[0]+ - (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + - (Math.round(transform.originalValue[1]+ - (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + - (Math.round(transform.originalValue[2]+ - (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : - (transform.originalValue + - (transform.targetValue - transform.originalValue) * position).toFixed(3) + - (transform.unit === null ? '' : transform.unit); - this.element.setStyle(style, true); - } -}); - -Effect.Transform = Class.create({ - initialize: function(tracks){ - this.tracks = []; - this.options = arguments[1] || { }; - this.addTracks(tracks); - }, - addTracks: function(tracks){ - tracks.each(function(track){ - track = $H(track); - var data = track.values().first(); - this.tracks.push($H({ - ids: track.keys().first(), - effect: Effect.Morph, - options: { style: data } - })); - }.bind(this)); - return this; - }, - play: function(){ - return new Effect.Parallel( - this.tracks.map(function(track){ - var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); - var elements = [$(ids) || $$(ids)].flatten(); - return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); - }).flatten(), - this.options - ); - } -}); - -Element.CSS_PROPERTIES = $w( - 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + - 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + - 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + - 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + - 'fontSize fontWeight height left letterSpacing lineHeight ' + - 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ - 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + - 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + - 'right textIndent top width wordSpacing zIndex'); - -Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; - -String.__parseStyleElement = document.createElement('div'); -String.prototype.parseStyle = function(){ - var style, styleRules = $H(); - if (Prototype.Browser.WebKit) - style = new Element('div',{style:this}).style; - else { - String.__parseStyleElement.innerHTML = '
    '; - style = String.__parseStyleElement.childNodes[0].style; - } - - Element.CSS_PROPERTIES.each(function(property){ - if (style[property]) styleRules.set(property, style[property]); - }); - - if (Prototype.Browser.IE && this.include('opacity')) - styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); - - return styleRules; -}; - -if (document.defaultView && document.defaultView.getComputedStyle) { - Element.getStyles = function(element) { - var css = document.defaultView.getComputedStyle($(element), null); - return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { - styles[property] = css[property]; - return styles; - }); - }; -} else { - Element.getStyles = function(element) { - element = $(element); - var css = element.currentStyle, styles; - styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { - results[property] = css[property]; - return results; - }); - if (!styles.opacity) styles.opacity = element.getOpacity(); - return styles; - }; -} - -Effect.Methods = { - morph: function(element, style) { - element = $(element); - new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); - return element; - }, - visualEffect: function(element, effect, options) { - element = $(element); - var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); - new Effect[klass](element, options); - return element; - }, - highlight: function(element, options) { - element = $(element); - new Effect.Highlight(element, options); - return element; - } -}; - -$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ - 'pulsate shake puff squish switchOff dropOut').each( - function(effect) { - Effect.Methods[effect] = function(element, options){ - element = $(element); - Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); - return element; - }; - } -); - -$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( - function(f) { Effect.Methods[f] = Element[f]; } -); - -Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js deleted file mode 100644 index 474b2231bb..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype.js +++ /dev/null @@ -1,6082 +0,0 @@ -/* Prototype JavaScript framework, version 1.7 - * (c) 2005-2010 Sam Stephenson - * - * Prototype is freely distributable under the terms of an MIT-style license. - * For details, see the Prototype web site: http://www.prototypejs.org/ - * - *--------------------------------------------------------------------------*/ - -var Prototype = { - - Version: '1.7', - - Browser: (function(){ - var ua = navigator.userAgent; - var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; - return { - IE: !!window.attachEvent && !isOpera, - Opera: isOpera, - WebKit: ua.indexOf('AppleWebKit/') > -1, - Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, - MobileSafari: /Apple.*Mobile/.test(ua) - } - })(), - - BrowserFeatures: { - XPath: !!document.evaluate, - - SelectorsAPI: !!document.querySelector, - - ElementExtensions: (function() { - var constructor = window.Element || window.HTMLElement; - return !!(constructor && constructor.prototype); - })(), - SpecificElementExtensions: (function() { - if (typeof window.HTMLDivElement !== 'undefined') - return true; - - var div = document.createElement('div'), - form = document.createElement('form'), - isSupported = false; - - if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { - isSupported = true; - } - - div = form = null; - - return isSupported; - })() - }, - - ScriptFragment: ']*>([\\S\\s]*?)<\/script>', - JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, - - emptyFunction: function() { }, - - K: function(x) { return x } -}; - -if (Prototype.Browser.MobileSafari) - Prototype.BrowserFeatures.SpecificElementExtensions = false; - - -var Abstract = { }; - - -var Try = { - these: function() { - var returnValue; - - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } - - return returnValue; - } -}; - -/* Based on Alex Arnell's inheritance implementation. */ - -var Class = (function() { - - var IS_DONTENUM_BUGGY = (function(){ - for (var p in { toString: 1 }) { - if (p === 'toString') return false; - } - return true; - })(); - - function subclass() {}; - function create() { - var parent = null, properties = $A(arguments); - if (Object.isFunction(properties[0])) - parent = properties.shift(); - - function klass() { - this.initialize.apply(this, arguments); - } - - Object.extend(klass, Class.Methods); - klass.superclass = parent; - klass.subclasses = []; - - if (parent) { - subclass.prototype = parent.prototype; - klass.prototype = new subclass; - parent.subclasses.push(klass); - } - - for (var i = 0, length = properties.length; i < length; i++) - klass.addMethods(properties[i]); - - if (!klass.prototype.initialize) - klass.prototype.initialize = Prototype.emptyFunction; - - klass.prototype.constructor = klass; - return klass; - } - - function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype, - properties = Object.keys(source); - - if (IS_DONTENUM_BUGGY) { - if (source.toString != Object.prototype.toString) - properties.push("toString"); - if (source.valueOf != Object.prototype.valueOf) - properties.push("valueOf"); - } - - for (var i = 0, length = properties.length; i < length; i++) { - var property = properties[i], value = source[property]; - if (ancestor && Object.isFunction(value) && - value.argumentNames()[0] == "$super") { - var method = value; - value = (function(m) { - return function() { return ancestor[m].apply(this, arguments); }; - })(property).wrap(method); - - value.valueOf = method.valueOf.bind(method); - value.toString = method.toString.bind(method); - } - this.prototype[property] = value; - } - - return this; - } - - return { - create: create, - Methods: { - addMethods: addMethods - } - }; -})(); -(function() { - - var _toString = Object.prototype.toString, - NULL_TYPE = 'Null', - UNDEFINED_TYPE = 'Undefined', - BOOLEAN_TYPE = 'Boolean', - NUMBER_TYPE = 'Number', - STRING_TYPE = 'String', - OBJECT_TYPE = 'Object', - FUNCTION_CLASS = '[object Function]', - BOOLEAN_CLASS = '[object Boolean]', - NUMBER_CLASS = '[object Number]', - STRING_CLASS = '[object String]', - ARRAY_CLASS = '[object Array]', - DATE_CLASS = '[object Date]', - NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && - typeof JSON.stringify === 'function' && - JSON.stringify(0) === '0' && - typeof JSON.stringify(Prototype.K) === 'undefined'; - - function Type(o) { - switch(o) { - case null: return NULL_TYPE; - case (void 0): return UNDEFINED_TYPE; - } - var type = typeof o; - switch(type) { - case 'boolean': return BOOLEAN_TYPE; - case 'number': return NUMBER_TYPE; - case 'string': return STRING_TYPE; - } - return OBJECT_TYPE; - } - - function extend(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; - } - - function inspect(object) { - try { - if (isUndefined(object)) return 'undefined'; - if (object === null) return 'null'; - return object.inspect ? object.inspect() : String(object); - } catch (e) { - if (e instanceof RangeError) return '...'; - throw e; - } - } - - function toJSON(value) { - return Str('', { '': value }, []); - } - - function Str(key, holder, stack) { - var value = holder[key], - type = typeof value; - - if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - - var _class = _toString.call(value); - - switch (_class) { - case NUMBER_CLASS: - case BOOLEAN_CLASS: - case STRING_CLASS: - value = value.valueOf(); - } - - switch (value) { - case null: return 'null'; - case true: return 'true'; - case false: return 'false'; - } - - type = typeof value; - switch (type) { - case 'string': - return value.inspect(true); - case 'number': - return isFinite(value) ? String(value) : 'null'; - case 'object': - - for (var i = 0, length = stack.length; i < length; i++) { - if (stack[i] === value) { throw new TypeError(); } - } - stack.push(value); - - var partial = []; - if (_class === ARRAY_CLASS) { - for (var i = 0, length = value.length; i < length; i++) { - var str = Str(i, value, stack); - partial.push(typeof str === 'undefined' ? 'null' : str); - } - partial = '[' + partial.join(',') + ']'; - } else { - var keys = Object.keys(value); - for (var i = 0, length = keys.length; i < length; i++) { - var key = keys[i], str = Str(key, value, stack); - if (typeof str !== "undefined") { - partial.push(key.inspect(true)+ ':' + str); - } - } - partial = '{' + partial.join(',') + '}'; - } - stack.pop(); - return partial; - } - } - - function stringify(object) { - return JSON.stringify(object); - } - - function toQueryString(object) { - return $H(object).toQueryString(); - } - - function toHTML(object) { - return object && object.toHTML ? object.toHTML() : String.interpret(object); - } - - function keys(object) { - if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } - var results = []; - for (var property in object) { - if (object.hasOwnProperty(property)) { - results.push(property); - } - } - return results; - } - - function values(object) { - var results = []; - for (var property in object) - results.push(object[property]); - return results; - } - - function clone(object) { - return extend({ }, object); - } - - function isElement(object) { - return !!(object && object.nodeType == 1); - } - - function isArray(object) { - return _toString.call(object) === ARRAY_CLASS; - } - - var hasNativeIsArray = (typeof Array.isArray == 'function') - && Array.isArray([]) && !Array.isArray({}); - - if (hasNativeIsArray) { - isArray = Array.isArray; - } - - function isHash(object) { - return object instanceof Hash; - } - - function isFunction(object) { - return _toString.call(object) === FUNCTION_CLASS; - } - - function isString(object) { - return _toString.call(object) === STRING_CLASS; - } - - function isNumber(object) { - return _toString.call(object) === NUMBER_CLASS; - } - - function isDate(object) { - return _toString.call(object) === DATE_CLASS; - } - - function isUndefined(object) { - return typeof object === "undefined"; - } - - extend(Object, { - extend: extend, - inspect: inspect, - toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, - toQueryString: toQueryString, - toHTML: toHTML, - keys: Object.keys || keys, - values: values, - clone: clone, - isElement: isElement, - isArray: isArray, - isHash: isHash, - isFunction: isFunction, - isString: isString, - isNumber: isNumber, - isDate: isDate, - isUndefined: isUndefined - }); -})(); -Object.extend(Function.prototype, (function() { - var slice = Array.prototype.slice; - - function update(array, args) { - var arrayLength = array.length, length = args.length; - while (length--) array[arrayLength + length] = args[length]; - return array; - } - - function merge(array, args) { - array = slice.call(array, 0); - return update(array, args); - } - - function argumentNames() { - var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] - .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') - .replace(/\s+/g, '').split(','); - return names.length == 1 && !names[0] ? [] : names; - } - - function bind(context) { - if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = slice.call(arguments, 1); - return function() { - var a = merge(args, arguments); - return __method.apply(context, a); - } - } - - function bindAsEventListener(context) { - var __method = this, args = slice.call(arguments, 1); - return function(event) { - var a = update([event || window.event], args); - return __method.apply(context, a); - } - } - - function curry() { - if (!arguments.length) return this; - var __method = this, args = slice.call(arguments, 0); - return function() { - var a = merge(args, arguments); - return __method.apply(this, a); - } - } - - function delay(timeout) { - var __method = this, args = slice.call(arguments, 1); - timeout = timeout * 1000; - return window.setTimeout(function() { - return __method.apply(__method, args); - }, timeout); - } - - function defer() { - var args = update([0.01], arguments); - return this.delay.apply(this, args); - } - - function wrap(wrapper) { - var __method = this; - return function() { - var a = update([__method.bind(this)], arguments); - return wrapper.apply(this, a); - } - } - - function methodize() { - if (this._methodized) return this._methodized; - var __method = this; - return this._methodized = function() { - var a = update([this], arguments); - return __method.apply(null, a); - }; - } - - return { - argumentNames: argumentNames, - bind: bind, - bindAsEventListener: bindAsEventListener, - curry: curry, - delay: delay, - defer: defer, - wrap: wrap, - methodize: methodize - } -})()); - - - -(function(proto) { - - - function toISOString() { - return this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z'; - } - - - function toJSON() { - return this.toISOString(); - } - - if (!proto.toISOString) proto.toISOString = toISOString; - if (!proto.toJSON) proto.toJSON = toJSON; - -})(Date.prototype); - - -RegExp.prototype.match = RegExp.prototype.test; - -RegExp.escape = function(str) { - return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); -}; -var PeriodicalExecuter = Class.create({ - initialize: function(callback, frequency) { - this.callback = callback; - this.frequency = frequency; - this.currentlyExecuting = false; - - this.registerCallback(); - }, - - registerCallback: function() { - this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); - }, - - execute: function() { - this.callback(this); - }, - - stop: function() { - if (!this.timer) return; - clearInterval(this.timer); - this.timer = null; - }, - - onTimerEvent: function() { - if (!this.currentlyExecuting) { - try { - this.currentlyExecuting = true; - this.execute(); - this.currentlyExecuting = false; - } catch(e) { - this.currentlyExecuting = false; - throw e; - } - } - } -}); -Object.extend(String, { - interpret: function(value) { - return value == null ? '' : String(value); - }, - specialChar: { - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '\\': '\\\\' - } -}); - -Object.extend(String.prototype, (function() { - var NATIVE_JSON_PARSE_SUPPORT = window.JSON && - typeof JSON.parse === 'function' && - JSON.parse('{"test": true}').test; - - function prepareReplacement(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; - } - - function gsub(pattern, replacement) { - var result = '', source = this, match; - replacement = prepareReplacement(replacement); - - if (Object.isString(pattern)) - pattern = RegExp.escape(pattern); - - if (!(pattern.length || pattern.source)) { - replacement = replacement(''); - return replacement + source.split('').join(replacement) + replacement; - } - - while (source.length > 0) { - if (match = source.match(pattern)) { - result += source.slice(0, match.index); - result += String.interpret(replacement(match)); - source = source.slice(match.index + match[0].length); - } else { - result += source, source = ''; - } - } - return result; - } - - function sub(pattern, replacement, count) { - replacement = prepareReplacement(replacement); - count = Object.isUndefined(count) ? 1 : count; - - return this.gsub(pattern, function(match) { - if (--count < 0) return match[0]; - return replacement(match); - }); - } - - function scan(pattern, iterator) { - this.gsub(pattern, iterator); - return String(this); - } - - function truncate(length, truncation) { - length = length || 30; - truncation = Object.isUndefined(truncation) ? '...' : truncation; - return this.length > length ? - this.slice(0, length - truncation.length) + truncation : String(this); - } - - function strip() { - return this.replace(/^\s+/, '').replace(/\s+$/, ''); - } - - function stripTags() { - return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); - } - - function stripScripts() { - return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - } - - function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), - matchOne = new RegExp(Prototype.ScriptFragment, 'im'); - return (this.match(matchAll) || []).map(function(scriptTag) { - return (scriptTag.match(matchOne) || ['', ''])[1]; - }); - } - - function evalScripts() { - return this.extractScripts().map(function(script) { return eval(script) }); - } - - function escapeHTML() { - return this.replace(/&/g,'&').replace(//g,'>'); - } - - function unescapeHTML() { - return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); - } - - - function toQueryParams(separator) { - var match = this.strip().match(/([^?#]*)(#.*)?$/); - if (!match) return { }; - - return match[1].split(separator || '&').inject({ }, function(hash, pair) { - if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()), - value = pair.length > 1 ? pair.join('=') : pair[0]; - - if (value != undefined) value = decodeURIComponent(value); - - if (key in hash) { - if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; - hash[key].push(value); - } - else hash[key] = value; - } - return hash; - }); - } - - function toArray() { - return this.split(''); - } - - function succ() { - return this.slice(0, this.length - 1) + - String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - } - - function times(count) { - return count < 1 ? '' : new Array(count + 1).join(this); - } - - function camelize() { - return this.replace(/-+(.)?/g, function(match, chr) { - return chr ? chr.toUpperCase() : ''; - }); - } - - function capitalize() { - return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - } - - function underscore() { - return this.replace(/::/g, '/') - .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') - .replace(/([a-z\d])([A-Z])/g, '$1_$2') - .replace(/-/g, '_') - .toLowerCase(); - } - - function dasherize() { - return this.replace(/_/g, '-'); - } - - function inspect(useDoubleQuotes) { - var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { - if (character in String.specialChar) { - return String.specialChar[character]; - } - return '\\u00' + character.charCodeAt().toPaddedString(2, 16); - }); - if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; - return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - } - - function unfilterJSON(filter) { - return this.replace(filter || Prototype.JSONFilter, '$1'); - } - - function isJSON() { - var str = this; - if (str.blank()) return false; - str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); - str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); - str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); - return (/^[\],:{}\s]*$/).test(str); - } - - function evalJSON(sanitize) { - var json = this.unfilterJSON(), - cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - if (cx.test(json)) { - json = json.replace(cx, function (a) { - return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - try { - if (!sanitize || json.isJSON()) return eval('(' + json + ')'); - } catch (e) { } - throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - } - - function parseJSON() { - var json = this.unfilterJSON(); - return JSON.parse(json); - } - - function include(pattern) { - return this.indexOf(pattern) > -1; - } - - function startsWith(pattern) { - return this.lastIndexOf(pattern, 0) === 0; - } - - function endsWith(pattern) { - var d = this.length - pattern.length; - return d >= 0 && this.indexOf(pattern, d) === d; - } - - function empty() { - return this == ''; - } - - function blank() { - return /^\s*$/.test(this); - } - - function interpolate(object, pattern) { - return new Template(this, pattern).evaluate(object); - } - - return { - gsub: gsub, - sub: sub, - scan: scan, - truncate: truncate, - strip: String.prototype.trim || strip, - stripTags: stripTags, - stripScripts: stripScripts, - extractScripts: extractScripts, - evalScripts: evalScripts, - escapeHTML: escapeHTML, - unescapeHTML: unescapeHTML, - toQueryParams: toQueryParams, - parseQuery: toQueryParams, - toArray: toArray, - succ: succ, - times: times, - camelize: camelize, - capitalize: capitalize, - underscore: underscore, - dasherize: dasherize, - inspect: inspect, - unfilterJSON: unfilterJSON, - isJSON: isJSON, - evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, - include: include, - startsWith: startsWith, - endsWith: endsWith, - empty: empty, - blank: blank, - interpolate: interpolate - }; -})()); - -var Template = Class.create({ - initialize: function(template, pattern) { - this.template = template.toString(); - this.pattern = pattern || Template.Pattern; - }, - - evaluate: function(object) { - if (object && Object.isFunction(object.toTemplateReplacements)) - object = object.toTemplateReplacements(); - - return this.template.gsub(this.pattern, function(match) { - if (object == null) return (match[1] + ''); - - var before = match[1] || ''; - if (before == '\\') return match[2]; - - var ctx = object, expr = match[3], - pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; - - match = pattern.exec(expr); - if (match == null) return before; - - while (match != null) { - var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; - ctx = ctx[comp]; - if (null == ctx || '' == match[3]) break; - expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); - match = pattern.exec(expr); - } - - return before + String.interpret(ctx); - }); - } -}); -Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; - -var $break = { }; - -var Enumerable = (function() { - function each(iterator, context) { - var index = 0; - try { - this._each(function(value) { - iterator.call(context, value, index++); - }); - } catch (e) { - if (e != $break) throw e; - } - return this; - } - - function eachSlice(number, iterator, context) { - var index = -number, slices = [], array = this.toArray(); - if (number < 1) return array; - while ((index += number) < array.length) - slices.push(array.slice(index, index+number)); - return slices.collect(iterator, context); - } - - function all(iterator, context) { - iterator = iterator || Prototype.K; - var result = true; - this.each(function(value, index) { - result = result && !!iterator.call(context, value, index); - if (!result) throw $break; - }); - return result; - } - - function any(iterator, context) { - iterator = iterator || Prototype.K; - var result = false; - this.each(function(value, index) { - if (result = !!iterator.call(context, value, index)) - throw $break; - }); - return result; - } - - function collect(iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - this.each(function(value, index) { - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function detect(iterator, context) { - var result; - this.each(function(value, index) { - if (iterator.call(context, value, index)) { - result = value; - throw $break; - } - }); - return result; - } - - function findAll(iterator, context) { - var results = []; - this.each(function(value, index) { - if (iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function grep(filter, iterator, context) { - iterator = iterator || Prototype.K; - var results = []; - - if (Object.isString(filter)) - filter = new RegExp(RegExp.escape(filter)); - - this.each(function(value, index) { - if (filter.match(value)) - results.push(iterator.call(context, value, index)); - }); - return results; - } - - function include(object) { - if (Object.isFunction(this.indexOf)) - if (this.indexOf(object) != -1) return true; - - var found = false; - this.each(function(value) { - if (value == object) { - found = true; - throw $break; - } - }); - return found; - } - - function inGroupsOf(number, fillWith) { - fillWith = Object.isUndefined(fillWith) ? null : fillWith; - return this.eachSlice(number, function(slice) { - while(slice.length < number) slice.push(fillWith); - return slice; - }); - } - - function inject(memo, iterator, context) { - this.each(function(value, index) { - memo = iterator.call(context, memo, value, index); - }); - return memo; - } - - function invoke(method) { - var args = $A(arguments).slice(1); - return this.map(function(value) { - return value[method].apply(value, args); - }); - } - - function max(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value >= result) - result = value; - }); - return result; - } - - function min(iterator, context) { - iterator = iterator || Prototype.K; - var result; - this.each(function(value, index) { - value = iterator.call(context, value, index); - if (result == null || value < result) - result = value; - }); - return result; - } - - function partition(iterator, context) { - iterator = iterator || Prototype.K; - var trues = [], falses = []; - this.each(function(value, index) { - (iterator.call(context, value, index) ? - trues : falses).push(value); - }); - return [trues, falses]; - } - - function pluck(property) { - var results = []; - this.each(function(value) { - results.push(value[property]); - }); - return results; - } - - function reject(iterator, context) { - var results = []; - this.each(function(value, index) { - if (!iterator.call(context, value, index)) - results.push(value); - }); - return results; - } - - function sortBy(iterator, context) { - return this.map(function(value, index) { - return { - value: value, - criteria: iterator.call(context, value, index) - }; - }).sort(function(left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }).pluck('value'); - } - - function toArray() { - return this.map(); - } - - function zip() { - var iterator = Prototype.K, args = $A(arguments); - if (Object.isFunction(args.last())) - iterator = args.pop(); - - var collections = [this].concat(args).map($A); - return this.map(function(value, index) { - return iterator(collections.pluck(index)); - }); - } - - function size() { - return this.toArray().length; - } - - function inspect() { - return '#'; - } - - - - - - - - - - return { - each: each, - eachSlice: eachSlice, - all: all, - every: all, - any: any, - some: any, - collect: collect, - map: collect, - detect: detect, - findAll: findAll, - select: findAll, - filter: findAll, - grep: grep, - include: include, - member: include, - inGroupsOf: inGroupsOf, - inject: inject, - invoke: invoke, - max: max, - min: min, - partition: partition, - pluck: pluck, - reject: reject, - sortBy: sortBy, - toArray: toArray, - entries: toArray, - zip: zip, - size: size, - inspect: inspect, - find: detect - }; -})(); - -function $A(iterable) { - if (!iterable) return []; - if ('toArray' in Object(iterable)) return iterable.toArray(); - var length = iterable.length || 0, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; -} - - -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} - -Array.from = $A; - - -(function() { - var arrayProto = Array.prototype, - slice = arrayProto.slice, - _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available - - function each(iterator, context) { - for (var i = 0, length = this.length >>> 0; i < length; i++) { - if (i in this) iterator.call(context, this[i], i, this); - } - } - if (!_each) _each = each; - - function clear() { - this.length = 0; - return this; - } - - function first() { - return this[0]; - } - - function last() { - return this[this.length - 1]; - } - - function compact() { - return this.select(function(value) { - return value != null; - }); - } - - function flatten() { - return this.inject([], function(array, value) { - if (Object.isArray(value)) - return array.concat(value.flatten()); - array.push(value); - return array; - }); - } - - function without() { - var values = slice.call(arguments, 0); - return this.select(function(value) { - return !values.include(value); - }); - } - - function reverse(inline) { - return (inline === false ? this.toArray() : this)._reverse(); - } - - function uniq(sorted) { - return this.inject([], function(array, value, index) { - if (0 == index || (sorted ? array.last() != value : !array.include(value))) - array.push(value); - return array; - }); - } - - function intersect(array) { - return this.uniq().findAll(function(item) { - return array.detect(function(value) { return item === value }); - }); - } - - - function clone() { - return slice.call(this, 0); - } - - function size() { - return this.length; - } - - function inspect() { - return '[' + this.map(Object.inspect).join(', ') + ']'; - } - - function indexOf(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; - } - - function lastIndexOf(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; - } - - function concat() { - var array = slice.call(this, 0), item; - for (var i = 0, length = arguments.length; i < length; i++) { - item = arguments[i]; - if (Object.isArray(item) && !('callee' in item)) { - for (var j = 0, arrayLength = item.length; j < arrayLength; j++) - array.push(item[j]); - } else { - array.push(item); - } - } - return array; - } - - Object.extend(arrayProto, Enumerable); - - if (!arrayProto._reverse) - arrayProto._reverse = arrayProto.reverse; - - Object.extend(arrayProto, { - _each: _each, - clear: clear, - first: first, - last: last, - compact: compact, - flatten: flatten, - without: without, - reverse: reverse, - uniq: uniq, - intersect: intersect, - clone: clone, - toArray: clone, - size: size, - inspect: inspect - }); - - var CONCAT_ARGUMENTS_BUGGY = (function() { - return [].concat(arguments)[0][0] !== 1; - })(1,2) - - if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; - - if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; - if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; -})(); -function $H(object) { - return new Hash(object); -}; - -var Hash = Class.create(Enumerable, (function() { - function initialize(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - } - - - function _each(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - } - - function set(key, value) { - return this._object[key] = value; - } - - function get(key) { - if (this._object[key] !== Object.prototype[key]) - return this._object[key]; - } - - function unset(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - } - - function toObject() { - return Object.clone(this._object); - } - - - - function keys() { - return this.pluck('key'); - } - - function values() { - return this.pluck('value'); - } - - function index(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - } - - function merge(object) { - return this.clone().update(object); - } - - function update(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - } - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); - } - - function toQueryString() { - return this.inject([], function(results, pair) { - var key = encodeURIComponent(pair.key), values = pair.value; - - if (values && typeof values == 'object') { - if (Object.isArray(values)) { - var queryValues = []; - for (var i = 0, len = values.length, value; i < len; i++) { - value = values[i]; - queryValues.push(toQueryPair(key, value)); - } - return results.concat(queryValues); - } - } else results.push(toQueryPair(key, values)); - return results; - }).join('&'); - } - - function inspect() { - return '#'; - } - - function clone() { - return new Hash(this); - } - - return { - initialize: initialize, - _each: _each, - set: set, - get: get, - unset: unset, - toObject: toObject, - toTemplateReplacements: toObject, - keys: keys, - values: values, - index: index, - merge: merge, - update: update, - toQueryString: toQueryString, - inspect: inspect, - toJSON: toObject, - clone: clone - }; -})()); - -Hash.from = $H; -Object.extend(Number.prototype, (function() { - function toColorPart() { - return this.toPaddedString(2, 16); - } - - function succ() { - return this + 1; - } - - function times(iterator, context) { - $R(0, this, true).each(iterator, context); - return this; - } - - function toPaddedString(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - } - - function abs() { - return Math.abs(this); - } - - function round() { - return Math.round(this); - } - - function ceil() { - return Math.ceil(this); - } - - function floor() { - return Math.floor(this); - } - - return { - toColorPart: toColorPart, - succ: succ, - times: times, - toPaddedString: toPaddedString, - abs: abs, - round: round, - ceil: ceil, - floor: floor - }; -})()); - -function $R(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -} - -var ObjectRange = Class.create(Enumerable, (function() { - function initialize(start, end, exclusive) { - this.start = start; - this.end = end; - this.exclusive = exclusive; - } - - function _each(iterator) { - var value = this.start; - while (this.include(value)) { - iterator(value); - value = value.succ(); - } - } - - function include(value) { - if (value < this.start) - return false; - if (this.exclusive) - return value < this.end; - return value <= this.end; - } - - return { - initialize: initialize, - _each: _each, - include: include - }; -})()); - - - -var Ajax = { - getTransport: function() { - return Try.these( - function() {return new XMLHttpRequest()}, - function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')} - ) || false; - }, - - activeRequestCount: 0 -}; - -Ajax.Responders = { - responders: [], - - _each: function(iterator) { - this.responders._each(iterator); - }, - - register: function(responder) { - if (!this.include(responder)) - this.responders.push(responder); - }, - - unregister: function(responder) { - this.responders = this.responders.without(responder); - }, - - dispatch: function(callback, request, transport, json) { - this.each(function(responder) { - if (Object.isFunction(responder[callback])) { - try { - responder[callback].apply(responder, [request, transport, json]); - } catch (e) { } - } - }); - } -}; - -Object.extend(Ajax.Responders, Enumerable); - -Ajax.Responders.register({ - onCreate: function() { Ajax.activeRequestCount++ }, - onComplete: function() { Ajax.activeRequestCount-- } -}); -Ajax.Base = Class.create({ - initialize: function(options) { - this.options = { - method: 'post', - asynchronous: true, - contentType: 'application/x-www-form-urlencoded', - encoding: 'UTF-8', - parameters: '', - evalJSON: true, - evalJS: true - }; - Object.extend(this.options, options || { }); - - this.options.method = this.options.method.toLowerCase(); - - if (Object.isHash(this.options.parameters)) - this.options.parameters = this.options.parameters.toObject(); - } -}); -Ajax.Request = Class.create(Ajax.Base, { - _complete: false, - - initialize: function($super, url, options) { - $super(options); - this.transport = Ajax.getTransport(); - this.request(url); - }, - - request: function(url) { - this.url = url; - this.method = this.options.method; - var params = Object.isString(this.options.parameters) ? - this.options.parameters : - Object.toQueryString(this.options.parameters); - - if (!['get', 'post'].include(this.method)) { - params += (params ? '&' : '') + "_method=" + this.method; - this.method = 'post'; - } - - if (params && this.method === 'get') { - this.url += (this.url.include('?') ? '&' : '?') + params; - } - - this.parameters = params.toQueryParams(); - - try { - var response = new Ajax.Response(this); - if (this.options.onCreate) this.options.onCreate(response); - Ajax.Responders.dispatch('onCreate', this, response); - - this.transport.open(this.method.toUpperCase(), this.url, - this.options.asynchronous); - - if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); - - this.transport.onreadystatechange = this.onStateChange.bind(this); - this.setRequestHeaders(); - - this.body = this.method == 'post' ? (this.options.postBody || params) : null; - this.transport.send(this.body); - - /* Force Firefox to handle ready state 4 for synchronous requests */ - if (!this.options.asynchronous && this.transport.overrideMimeType) - this.onStateChange(); - - } - catch (e) { - this.dispatchException(e); - } - }, - - onStateChange: function() { - var readyState = this.transport.readyState; - if (readyState > 1 && !((readyState == 4) && this._complete)) - this.respondToReadyState(this.transport.readyState); - }, - - setRequestHeaders: function() { - var headers = { - 'X-Requested-With': 'XMLHttpRequest', - 'X-Prototype-Version': Prototype.Version, - 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' - }; - - if (this.method == 'post') { - headers['Content-type'] = this.options.contentType + - (this.options.encoding ? '; charset=' + this.options.encoding : ''); - - /* Force "Connection: close" for older Mozilla browsers to work - * around a bug where XMLHttpRequest sends an incorrect - * Content-length header. See Mozilla Bugzilla #246651. - */ - if (this.transport.overrideMimeType && - (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) - headers['Connection'] = 'close'; - } - - if (typeof this.options.requestHeaders == 'object') { - var extras = this.options.requestHeaders; - - if (Object.isFunction(extras.push)) - for (var i = 0, length = extras.length; i < length; i += 2) - headers[extras[i]] = extras[i+1]; - else - $H(extras).each(function(pair) { headers[pair.key] = pair.value }); - } - - for (var name in headers) - this.transport.setRequestHeader(name, headers[name]); - }, - - success: function() { - var status = this.getStatus(); - return !status || (status >= 200 && status < 300) || status == 304; - }, - - getStatus: function() { - try { - if (this.transport.status === 1223) return 204; - return this.transport.status || 0; - } catch (e) { return 0 } - }, - - respondToReadyState: function(readyState) { - var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); - - if (state == 'Complete') { - try { - this._complete = true; - (this.options['on' + response.status] - || this.options['on' + (this.success() ? 'Success' : 'Failure')] - || Prototype.emptyFunction)(response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - var contentType = response.getHeader('Content-type'); - if (this.options.evalJS == 'force' - || (this.options.evalJS && this.isSameOrigin() && contentType - && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) - this.evalResponse(); - } - - try { - (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); - Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); - } catch (e) { - this.dispatchException(e); - } - - if (state == 'Complete') { - this.transport.onreadystatechange = Prototype.emptyFunction; - } - }, - - isSameOrigin: function() { - var m = this.url.match(/^\s*https?:\/\/[^\/]*/); - return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ - protocol: location.protocol, - domain: document.domain, - port: location.port ? ':' + location.port : '' - })); - }, - - getHeader: function(name) { - try { - return this.transport.getResponseHeader(name) || null; - } catch (e) { return null; } - }, - - evalResponse: function() { - try { - return eval((this.transport.responseText || '').unfilterJSON()); - } catch (e) { - this.dispatchException(e); - } - }, - - dispatchException: function(exception) { - (this.options.onException || Prototype.emptyFunction)(this, exception); - Ajax.Responders.dispatch('onException', this, exception); - } -}); - -Ajax.Request.Events = - ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; - - - - - - - - -Ajax.Response = Class.create({ - initialize: function(request){ - this.request = request; - var transport = this.transport = request.transport, - readyState = this.readyState = transport.readyState; - - if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { - this.status = this.getStatus(); - this.statusText = this.getStatusText(); - this.responseText = String.interpret(transport.responseText); - this.headerJSON = this._getHeaderJSON(); - } - - if (readyState == 4) { - var xml = transport.responseXML; - this.responseXML = Object.isUndefined(xml) ? null : xml; - this.responseJSON = this._getResponseJSON(); - } - }, - - status: 0, - - statusText: '', - - getStatus: Ajax.Request.prototype.getStatus, - - getStatusText: function() { - try { - return this.transport.statusText || ''; - } catch (e) { return '' } - }, - - getHeader: Ajax.Request.prototype.getHeader, - - getAllHeaders: function() { - try { - return this.getAllResponseHeaders(); - } catch (e) { return null } - }, - - getResponseHeader: function(name) { - return this.transport.getResponseHeader(name); - }, - - getAllResponseHeaders: function() { - return this.transport.getAllResponseHeaders(); - }, - - _getHeaderJSON: function() { - var json = this.getHeader('X-JSON'); - if (!json) return null; - json = decodeURIComponent(escape(json)); - try { - return json.evalJSON(this.request.options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - }, - - _getResponseJSON: function() { - var options = this.request.options; - if (!options.evalJSON || (options.evalJSON != 'force' && - !(this.getHeader('Content-type') || '').include('application/json')) || - this.responseText.blank()) - return null; - try { - return this.responseText.evalJSON(options.sanitizeJSON || - !this.request.isSameOrigin()); - } catch (e) { - this.request.dispatchException(e); - } - } -}); - -Ajax.Updater = Class.create(Ajax.Request, { - initialize: function($super, container, url, options) { - this.container = { - success: (container.success || container), - failure: (container.failure || (container.success ? null : container)) - }; - - options = Object.clone(options); - var onComplete = options.onComplete; - options.onComplete = (function(response, json) { - this.updateContent(response.responseText); - if (Object.isFunction(onComplete)) onComplete(response, json); - }).bind(this); - - $super(url, options); - }, - - updateContent: function(responseText) { - var receiver = this.container[this.success() ? 'success' : 'failure'], - options = this.options; - - if (!options.evalScripts) responseText = responseText.stripScripts(); - - if (receiver = $(receiver)) { - if (options.insertion) { - if (Object.isString(options.insertion)) { - var insertion = { }; insertion[options.insertion] = responseText; - receiver.insert(insertion); - } - else options.insertion(receiver, responseText); - } - else receiver.update(responseText); - } - } -}); - -Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { - initialize: function($super, container, url, options) { - $super(options); - this.onComplete = this.options.onComplete; - - this.frequency = (this.options.frequency || 2); - this.decay = (this.options.decay || 1); - - this.updater = { }; - this.container = container; - this.url = url; - - this.start(); - }, - - start: function() { - this.options.onComplete = this.updateComplete.bind(this); - this.onTimerEvent(); - }, - - stop: function() { - this.updater.options.onComplete = undefined; - clearTimeout(this.timer); - (this.onComplete || Prototype.emptyFunction).apply(this, arguments); - }, - - updateComplete: function(response) { - if (this.options.decay) { - this.decay = (response.responseText == this.lastText ? - this.decay * this.options.decay : 1); - - this.lastText = response.responseText; - } - this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); - }, - - onTimerEvent: function() { - this.updater = new Ajax.Updater(this.container, this.url, this.options); - } -}); - - -function $(element) { - if (arguments.length > 1) { - for (var i = 0, elements = [], length = arguments.length; i < length; i++) - elements.push($(arguments[i])); - return elements; - } - if (Object.isString(element)) - element = document.getElementById(element); - return Element.extend(element); -} - -if (Prototype.BrowserFeatures.XPath) { - document._getElementsByXPath = function(expression, parentElement) { - var results = []; - var query = document.evaluate(expression, $(parentElement) || document, - null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - for (var i = 0, length = query.snapshotLength; i < length; i++) - results.push(Element.extend(query.snapshotItem(i))); - return results; - }; -} - -/*--------------------------------------------------------------------------*/ - -if (!Node) var Node = { }; - -if (!Node.ELEMENT_NODE) { - Object.extend(Node, { - ELEMENT_NODE: 1, - ATTRIBUTE_NODE: 2, - TEXT_NODE: 3, - CDATA_SECTION_NODE: 4, - ENTITY_REFERENCE_NODE: 5, - ENTITY_NODE: 6, - PROCESSING_INSTRUCTION_NODE: 7, - COMMENT_NODE: 8, - DOCUMENT_NODE: 9, - DOCUMENT_TYPE_NODE: 10, - DOCUMENT_FRAGMENT_NODE: 11, - NOTATION_NODE: 12 - }); -} - - - -(function(global) { - function shouldUseCache(tagName, attributes) { - if (tagName === 'select') return false; - if ('type' in attributes) return false; - return true; - } - - var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ - try { - var el = document.createElement(''); - return el.tagName.toLowerCase() === 'input' && el.name === 'x'; - } - catch(err) { - return false; - } - })(); - - var element = global.Element; - - global.Element = function(tagName, attributes) { - attributes = attributes || { }; - tagName = tagName.toLowerCase(); - var cache = Element.cache; - - if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { - tagName = '<' + tagName + ' name="' + attributes.name + '">'; - delete attributes.name; - return Element.writeAttribute(document.createElement(tagName), attributes); - } - - if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); - - var node = shouldUseCache(tagName, attributes) ? - cache[tagName].cloneNode(false) : document.createElement(tagName); - - return Element.writeAttribute(node, attributes); - }; - - Object.extend(global.Element, element || { }); - if (element) global.Element.prototype = element.prototype; - -})(this); - -Element.idCounter = 1; -Element.cache = { }; - -Element._purgeElement = function(element) { - var uid = element._prototypeUID; - if (uid) { - Element.stopObserving(element); - element._prototypeUID = void 0; - delete Element.Storage[uid]; - } -} - -Element.Methods = { - visible: function(element) { - return $(element).style.display != 'none'; - }, - - toggle: function(element) { - element = $(element); - Element[Element.visible(element) ? 'hide' : 'show'](element); - return element; - }, - - hide: function(element) { - element = $(element); - element.style.display = 'none'; - return element; - }, - - show: function(element) { - element = $(element); - element.style.display = ''; - return element; - }, - - remove: function(element) { - element = $(element); - element.parentNode.removeChild(element); - return element; - }, - - update: (function(){ - - var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ - var el = document.createElement("select"), - isBuggy = true; - el.innerHTML = ""; - if (el.options && el.options[0]) { - isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; - } - el = null; - return isBuggy; - })(); - - var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ - try { - var el = document.createElement("table"); - if (el && el.tBodies) { - el.innerHTML = "test"; - var isBuggy = typeof el.tBodies[0] == "undefined"; - el = null; - return isBuggy; - } - } catch (e) { - return true; - } - })(); - - var LINK_ELEMENT_INNERHTML_BUGGY = (function() { - try { - var el = document.createElement('div'); - el.innerHTML = ""; - var isBuggy = (el.childNodes.length === 0); - el = null; - return isBuggy; - } catch(e) { - return true; - } - })(); - - var ANY_INNERHTML_BUGGY = SELECT_ELEMENT_INNERHTML_BUGGY || - TABLE_ELEMENT_INNERHTML_BUGGY || LINK_ELEMENT_INNERHTML_BUGGY; - - var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { - var s = document.createElement("script"), - isBuggy = false; - try { - s.appendChild(document.createTextNode("")); - isBuggy = !s.firstChild || - s.firstChild && s.firstChild.nodeType !== 3; - } catch (e) { - isBuggy = true; - } - s = null; - return isBuggy; - })(); - - - function update(element, content) { - element = $(element); - var purgeElement = Element._purgeElement; - - var descendants = element.getElementsByTagName('*'), - i = descendants.length; - while (i--) purgeElement(descendants[i]); - - if (content && content.toElement) - content = content.toElement(); - - if (Object.isElement(content)) - return element.update().insert(content); - - content = Object.toHTML(content); - - var tagName = element.tagName.toUpperCase(); - - if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { - element.text = content; - return element; - } - - if (ANY_INNERHTML_BUGGY) { - if (tagName in Element._insertionTranslations.tags) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { - element.appendChild(node) - }); - } else if (LINK_ELEMENT_INNERHTML_BUGGY && Object.isString(content) && content.indexOf(' -1) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } - var nodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts(), true); - nodes.each(function(node) { element.appendChild(node) }); - } - else { - element.innerHTML = content.stripScripts(); - } - } - else { - element.innerHTML = content.stripScripts(); - } - - content.evalScripts.bind(content).defer(); - return element; - } - - return update; - })(), - - replace: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - else if (!Object.isElement(content)) { - content = Object.toHTML(content); - var range = element.ownerDocument.createRange(); - range.selectNode(element); - content.evalScripts.bind(content).defer(); - content = range.createContextualFragment(content.stripScripts()); - } - element.parentNode.replaceChild(content, element); - return element; - }, - - insert: function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = {bottom:insertions}; - - var content, insert, tagName, childNodes; - - for (var position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - insert = Element._insertionTranslations[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - insert(element, content); - continue; - } - - content = Object.toHTML(content); - - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - - if (position == 'top' || position == 'after') childNodes.reverse(); - childNodes.each(insert.curry(element)); - - content.evalScripts.bind(content).defer(); - } - - return element; - }, - - wrap: function(element, wrapper, attributes) { - element = $(element); - if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || { }); - else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); - else wrapper = new Element('div', wrapper); - if (element.parentNode) - element.parentNode.replaceChild(wrapper, element); - wrapper.appendChild(element); - return wrapper; - }, - - inspect: function(element) { - element = $(element); - var result = '<' + element.tagName.toLowerCase(); - $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), - attribute = pair.last(), - value = (element[property] || '').toString(); - if (value) result += ' ' + attribute + '=' + value.inspect(true); - }); - return result + '>'; - }, - - recursivelyCollect: function(element, property, maximumLength) { - element = $(element); - maximumLength = maximumLength || -1; - var elements = []; - - while (element = element[property]) { - if (element.nodeType == 1) - elements.push(Element.extend(element)); - if (elements.length == maximumLength) - break; - } - - return elements; - }, - - ancestors: function(element) { - return Element.recursivelyCollect(element, 'parentNode'); - }, - - descendants: function(element) { - return Element.select(element, "*"); - }, - - firstDescendant: function(element) { - element = $(element).firstChild; - while (element && element.nodeType != 1) element = element.nextSibling; - return $(element); - }, - - immediateDescendants: function(element) { - var results = [], child = $(element).firstChild; - while (child) { - if (child.nodeType === 1) { - results.push(Element.extend(child)); - } - child = child.nextSibling; - } - return results; - }, - - previousSiblings: function(element, maximumLength) { - return Element.recursivelyCollect(element, 'previousSibling'); - }, - - nextSiblings: function(element) { - return Element.recursivelyCollect(element, 'nextSibling'); - }, - - siblings: function(element) { - element = $(element); - return Element.previousSiblings(element).reverse() - .concat(Element.nextSiblings(element)); - }, - - match: function(element, selector) { - element = $(element); - if (Object.isString(selector)) - return Prototype.Selector.match(element, selector); - return selector.match(element); - }, - - up: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return $(element.parentNode); - var ancestors = Element.ancestors(element); - return Object.isNumber(expression) ? ancestors[expression] : - Prototype.Selector.find(ancestors, expression, index); - }, - - down: function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return Element.firstDescendant(element); - return Object.isNumber(expression) ? Element.descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - }, - - previous: function(element, expression, index) { - element = $(element); - if (Object.isNumber(expression)) index = expression, expression = false; - if (!Object.isNumber(index)) index = 0; - - if (expression) { - return Prototype.Selector.find(element.previousSiblings(), expression, index); - } else { - return element.recursivelyCollect("previousSibling", index + 1)[index]; - } - }, - - next: function(element, expression, index) { - element = $(element); - if (Object.isNumber(expression)) index = expression, expression = false; - if (!Object.isNumber(index)) index = 0; - - if (expression) { - return Prototype.Selector.find(element.nextSiblings(), expression, index); - } else { - var maximumLength = Object.isNumber(index) ? index + 1 : 1; - return element.recursivelyCollect("nextSibling", index + 1)[index]; - } - }, - - - select: function(element) { - element = $(element); - var expressions = Array.prototype.slice.call(arguments, 1).join(', '); - return Prototype.Selector.select(expressions, element); - }, - - adjacent: function(element) { - element = $(element); - var expressions = Array.prototype.slice.call(arguments, 1).join(', '); - return Prototype.Selector.select(expressions, element.parentNode).without(element); - }, - - identify: function(element) { - element = $(element); - var id = Element.readAttribute(element, 'id'); - if (id) return id; - do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); - Element.writeAttribute(element, 'id', id); - return id; - }, - - readAttribute: function(element, name) { - element = $(element); - if (Prototype.Browser.IE) { - var t = Element._attributeTranslations.read; - if (t.values[name]) return t.values[name](element, name); - if (t.names[name]) name = t.names[name]; - if (name.include(':')) { - return (!element.attributes || !element.attributes[name]) ? null : - element.attributes[name].value; - } - } - return element.getAttribute(name); - }, - - writeAttribute: function(element, name, value) { - element = $(element); - var attributes = { }, t = Element._attributeTranslations.write; - - if (typeof name == 'object') attributes = name; - else attributes[name] = Object.isUndefined(value) ? true : value; - - for (var attr in attributes) { - name = t.names[attr] || attr; - value = attributes[attr]; - if (t.values[attr]) name = t.values[attr](element, value); - if (value === false || value === null) - element.removeAttribute(name); - else if (value === true) - element.setAttribute(name, name); - else element.setAttribute(name, value); - } - return element; - }, - - getHeight: function(element) { - return Element.getDimensions(element).height; - }, - - getWidth: function(element) { - return Element.getDimensions(element).width; - }, - - classNames: function(element) { - return new Element.ClassNames(element); - }, - - hasClassName: function(element, className) { - if (!(element = $(element))) return; - var elementClassName = element.className; - return (elementClassName.length > 0 && (elementClassName == className || - new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); - }, - - addClassName: function(element, className) { - if (!(element = $(element))) return; - if (!Element.hasClassName(element, className)) - element.className += (element.className ? ' ' : '') + className; - return element; - }, - - removeClassName: function(element, className) { - if (!(element = $(element))) return; - element.className = element.className.replace( - new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); - return element; - }, - - toggleClassName: function(element, className) { - if (!(element = $(element))) return; - return Element[Element.hasClassName(element, className) ? - 'removeClassName' : 'addClassName'](element, className); - }, - - cleanWhitespace: function(element) { - element = $(element); - var node = element.firstChild; - while (node) { - var nextNode = node.nextSibling; - if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) - element.removeChild(node); - node = nextNode; - } - return element; - }, - - empty: function(element) { - return $(element).innerHTML.blank(); - }, - - descendantOf: function(element, ancestor) { - element = $(element), ancestor = $(ancestor); - - if (element.compareDocumentPosition) - return (element.compareDocumentPosition(ancestor) & 8) === 8; - - if (ancestor.contains) - return ancestor.contains(element) && ancestor !== element; - - while (element = element.parentNode) - if (element == ancestor) return true; - - return false; - }, - - scrollTo: function(element) { - element = $(element); - var pos = Element.cumulativeOffset(element); - window.scrollTo(pos[0], pos[1]); - return element; - }, - - getStyle: function(element, style) { - element = $(element); - style = style == 'float' ? 'cssFloat' : style.camelize(); - var value = element.style[style]; - if (!value || value == 'auto') { - var css = document.defaultView.getComputedStyle(element, null); - value = css ? css[style] : null; - } - if (style == 'opacity') return value ? parseFloat(value) : 1.0; - return value == 'auto' ? null : value; - }, - - getOpacity: function(element) { - return $(element).getStyle('opacity'); - }, - - setStyle: function(element, styles) { - element = $(element); - var elementStyle = element.style, match; - if (Object.isString(styles)) { - element.style.cssText += ';' + styles; - return styles.include('opacity') ? - element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; - } - for (var property in styles) - if (property == 'opacity') element.setOpacity(styles[property]); - else - elementStyle[(property == 'float' || property == 'cssFloat') ? - (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : - property] = styles[property]; - - return element; - }, - - setOpacity: function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - return element; - }, - - makePositioned: function(element) { - element = $(element); - var pos = Element.getStyle(element, 'position'); - if (pos == 'static' || !pos) { - element._madePositioned = true; - element.style.position = 'relative'; - if (Prototype.Browser.Opera) { - element.style.top = 0; - element.style.left = 0; - } - } - return element; - }, - - undoPositioned: function(element) { - element = $(element); - if (element._madePositioned) { - element._madePositioned = undefined; - element.style.position = - element.style.top = - element.style.left = - element.style.bottom = - element.style.right = ''; - } - return element; - }, - - makeClipping: function(element) { - element = $(element); - if (element._overflow) return element; - element._overflow = Element.getStyle(element, 'overflow') || 'auto'; - if (element._overflow !== 'hidden') - element.style.overflow = 'hidden'; - return element; - }, - - undoClipping: function(element) { - element = $(element); - if (!element._overflow) return element; - element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; - element._overflow = null; - return element; - }, - - clonePosition: function(element, source) { - var options = Object.extend({ - setLeft: true, - setTop: true, - setWidth: true, - setHeight: true, - offsetTop: 0, - offsetLeft: 0 - }, arguments[2] || { }); - - source = $(source); - var p = Element.viewportOffset(source), delta = [0, 0], parent = null; - - element = $(element); - - if (Element.getStyle(element, 'position') == 'absolute') { - parent = Element.getOffsetParent(element); - delta = Element.viewportOffset(parent); - } - - if (parent == document.body) { - delta[0] -= document.body.offsetLeft; - delta[1] -= document.body.offsetTop; - } - - if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; - if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; - if (options.setWidth) element.style.width = source.offsetWidth + 'px'; - if (options.setHeight) element.style.height = source.offsetHeight + 'px'; - return element; - } -}; - -Object.extend(Element.Methods, { - getElementsBySelector: Element.Methods.select, - - childElements: Element.Methods.immediateDescendants -}); - -Element._attributeTranslations = { - write: { - names: { - className: 'class', - htmlFor: 'for' - }, - values: { } - } -}; - -if (Prototype.Browser.Opera) { - Element.Methods.getStyle = Element.Methods.getStyle.wrap( - function(proceed, element, style) { - switch (style) { - case 'height': case 'width': - if (!Element.visible(element)) return null; - - var dim = parseInt(proceed(element, style), 10); - - if (dim !== element['offset' + style.capitalize()]) - return dim + 'px'; - - var properties; - if (style === 'height') { - properties = ['border-top-width', 'padding-top', - 'padding-bottom', 'border-bottom-width']; - } - else { - properties = ['border-left-width', 'padding-left', - 'padding-right', 'border-right-width']; - } - return properties.inject(dim, function(memo, property) { - var val = proceed(element, property); - return val === null ? memo : memo - parseInt(val, 10); - }) + 'px'; - default: return proceed(element, style); - } - } - ); - - Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( - function(proceed, element, attribute) { - if (attribute === 'title') return element.title; - return proceed(element, attribute); - } - ); -} - -else if (Prototype.Browser.IE) { - Element.Methods.getStyle = function(element, style) { - element = $(element); - style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); - var value = element.style[style]; - if (!value && element.currentStyle) value = element.currentStyle[style]; - - if (style == 'opacity') { - if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) - if (value[1]) return parseFloat(value[1]) / 100; - return 1.0; - } - - if (value == 'auto') { - if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) - return element['offset' + style.capitalize()] + 'px'; - return null; - } - return value; - }; - - Element.Methods.setOpacity = function(element, value) { - function stripAlpha(filter){ - return filter.replace(/alpha\([^\)]*\)/gi,''); - } - element = $(element); - var currentStyle = element.currentStyle; - if ((currentStyle && !currentStyle.hasLayout) || - (!currentStyle && element.style.zoom == 'normal')) - element.style.zoom = 1; - - var filter = element.getStyle('filter'), style = element.style; - if (value == 1 || value === '') { - (filter = stripAlpha(filter)) ? - style.filter = filter : style.removeAttribute('filter'); - return element; - } else if (value < 0.00001) value = 0; - style.filter = stripAlpha(filter) + - 'alpha(opacity=' + (value * 100) + ')'; - return element; - }; - - Element._attributeTranslations = (function(){ - - var classProp = 'className', - forProp = 'for', - el = document.createElement('div'); - - el.setAttribute(classProp, 'x'); - - if (el.className !== 'x') { - el.setAttribute('class', 'x'); - if (el.className === 'x') { - classProp = 'class'; - } - } - el = null; - - el = document.createElement('label'); - el.setAttribute(forProp, 'x'); - if (el.htmlFor !== 'x') { - el.setAttribute('htmlFor', 'x'); - if (el.htmlFor === 'x') { - forProp = 'htmlFor'; - } - } - el = null; - - return { - read: { - names: { - 'class': classProp, - 'className': classProp, - 'for': forProp, - 'htmlFor': forProp - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute); - }, - _getAttr2: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: (function(){ - - var el = document.createElement('div'), f; - el.onclick = Prototype.emptyFunction; - var value = el.getAttribute('onclick'); - - if (String(value).indexOf('{') > -1) { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - attribute = attribute.toString(); - attribute = attribute.split('{')[1]; - attribute = attribute.split('}')[0]; - return attribute.strip(); - }; - } - else if (value === '') { - f = function(element, attribute) { - attribute = element.getAttribute(attribute); - if (!attribute) return null; - return attribute.strip(); - }; - } - el = null; - return f; - })(), - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); - }, - title: function(element) { - return element.title; - } - } - } - } - })(); - - Element._attributeTranslations.write = { - names: Object.extend({ - cellpadding: 'cellPadding', - cellspacing: 'cellSpacing' - }, Element._attributeTranslations.read.names), - values: { - checked: function(element, value) { - element.checked = !!value; - }, - - style: function(element, value) { - element.style.cssText = value ? value : ''; - } - } - }; - - Element._attributeTranslations.has = {}; - - $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { - Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; - Element._attributeTranslations.has[attr.toLowerCase()] = attr; - }); - - (function(v) { - Object.extend(v, { - href: v._getAttr2, - src: v._getAttr2, - type: v._getAttr, - action: v._getAttrNode, - disabled: v._flag, - checked: v._flag, - readonly: v._flag, - multiple: v._flag, - onload: v._getEv, - onunload: v._getEv, - onclick: v._getEv, - ondblclick: v._getEv, - onmousedown: v._getEv, - onmouseup: v._getEv, - onmouseover: v._getEv, - onmousemove: v._getEv, - onmouseout: v._getEv, - onfocus: v._getEv, - onblur: v._getEv, - onkeypress: v._getEv, - onkeydown: v._getEv, - onkeyup: v._getEv, - onsubmit: v._getEv, - onreset: v._getEv, - onselect: v._getEv, - onchange: v._getEv - }); - })(Element._attributeTranslations.read.values); - - if (Prototype.BrowserFeatures.ElementExtensions) { - (function() { - function _descendants(element) { - var nodes = element.getElementsByTagName('*'), results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName !== "!") // Filter out comment nodes. - results.push(node); - return results; - } - - Element.Methods.down = function(element, expression, index) { - element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - return Object.isNumber(expression) ? _descendants(element)[expression] : - Element.select(element, expression)[index || 0]; - } - })(); - } - -} - -else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1) ? 0.999999 : - (value === '') ? '' : (value < 0.00001) ? 0 : value; - return element; - }; -} - -else if (Prototype.Browser.WebKit) { - Element.Methods.setOpacity = function(element, value) { - element = $(element); - element.style.opacity = (value == 1 || value === '') ? '' : - (value < 0.00001) ? 0 : value; - - if (value == 1) - if (element.tagName.toUpperCase() == 'IMG' && element.width) { - element.width++; element.width--; - } else try { - var n = document.createTextNode(' '); - element.appendChild(n); - element.removeChild(n); - } catch (e) { } - - return element; - }; -} - -if ('outerHTML' in document.documentElement) { - Element.Methods.replace = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - element.parentNode.replaceChild(content, element); - return element; - } - - content = Object.toHTML(content); - var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); - - if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(), - fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - parent.removeChild(element); - if (nextSibling) - fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); - else - fragments.each(function(node) { parent.appendChild(node) }); - } - else element.outerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -Element._returnOffset = function(l, t) { - var result = [l, t]; - result.left = l; - result.top = t; - return result; -}; - -Element._getContentFromAnonymousElement = function(tagName, html, force) { - var div = new Element('div'), - t = Element._insertionTranslations.tags[tagName]; - - var workaround = false; - if (t) workaround = true; - else if (force) { - workaround = true; - t = ['', '', 0]; - } - - if (workaround) { - div.innerHTML = ' ' + t[0] + html + t[1]; - div.removeChild(div.firstChild); - for (var i = t[2]; i--; ) { - div = div.firstChild; - } - } - else { - div.innerHTML = html; - } - return $A(div.childNodes); -}; - -Element._insertionTranslations = { - before: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - top: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - bottom: function(element, node) { - element.appendChild(node); - }, - after: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - tags: { - TABLE: ['', '
    ', 1], - TBODY: ['', '
    ', 2], - TR: ['', '
    ', 3], - TD: ['
    ', '
    ', 4], - SELECT: ['', 1] - } -}; - -(function() { - var tags = Element._insertionTranslations.tags; - Object.extend(tags, { - THEAD: tags.TBODY, - TFOOT: tags.TBODY, - TH: tags.TD - }); -})(); - -Element.Methods.Simulated = { - hasAttribute: function(element, attribute) { - attribute = Element._attributeTranslations.has[attribute] || attribute; - var node = $(element).getAttributeNode(attribute); - return !!(node && node.specified); - } -}; - -Element.Methods.ByTag = { }; - -Object.extend(Element, Element.Methods); - -(function(div) { - - if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { - window.HTMLElement = { }; - window.HTMLElement.prototype = div['__proto__']; - Prototype.BrowserFeatures.ElementExtensions = true; - } - - div = null; - -})(document.createElement('div')); - -Element.extend = (function() { - - function checkDeficiency(tagName) { - if (typeof window.Element != 'undefined') { - var proto = window.Element.prototype; - if (proto) { - var id = '_' + (Math.random()+'').slice(2), - el = document.createElement(tagName); - proto[id] = 'x'; - var isBuggy = (el[id] !== 'x'); - delete proto[id]; - el = null; - return isBuggy; - } - } - return false; - } - - function extendElementWith(element, methods) { - for (var property in methods) { - var value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } - } - - var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); - - if (Prototype.BrowserFeatures.SpecificElementExtensions) { - if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { - return function(element) { - if (element && typeof element._extendedByPrototype == 'undefined') { - var t = element.tagName; - if (t && (/^(?:object|applet|embed)$/i.test(t))) { - extendElementWith(element, Element.Methods); - extendElementWith(element, Element.Methods.Simulated); - extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); - } - } - return element; - } - } - return Prototype.K; - } - - var Methods = { }, ByTag = Element.Methods.ByTag; - - var extend = Object.extend(function(element) { - if (!element || typeof element._extendedByPrototype != 'undefined' || - element.nodeType != 1 || element == window) return element; - - var methods = Object.clone(Methods), - tagName = element.tagName.toUpperCase(); - - if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - - extendElementWith(element, methods); - - element._extendedByPrototype = Prototype.emptyFunction; - return element; - - }, { - refresh: function() { - if (!Prototype.BrowserFeatures.ElementExtensions) { - Object.extend(Methods, Element.Methods); - Object.extend(Methods, Element.Methods.Simulated); - } - } - }); - - extend.refresh(); - return extend; -})(); - -if (document.documentElement.hasAttribute) { - Element.hasAttribute = function(element, attribute) { - return element.hasAttribute(attribute); - }; -} -else { - Element.hasAttribute = Element.Methods.Simulated.hasAttribute; -} - -Element.addMethods = function(methods) { - var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; - - if (!methods) { - Object.extend(Form, Form.Methods); - Object.extend(Form.Element, Form.Element.Methods); - Object.extend(Element.Methods.ByTag, { - "FORM": Object.clone(Form.Methods), - "INPUT": Object.clone(Form.Element.Methods), - "SELECT": Object.clone(Form.Element.Methods), - "TEXTAREA": Object.clone(Form.Element.Methods), - "BUTTON": Object.clone(Form.Element.Methods) - }); - } - - if (arguments.length == 2) { - var tagName = methods; - methods = arguments[1]; - } - - if (!tagName) Object.extend(Element.Methods, methods || { }); - else { - if (Object.isArray(tagName)) tagName.each(extend); - else extend(tagName); - } - - function extend(tagName) { - tagName = tagName.toUpperCase(); - if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = { }; - Object.extend(Element.Methods.ByTag[tagName], methods); - } - - function copy(methods, destination, onlyIfAbsent) { - onlyIfAbsent = onlyIfAbsent || false; - for (var property in methods) { - var value = methods[property]; - if (!Object.isFunction(value)) continue; - if (!onlyIfAbsent || !(property in destination)) - destination[property] = value.methodize(); - } - } - - function findDOMClass(tagName) { - var klass; - var trans = { - "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", - "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", - "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", - "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", - "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": - "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": - "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": - "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": - "FrameSet", "IFRAME": "IFrame" - }; - if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName + 'Element'; - if (window[klass]) return window[klass]; - klass = 'HTML' + tagName.capitalize() + 'Element'; - if (window[klass]) return window[klass]; - - var element = document.createElement(tagName), - proto = element['__proto__'] || element.constructor.prototype; - - element = null; - return proto; - } - - var elementPrototype = window.HTMLElement ? HTMLElement.prototype : - Element.prototype; - - if (F.ElementExtensions) { - copy(Element.Methods, elementPrototype); - copy(Element.Methods.Simulated, elementPrototype, true); - } - - if (F.SpecificElementExtensions) { - for (var tag in Element.Methods.ByTag) { - var klass = findDOMClass(tag); - if (Object.isUndefined(klass)) continue; - copy(T[tag], klass.prototype); - } - } - - Object.extend(Element, Element.Methods); - delete Element.ByTag; - - if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = { }; -}; - - -document.viewport = { - - getDimensions: function() { - return { width: this.getWidth(), height: this.getHeight() }; - }, - - getScrollOffsets: function() { - return Element._returnOffset( - window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); - } -}; - -(function(viewport) { - var B = Prototype.Browser, doc = document, element, property = {}; - - function getRootElement() { - if (B.WebKit && !doc.evaluate) - return document; - - if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) - return document.body; - - return document.documentElement; - } - - function define(D) { - if (!element) element = getRootElement(); - - property[D] = 'client' + D; - - viewport['get' + D] = function() { return element[property[D]] }; - return viewport['get' + D](); - } - - viewport.getWidth = define.curry('Width'); - - viewport.getHeight = define.curry('Height'); -})(document.viewport); - - -Element.Storage = { - UID: 1 -}; - -Element.addMethods({ - getStorage: function(element) { - if (!(element = $(element))) return; - - var uid; - if (element === window) { - uid = 0; - } else { - if (typeof element._prototypeUID === "undefined") - element._prototypeUID = Element.Storage.UID++; - uid = element._prototypeUID; - } - - if (!Element.Storage[uid]) - Element.Storage[uid] = $H(); - - return Element.Storage[uid]; - }, - - store: function(element, key, value) { - if (!(element = $(element))) return; - - if (arguments.length === 2) { - Element.getStorage(element).update(key); - } else { - Element.getStorage(element).set(key, value); - } - - return element; - }, - - retrieve: function(element, key, defaultValue) { - if (!(element = $(element))) return; - var hash = Element.getStorage(element), value = hash.get(key); - - if (Object.isUndefined(value)) { - hash.set(key, defaultValue); - value = defaultValue; - } - - return value; - }, - - clone: function(element, deep) { - if (!(element = $(element))) return; - var clone = element.cloneNode(deep); - clone._prototypeUID = void 0; - if (deep) { - var descendants = Element.select(clone, '*'), - i = descendants.length; - while (i--) { - descendants[i]._prototypeUID = void 0; - } - } - return Element.extend(clone); - }, - - purge: function(element) { - if (!(element = $(element))) return; - var purgeElement = Element._purgeElement; - - purgeElement(element); - - var descendants = element.getElementsByTagName('*'), - i = descendants.length; - - while (i--) purgeElement(descendants[i]); - - return null; - } -}); - -(function() { - - function toDecimal(pctString) { - var match = pctString.match(/^(\d+)%?$/i); - if (!match) return null; - return (Number(match[1]) / 100); - } - - function getPixelValue(value, property, context) { - var element = null; - if (Object.isElement(value)) { - element = value; - value = element.getStyle(property); - } - - if (value === null) { - return null; - } - - if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { - return window.parseFloat(value); - } - - var isPercentage = value.include('%'), isViewport = (context === document.viewport); - - if (/\d/.test(value) && element && element.runtimeStyle && !(isPercentage && isViewport)) { - var style = element.style.left, rStyle = element.runtimeStyle.left; - element.runtimeStyle.left = element.currentStyle.left; - element.style.left = value || 0; - value = element.style.pixelLeft; - element.style.left = style; - element.runtimeStyle.left = rStyle; - - return value; - } - - if (element && isPercentage) { - context = context || element.parentNode; - var decimal = toDecimal(value); - var whole = null; - var position = element.getStyle('position'); - - var isHorizontal = property.include('left') || property.include('right') || - property.include('width'); - - var isVertical = property.include('top') || property.include('bottom') || - property.include('height'); - - if (context === document.viewport) { - if (isHorizontal) { - whole = document.viewport.getWidth(); - } else if (isVertical) { - whole = document.viewport.getHeight(); - } - } else { - if (isHorizontal) { - whole = $(context).measure('width'); - } else if (isVertical) { - whole = $(context).measure('height'); - } - } - - return (whole === null) ? 0 : whole * decimal; - } - - return 0; - } - - function toCSSPixels(number) { - if (Object.isString(number) && number.endsWith('px')) { - return number; - } - return number + 'px'; - } - - function isDisplayed(element) { - var originalElement = element; - while (element && element.parentNode) { - var display = element.getStyle('display'); - if (display === 'none') { - return false; - } - element = $(element.parentNode); - } - return true; - } - - var hasLayout = Prototype.K; - if ('currentStyle' in document.documentElement) { - hasLayout = function(element) { - if (!element.currentStyle.hasLayout) { - element.style.zoom = 1; - } - return element; - }; - } - - function cssNameFor(key) { - if (key.include('border')) key = key + '-width'; - return key.camelize(); - } - - Element.Layout = Class.create(Hash, { - initialize: function($super, element, preCompute) { - $super(); - this.element = $(element); - - Element.Layout.PROPERTIES.each( function(property) { - this._set(property, null); - }, this); - - if (preCompute) { - this._preComputing = true; - this._begin(); - Element.Layout.PROPERTIES.each( this._compute, this ); - this._end(); - this._preComputing = false; - } - }, - - _set: function(property, value) { - return Hash.prototype.set.call(this, property, value); - }, - - set: function(property, value) { - throw "Properties of Element.Layout are read-only."; - }, - - get: function($super, property) { - var value = $super(property); - return value === null ? this._compute(property) : value; - }, - - _begin: function() { - if (this._prepared) return; - - var element = this.element; - if (isDisplayed(element)) { - this._prepared = true; - return; - } - - var originalStyles = { - position: element.style.position || '', - width: element.style.width || '', - visibility: element.style.visibility || '', - display: element.style.display || '' - }; - - element.store('prototype_original_styles', originalStyles); - - var position = element.getStyle('position'), - width = element.getStyle('width'); - - if (width === "0px" || width === null) { - element.style.display = 'block'; - width = element.getStyle('width'); - } - - var context = (position === 'fixed') ? document.viewport : - element.parentNode; - - element.setStyle({ - position: 'absolute', - visibility: 'hidden', - display: 'block' - }); - - var positionedWidth = element.getStyle('width'); - - var newWidth; - if (width && (positionedWidth === width)) { - newWidth = getPixelValue(element, 'width', context); - } else if (position === 'absolute' || position === 'fixed') { - newWidth = getPixelValue(element, 'width', context); - } else { - var parent = element.parentNode, pLayout = $(parent).getLayout(); - - newWidth = pLayout.get('width') - - this.get('margin-left') - - this.get('border-left') - - this.get('padding-left') - - this.get('padding-right') - - this.get('border-right') - - this.get('margin-right'); - } - - element.setStyle({ width: newWidth + 'px' }); - - this._prepared = true; - }, - - _end: function() { - var element = this.element; - var originalStyles = element.retrieve('prototype_original_styles'); - element.store('prototype_original_styles', null); - element.setStyle(originalStyles); - this._prepared = false; - }, - - _compute: function(property) { - var COMPUTATIONS = Element.Layout.COMPUTATIONS; - if (!(property in COMPUTATIONS)) { - throw "Property not found."; - } - - return this._set(property, COMPUTATIONS[property].call(this, this.element)); - }, - - toObject: function() { - var args = $A(arguments); - var keys = (args.length === 0) ? Element.Layout.PROPERTIES : - args.join(' ').split(' '); - var obj = {}; - keys.each( function(key) { - if (!Element.Layout.PROPERTIES.include(key)) return; - var value = this.get(key); - if (value != null) obj[key] = value; - }, this); - return obj; - }, - - toHash: function() { - var obj = this.toObject.apply(this, arguments); - return new Hash(obj); - }, - - toCSS: function() { - var args = $A(arguments); - var keys = (args.length === 0) ? Element.Layout.PROPERTIES : - args.join(' ').split(' '); - var css = {}; - - keys.each( function(key) { - if (!Element.Layout.PROPERTIES.include(key)) return; - if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; - - var value = this.get(key); - if (value != null) css[cssNameFor(key)] = value + 'px'; - }, this); - return css; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Element.Layout, { - PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), - - COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), - - COMPUTATIONS: { - 'height': function(element) { - if (!this._preComputing) this._begin(); - - var bHeight = this.get('border-box-height'); - if (bHeight <= 0) { - if (!this._preComputing) this._end(); - return 0; - } - - var bTop = this.get('border-top'), - bBottom = this.get('border-bottom'); - - var pTop = this.get('padding-top'), - pBottom = this.get('padding-bottom'); - - if (!this._preComputing) this._end(); - - return bHeight - bTop - bBottom - pTop - pBottom; - }, - - 'width': function(element) { - if (!this._preComputing) this._begin(); - - var bWidth = this.get('border-box-width'); - if (bWidth <= 0) { - if (!this._preComputing) this._end(); - return 0; - } - - var bLeft = this.get('border-left'), - bRight = this.get('border-right'); - - var pLeft = this.get('padding-left'), - pRight = this.get('padding-right'); - - if (!this._preComputing) this._end(); - - return bWidth - bLeft - bRight - pLeft - pRight; - }, - - 'padding-box-height': function(element) { - var height = this.get('height'), - pTop = this.get('padding-top'), - pBottom = this.get('padding-bottom'); - - return height + pTop + pBottom; - }, - - 'padding-box-width': function(element) { - var width = this.get('width'), - pLeft = this.get('padding-left'), - pRight = this.get('padding-right'); - - return width + pLeft + pRight; - }, - - 'border-box-height': function(element) { - if (!this._preComputing) this._begin(); - var height = element.offsetHeight; - if (!this._preComputing) this._end(); - return height; - }, - - 'border-box-width': function(element) { - if (!this._preComputing) this._begin(); - var width = element.offsetWidth; - if (!this._preComputing) this._end(); - return width; - }, - - 'margin-box-height': function(element) { - var bHeight = this.get('border-box-height'), - mTop = this.get('margin-top'), - mBottom = this.get('margin-bottom'); - - if (bHeight <= 0) return 0; - - return bHeight + mTop + mBottom; - }, - - 'margin-box-width': function(element) { - var bWidth = this.get('border-box-width'), - mLeft = this.get('margin-left'), - mRight = this.get('margin-right'); - - if (bWidth <= 0) return 0; - - return bWidth + mLeft + mRight; - }, - - 'top': function(element) { - var offset = element.positionedOffset(); - return offset.top; - }, - - 'bottom': function(element) { - var offset = element.positionedOffset(), - parent = element.getOffsetParent(), - pHeight = parent.measure('height'); - - var mHeight = this.get('border-box-height'); - - return pHeight - mHeight - offset.top; - }, - - 'left': function(element) { - var offset = element.positionedOffset(); - return offset.left; - }, - - 'right': function(element) { - var offset = element.positionedOffset(), - parent = element.getOffsetParent(), - pWidth = parent.measure('width'); - - var mWidth = this.get('border-box-width'); - - return pWidth - mWidth - offset.left; - }, - - 'padding-top': function(element) { - return getPixelValue(element, 'paddingTop'); - }, - - 'padding-bottom': function(element) { - return getPixelValue(element, 'paddingBottom'); - }, - - 'padding-left': function(element) { - return getPixelValue(element, 'paddingLeft'); - }, - - 'padding-right': function(element) { - return getPixelValue(element, 'paddingRight'); - }, - - 'border-top': function(element) { - return getPixelValue(element, 'borderTopWidth'); - }, - - 'border-bottom': function(element) { - return getPixelValue(element, 'borderBottomWidth'); - }, - - 'border-left': function(element) { - return getPixelValue(element, 'borderLeftWidth'); - }, - - 'border-right': function(element) { - return getPixelValue(element, 'borderRightWidth'); - }, - - 'margin-top': function(element) { - return getPixelValue(element, 'marginTop'); - }, - - 'margin-bottom': function(element) { - return getPixelValue(element, 'marginBottom'); - }, - - 'margin-left': function(element) { - return getPixelValue(element, 'marginLeft'); - }, - - 'margin-right': function(element) { - return getPixelValue(element, 'marginRight'); - } - } - }); - - if ('getBoundingClientRect' in document.documentElement) { - Object.extend(Element.Layout.COMPUTATIONS, { - 'right': function(element) { - var parent = hasLayout(element.getOffsetParent()); - var rect = element.getBoundingClientRect(), - pRect = parent.getBoundingClientRect(); - - return (pRect.right - rect.right).round(); - }, - - 'bottom': function(element) { - var parent = hasLayout(element.getOffsetParent()); - var rect = element.getBoundingClientRect(), - pRect = parent.getBoundingClientRect(); - - return (pRect.bottom - rect.bottom).round(); - } - }); - } - - Element.Offset = Class.create({ - initialize: function(left, top) { - this.left = left.round(); - this.top = top.round(); - - this[0] = this.left; - this[1] = this.top; - }, - - relativeTo: function(offset) { - return new Element.Offset( - this.left - offset.left, - this.top - offset.top - ); - }, - - inspect: function() { - return "#".interpolate(this); - }, - - toString: function() { - return "[#{left}, #{top}]".interpolate(this); - }, - - toArray: function() { - return [this.left, this.top]; - } - }); - - function getLayout(element, preCompute) { - return new Element.Layout(element, preCompute); - } - - function measure(element, property) { - return $(element).getLayout().get(property); - } - - function getDimensions(element) { - element = $(element); - var display = Element.getStyle(element, 'display'); - - if (display && display !== 'none') { - return { width: element.offsetWidth, height: element.offsetHeight }; - } - - var style = element.style; - var originalStyles = { - visibility: style.visibility, - position: style.position, - display: style.display - }; - - var newStyles = { - visibility: 'hidden', - display: 'block' - }; - - if (originalStyles.position !== 'fixed') - newStyles.position = 'absolute'; - - Element.setStyle(element, newStyles); - - var dimensions = { - width: element.offsetWidth, - height: element.offsetHeight - }; - - Element.setStyle(element, originalStyles); - - return dimensions; - } - - function getOffsetParent(element) { - element = $(element); - - if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) - return $(document.body); - - var isInline = (Element.getStyle(element, 'display') === 'inline'); - if (!isInline && element.offsetParent) return $(element.offsetParent); - - while ((element = element.parentNode) && element !== document.body) { - if (Element.getStyle(element, 'position') !== 'static') { - return isHtml(element) ? $(document.body) : $(element); - } - } - - return $(document.body); - } - - - function cumulativeOffset(element) { - element = $(element); - var valueT = 0, valueL = 0; - if (element.parentNode) { - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); - } - return new Element.Offset(valueL, valueT); - } - - function positionedOffset(element) { - element = $(element); - - var layout = element.getLayout(); - - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - if (element) { - if (isBody(element)) break; - var p = Element.getStyle(element, 'position'); - if (p !== 'static') break; - } - } while (element); - - valueL -= layout.get('margin-top'); - valueT -= layout.get('margin-left'); - - return new Element.Offset(valueL, valueT); - } - - function cumulativeScrollOffset(element) { - var valueT = 0, valueL = 0; - do { - valueT += element.scrollTop || 0; - valueL += element.scrollLeft || 0; - element = element.parentNode; - } while (element); - return new Element.Offset(valueL, valueT); - } - - function viewportOffset(forElement) { - element = $(element); - var valueT = 0, valueL = 0, docBody = document.body; - - var element = forElement; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == docBody && - Element.getStyle(element, 'position') == 'absolute') break; - } while (element = element.offsetParent); - - element = forElement; - do { - if (element != docBody) { - valueT -= element.scrollTop || 0; - valueL -= element.scrollLeft || 0; - } - } while (element = element.parentNode); - return new Element.Offset(valueL, valueT); - } - - function absolutize(element) { - element = $(element); - - if (Element.getStyle(element, 'position') === 'absolute') { - return element; - } - - var offsetParent = getOffsetParent(element); - var eOffset = element.viewportOffset(), - pOffset = offsetParent.viewportOffset(); - - var offset = eOffset.relativeTo(pOffset); - var layout = element.getLayout(); - - element.store('prototype_absolutize_original_styles', { - left: element.getStyle('left'), - top: element.getStyle('top'), - width: element.getStyle('width'), - height: element.getStyle('height') - }); - - element.setStyle({ - position: 'absolute', - top: offset.top + 'px', - left: offset.left + 'px', - width: layout.get('width') + 'px', - height: layout.get('height') + 'px' - }); - - return element; - } - - function relativize(element) { - element = $(element); - if (Element.getStyle(element, 'position') === 'relative') { - return element; - } - - var originalStyles = - element.retrieve('prototype_absolutize_original_styles'); - - if (originalStyles) element.setStyle(originalStyles); - return element; - } - - if (Prototype.Browser.IE) { - getOffsetParent = getOffsetParent.wrap( - function(proceed, element) { - element = $(element); - - if (isDocument(element) || isDetached(element) || isBody(element) || isHtml(element)) - return $(document.body); - - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - } - ); - - positionedOffset = positionedOffset.wrap(function(proceed, element) { - element = $(element); - if (!element.parentNode) return new Element.Offset(0, 0); - var position = element.getStyle('position'); - if (position !== 'static') return proceed(element); - - var offsetParent = element.getOffsetParent(); - if (offsetParent && offsetParent.getStyle('position') === 'fixed') - hasLayout(offsetParent); - - element.setStyle({ position: 'relative' }); - var value = proceed(element); - element.setStyle({ position: position }); - return value; - }); - } else if (Prototype.Browser.Webkit) { - cumulativeOffset = function(element) { - element = $(element); - var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - if (element.offsetParent == document.body) - if (Element.getStyle(element, 'position') == 'absolute') break; - - element = element.offsetParent; - } while (element); - - return new Element.Offset(valueL, valueT); - }; - } - - - Element.addMethods({ - getLayout: getLayout, - measure: measure, - getDimensions: getDimensions, - getOffsetParent: getOffsetParent, - cumulativeOffset: cumulativeOffset, - positionedOffset: positionedOffset, - cumulativeScrollOffset: cumulativeScrollOffset, - viewportOffset: viewportOffset, - absolutize: absolutize, - relativize: relativize - }); - - function isBody(element) { - return element.nodeName.toUpperCase() === 'BODY'; - } - - function isHtml(element) { - return element.nodeName.toUpperCase() === 'HTML'; - } - - function isDocument(element) { - return element.nodeType === Node.DOCUMENT_NODE; - } - - function isDetached(element) { - return element !== document.body && - !Element.descendantOf(element, document.body); - } - - if ('getBoundingClientRect' in document.documentElement) { - Element.addMethods({ - viewportOffset: function(element) { - element = $(element); - if (isDetached(element)) return new Element.Offset(0, 0); - - var rect = element.getBoundingClientRect(), - docEl = document.documentElement; - return new Element.Offset(rect.left - docEl.clientLeft, - rect.top - docEl.clientTop); - } - }); - } -})(); -window.$$ = function() { - var expression = $A(arguments).join(', '); - return Prototype.Selector.select(expression, document); -}; - -Prototype.Selector = (function() { - - function select() { - throw new Error('Method "Prototype.Selector.select" must be defined.'); - } - - function match() { - throw new Error('Method "Prototype.Selector.match" must be defined.'); - } - - function find(elements, expression, index) { - index = index || 0; - var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; - - for (i = 0; i < length; i++) { - if (match(elements[i], expression) && index == matchIndex++) { - return Element.extend(elements[i]); - } - } - } - - function extendElements(elements) { - for (var i = 0, length = elements.length; i < length; i++) { - Element.extend(elements[i]); - } - return elements; - } - - - var K = Prototype.K; - - return { - select: select, - match: match, - find: find, - extendElements: (Element.extend === K) ? K : extendElements, - extendElement: Element.extend - }; -})(); -Prototype._original_property = window.Sizzle; -/*! - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true; - -[0, 0].sort(function(){ - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function(selector, context, results, seed) { - results = results || []; - var origContext = context = context || document; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), - soFar = selector; - - while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) - selector += parts.shift(); - - set = posProcess( selector, set ); - } - } - } else { - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - - if ( context ) { - var ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } - - while ( parts.length ) { - var cur = parts.pop(), pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } - - return results; -}; - -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; - -Sizzle.find = function(expr, context, isXML){ - var set, match; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = context.getElementsByTagName("*"); - } - - return {set: set, expr: expr}; -}; - -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; - anyFound = false; - - if ( curLoop == result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - if ( expr == old ) { - if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag && !isXML ) { - part = part.toUpperCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; - - if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); - - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - - if ( typeof part === "string" && !/\W/.test(part) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } - - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context, isXML){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) - result.push( elem ); - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); - }, - CHILD: function(match){ - if ( match[1] == "nth" ) { - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - - match[0] = done++; - - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); - } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return /h\d/i.test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; - }, - input: function(elem){ - return /input|select|textarea|button/i.test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; - }, - gt: function(elem, i, match){ - return i > match[3] - 0; - }, - nth: function(elem, i, match){ - return match[3] - 0 == i; - }, - eq: function(elem, i, match){ - return match[3] - 0 == i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; - - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } - - return true; - } - }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) return false; - } - if ( type == 'first') return true; - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) return false; - } - return true; - case 'nth': - var first = match[2], last = match[3]; - - if ( first == 1 && last == 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first == 0 ) { - return diff == 0; - } else { - return ( diff % first == 0 && diff / first >= 0 ); - } - } - }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value != check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); -} - -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 ); - -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( var i = 0; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( "sourceIndex" in document.documentElement ) { - sortOrder = function( a, b ) { - if ( !a.sourceIndex || !b.sourceIndex ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - if ( !a.ownerDocument || !b.ownerDocument ) { - if ( a == b ) { - hasDuplicate = true; - } - return 0; - } - - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} - -(function(){ - var form = document.createElement("div"), - id = "script" + (new Date).getTime(); - form.innerHTML = ""; - - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); - - if ( !!document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - root = form = null; // release memory in IE -})(); - -(function(){ - - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); - - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); - }; - } - - div = null; // release memory in IE -})(); - -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; - - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function(query, context, extra, seed){ - context = context || document; - - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - div = null; // release memory in IE -})(); - -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ - var div = document.createElement("div"); - div.innerHTML = "
    "; - - if ( div.getElementsByClassName("e").length === 0 ) - return; - - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) - return; - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - div = null; // release memory in IE -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; - -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; -}; - -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; - - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - - -window.Sizzle = Sizzle; - -})(); - -;(function(engine) { - var extendElements = Prototype.Selector.extendElements; - - function select(selector, scope) { - return extendElements(engine(selector, scope || document)); - } - - function match(element, selector) { - return engine.matches(selector, [element]).length == 1; - } - - Prototype.Selector.engine = engine; - Prototype.Selector.select = select; - Prototype.Selector.match = match; -})(Sizzle); - -window.Sizzle = Prototype._original_property; -delete Prototype._original_property; - -var Form = { - reset: function(form) { - form = $(form); - form.reset(); - return form; - }, - - serializeElements: function(elements, options) { - if (typeof options != 'object') options = { hash: !!options }; - else if (Object.isUndefined(options.hash)) options.hash = true; - var key, value, submitted = false, submit = options.submit, accumulator, initial; - - if (options.hash) { - initial = {}; - accumulator = function(result, key, value) { - if (key in result) { - if (!Object.isArray(result[key])) result[key] = [result[key]]; - result[key].push(value); - } else result[key] = value; - return result; - }; - } else { - initial = ''; - accumulator = function(result, key, value) { - return result + (result ? '&' : '') + encodeURIComponent(key) + '=' + encodeURIComponent(value); - } - } - - return elements.inject(initial, function(result, element) { - if (!element.disabled && element.name) { - key = element.name; value = $(element).getValue(); - if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && - submit !== false && (!submit || key == submit) && (submitted = true)))) { - result = accumulator(result, key, value); - } - } - return result; - }); - } -}; - -Form.Methods = { - serialize: function(form, options) { - return Form.serializeElements(Form.getElements(form), options); - }, - - getElements: function(form) { - var elements = $(form).getElementsByTagName('*'), - element, - arr = [ ], - serializers = Form.Element.Serializers; - for (var i = 0; element = elements[i]; i++) { - arr.push(element); - } - return arr.inject([], function(elements, child) { - if (serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - }) - }, - - getInputs: function(form, typeName, name) { - form = $(form); - var inputs = form.getElementsByTagName('input'); - - if (!typeName && !name) return $A(inputs).map(Element.extend); - - for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { - var input = inputs[i]; - if ((typeName && input.type != typeName) || (name && input.name != name)) - continue; - matchingInputs.push(Element.extend(input)); - } - - return matchingInputs; - }, - - disable: function(form) { - form = $(form); - Form.getElements(form).invoke('disable'); - return form; - }, - - enable: function(form) { - form = $(form); - Form.getElements(form).invoke('enable'); - return form; - }, - - findFirstElement: function(form) { - var elements = $(form).getElements().findAll(function(element) { - return 'hidden' != element.type && !element.disabled; - }); - var firstByIndex = elements.findAll(function(element) { - return element.hasAttribute('tabIndex') && element.tabIndex >= 0; - }).sortBy(function(element) { return element.tabIndex }).first(); - - return firstByIndex ? firstByIndex : elements.find(function(element) { - return /^(?:input|select|textarea)$/i.test(element.tagName); - }); - }, - - focusFirstElement: function(form) { - form = $(form); - var element = form.findFirstElement(); - if (element) element.activate(); - return form; - }, - - request: function(form, options) { - form = $(form), options = Object.clone(options || { }); - - var params = options.parameters, action = form.readAttribute('action') || ''; - if (action.blank()) action = window.location.href; - options.parameters = form.serialize(true); - - if (params) { - if (Object.isString(params)) params = params.toQueryParams(); - Object.extend(options.parameters, params); - } - - if (form.hasAttribute('method') && !options.method) - options.method = form.method; - - return new Ajax.Request(action, options); - } -}; - -/*--------------------------------------------------------------------------*/ - - -Form.Element = { - focus: function(element) { - $(element).focus(); - return element; - }, - - select: function(element) { - $(element).select(); - return element; - } -}; - -Form.Element.Methods = { - - serialize: function(element) { - element = $(element); - if (!element.disabled && element.name) { - var value = element.getValue(); - if (value != undefined) { - var pair = { }; - pair[element.name] = value; - return Object.toQueryString(pair); - } - } - return ''; - }, - - getValue: function(element) { - element = $(element); - var method = element.tagName.toLowerCase(); - return Form.Element.Serializers[method](element); - }, - - setValue: function(element, value) { - element = $(element); - var method = element.tagName.toLowerCase(); - Form.Element.Serializers[method](element, value); - return element; - }, - - clear: function(element) { - $(element).value = ''; - return element; - }, - - present: function(element) { - return $(element).value != ''; - }, - - activate: function(element) { - element = $(element); - try { - element.focus(); - if (element.select && (element.tagName.toLowerCase() != 'input' || - !(/^(?:button|reset|submit)$/i.test(element.type)))) - element.select(); - } catch (e) { } - return element; - }, - - disable: function(element) { - element = $(element); - element.disabled = true; - return element; - }, - - enable: function(element) { - element = $(element); - element.disabled = false; - return element; - } -}; - -/*--------------------------------------------------------------------------*/ - -var Field = Form.Element; - -var $F = Form.Element.Methods.getValue; - -/*--------------------------------------------------------------------------*/ - -Form.Element.Serializers = (function() { - function input(element, value) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - return inputSelector(element, value); - default: - return valueSelector(element, value); - } - } - - function inputSelector(element, value) { - if (Object.isUndefined(value)) - return element.checked ? element.value : null; - else element.checked = !!value; - } - - function valueSelector(element, value) { - if (Object.isUndefined(value)) return element.value; - else element.value = value; - } - - function select(element, value) { - if (Object.isUndefined(value)) - return (element.type === 'select-one' ? selectOne : selectMany)(element); - - var opt, currentValue, single = !Object.isArray(value); - for (var i = 0, length = element.length; i < length; i++) { - opt = element.options[i]; - currentValue = this.optionValue(opt); - if (single) { - if (currentValue == value) { - opt.selected = true; - return; - } - } - else opt.selected = value.include(currentValue); - } - } - - function selectOne(element) { - var index = element.selectedIndex; - return index >= 0 ? optionValue(element.options[index]) : null; - } - - function selectMany(element) { - var values, length = element.length; - if (!length) return null; - - for (var i = 0, values = []; i < length; i++) { - var opt = element.options[i]; - if (opt.selected) values.push(optionValue(opt)); - } - return values; - } - - function optionValue(opt) { - return Element.hasAttribute(opt, 'value') ? opt.value : opt.text; - } - - return { - input: input, - inputSelector: inputSelector, - textarea: valueSelector, - select: select, - selectOne: selectOne, - selectMany: selectMany, - optionValue: optionValue, - button: valueSelector - }; -})(); - -/*--------------------------------------------------------------------------*/ - - -Abstract.TimedObserver = Class.create(PeriodicalExecuter, { - initialize: function($super, element, frequency, callback) { - $super(callback, frequency); - this.element = $(element); - this.lastValue = this.getValue(); - }, - - execute: function() { - var value = this.getValue(); - if (Object.isString(this.lastValue) && Object.isString(value) ? - this.lastValue != value : String(this.lastValue) != String(value)) { - this.callback(this.element, value); - this.lastValue = value; - } - } -}); - -Form.Element.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.Observer = Class.create(Abstract.TimedObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); - -/*--------------------------------------------------------------------------*/ - -Abstract.EventObserver = Class.create({ - initialize: function(element, callback) { - this.element = $(element); - this.callback = callback; - - this.lastValue = this.getValue(); - if (this.element.tagName.toLowerCase() == 'form') - this.registerFormCallbacks(); - else - this.registerCallback(this.element); - }, - - onElementEvent: function() { - var value = this.getValue(); - if (this.lastValue != value) { - this.callback(this.element, value); - this.lastValue = value; - } - }, - - registerFormCallbacks: function() { - Form.getElements(this.element).each(this.registerCallback, this); - }, - - registerCallback: function(element) { - if (element.type) { - switch (element.type.toLowerCase()) { - case 'checkbox': - case 'radio': - Event.observe(element, 'click', this.onElementEvent.bind(this)); - break; - default: - Event.observe(element, 'change', this.onElementEvent.bind(this)); - break; - } - } - } -}); - -Form.Element.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.Element.getValue(this.element); - } -}); - -Form.EventObserver = Class.create(Abstract.EventObserver, { - getValue: function() { - return Form.serialize(this.element); - } -}); -(function() { - - var Event = { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: {} - }; - - var docEl = document.documentElement; - var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl - && 'onmouseleave' in docEl; - - - - var isIELegacyEvent = function(event) { return false; }; - - if (window.attachEvent) { - if (window.addEventListener) { - isIELegacyEvent = function(event) { - return !(event instanceof window.Event); - }; - } else { - isIELegacyEvent = function(event) { return true; }; - } - } - - var _isButton; - - function _isButtonForDOMEvents(event, code) { - return event.which ? (event.which === code + 1) : (event.button === code); - } - - var legacyButtonMap = { 0: 1, 1: 4, 2: 2 }; - function _isButtonForLegacyEvents(event, code) { - return event.button === legacyButtonMap[code]; - } - - function _isButtonForWebKit(event, code) { - switch (code) { - case 0: return event.which == 1 && !event.metaKey; - case 1: return event.which == 2 || (event.which == 1 && event.metaKey); - case 2: return event.which == 3; - default: return false; - } - } - - if (window.attachEvent) { - if (!window.addEventListener) { - _isButton = _isButtonForLegacyEvents; - } else { - _isButton = function(event, code) { - return isIELegacyEvent(event) ? _isButtonForLegacyEvents(event, code) : - _isButtonForDOMEvents(event, code); - } - } - } else if (Prototype.Browser.WebKit) { - _isButton = _isButtonForWebKit; - } else { - _isButton = _isButtonForDOMEvents; - } - - function isLeftClick(event) { return _isButton(event, 0) } - - function isMiddleClick(event) { return _isButton(event, 1) } - - function isRightClick(event) { return _isButton(event, 2) } - - function element(event) { - event = Event.extend(event); - - var node = event.target, type = event.type, - currentTarget = event.currentTarget; - - if (currentTarget && currentTarget.tagName) { - if (type === 'load' || type === 'error' || - (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' - && currentTarget.type === 'radio')) - node = currentTarget; - } - - if (node.nodeType == Node.TEXT_NODE) - node = node.parentNode; - - return Element.extend(node); - } - - function findElement(event, expression) { - var element = Event.element(event); - - if (!expression) return element; - while (element) { - if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { - return Element.extend(element); - } - element = element.parentNode; - } - } - - function pointer(event) { - return { x: pointerX(event), y: pointerY(event) }; - } - - function pointerX(event) { - var docElement = document.documentElement, - body = document.body || { scrollLeft: 0 }; - - return event.pageX || (event.clientX + - (docElement.scrollLeft || body.scrollLeft) - - (docElement.clientLeft || 0)); - } - - function pointerY(event) { - var docElement = document.documentElement, - body = document.body || { scrollTop: 0 }; - - return event.pageY || (event.clientY + - (docElement.scrollTop || body.scrollTop) - - (docElement.clientTop || 0)); - } - - - function stop(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - - event.stopped = true; - } - - - Event.Methods = { - isLeftClick: isLeftClick, - isMiddleClick: isMiddleClick, - isRightClick: isRightClick, - - element: element, - findElement: findElement, - - pointer: pointer, - pointerX: pointerX, - pointerY: pointerY, - - stop: stop - }; - - var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { - m[name] = Event.Methods[name].methodize(); - return m; - }); - - if (window.attachEvent) { - function _relatedTarget(event) { - var element; - switch (event.type) { - case 'mouseover': - case 'mouseenter': - element = event.fromElement; - break; - case 'mouseout': - case 'mouseleave': - element = event.toElement; - break; - default: - return null; - } - return Element.extend(element); - } - - var additionalMethods = { - stopPropagation: function() { this.cancelBubble = true }, - preventDefault: function() { this.returnValue = false }, - inspect: function() { return '[object Event]' } - }; - - Event.extend = function(event, element) { - if (!event) return false; - - if (!isIELegacyEvent(event)) return event; - - if (event._extendedByPrototype) return event; - event._extendedByPrototype = Prototype.emptyFunction; - - var pointer = Event.pointer(event); - - Object.extend(event, { - target: event.srcElement || element, - relatedTarget: _relatedTarget(event), - pageX: pointer.x, - pageY: pointer.y - }); - - Object.extend(event, methods); - Object.extend(event, additionalMethods); - - return event; - }; - } else { - Event.extend = Prototype.K; - } - - if (window.addEventListener) { - Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; - Object.extend(Event.prototype, methods); - } - - function _createResponder(element, eventName, handler) { - var registry = Element.retrieve(element, 'prototype_event_registry'); - - if (Object.isUndefined(registry)) { - CACHE.push(element); - registry = Element.retrieve(element, 'prototype_event_registry', $H()); - } - - var respondersForEvent = registry.get(eventName); - if (Object.isUndefined(respondersForEvent)) { - respondersForEvent = []; - registry.set(eventName, respondersForEvent); - } - - if (respondersForEvent.pluck('handler').include(handler)) return false; - - var responder; - if (eventName.include(":")) { - responder = function(event) { - if (Object.isUndefined(event.eventName)) - return false; - - if (event.eventName !== eventName) - return false; - - Event.extend(event, element); - handler.call(element, event); - }; - } else { - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && - (eventName === "mouseenter" || eventName === "mouseleave")) { - if (eventName === "mouseenter" || eventName === "mouseleave") { - responder = function(event) { - Event.extend(event, element); - - var parent = event.relatedTarget; - while (parent && parent !== element) { - try { parent = parent.parentNode; } - catch(e) { parent = element; } - } - - if (parent === element) return; - - handler.call(element, event); - }; - } - } else { - responder = function(event) { - Event.extend(event, element); - handler.call(element, event); - }; - } - } - - responder.handler = handler; - respondersForEvent.push(responder); - return responder; - } - - function _destroyCache() { - for (var i = 0, length = CACHE.length; i < length; i++) { - Event.stopObserving(CACHE[i]); - CACHE[i] = null; - } - } - - var CACHE = []; - - if (Prototype.Browser.IE) - window.attachEvent('onunload', _destroyCache); - - if (Prototype.Browser.WebKit) - window.addEventListener('unload', Prototype.emptyFunction, false); - - - var _getDOMEventName = Prototype.K, - translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - - if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { - _getDOMEventName = function(eventName) { - return (translations[eventName] || eventName); - }; - } - - function observe(element, eventName, handler) { - element = $(element); - - var responder = _createResponder(element, eventName, handler); - - if (!responder) return element; - - if (eventName.include(':')) { - if (element.addEventListener) - element.addEventListener("dataavailable", responder, false); - else { - element.attachEvent("ondataavailable", responder); - element.attachEvent("onlosecapture", responder); - } - } else { - var actualEventName = _getDOMEventName(eventName); - - if (element.addEventListener) - element.addEventListener(actualEventName, responder, false); - else - element.attachEvent("on" + actualEventName, responder); - } - - return element; - } - - function stopObserving(element, eventName, handler) { - element = $(element); - - var registry = Element.retrieve(element, 'prototype_event_registry'); - if (!registry) return element; - - if (!eventName) { - registry.each( function(pair) { - var eventName = pair.key; - stopObserving(element, eventName); - }); - return element; - } - - var responders = registry.get(eventName); - if (!responders) return element; - - if (!handler) { - responders.each(function(r) { - stopObserving(element, eventName, r.handler); - }); - return element; - } - - var i = responders.length, responder; - while (i--) { - if (responders[i].handler === handler) { - responder = responders[i]; - break; - } - } - if (!responder) return element; - - if (eventName.include(':')) { - if (element.removeEventListener) - element.removeEventListener("dataavailable", responder, false); - else { - element.detachEvent("ondataavailable", responder); - element.detachEvent("onlosecapture", responder); - } - } else { - var actualEventName = _getDOMEventName(eventName); - if (element.removeEventListener) - element.removeEventListener(actualEventName, responder, false); - else - element.detachEvent('on' + actualEventName, responder); - } - - registry.set(eventName, responders.without(responder)); - - return element; - } - - function fire(element, eventName, memo, bubble) { - element = $(element); - - if (Object.isUndefined(bubble)) - bubble = true; - - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; - - var event; - if (document.createEvent) { - event = document.createEvent('HTMLEvents'); - event.initEvent('dataavailable', bubble, true); - } else { - event = document.createEventObject(); - event.eventType = bubble ? 'ondataavailable' : 'onlosecapture'; - } - - event.eventName = eventName; - event.memo = memo || { }; - - if (document.createEvent) - element.dispatchEvent(event); - else - element.fireEvent(event.eventType, event); - - return Event.extend(event); - } - - Event.Handler = Class.create({ - initialize: function(element, eventName, selector, callback) { - this.element = $(element); - this.eventName = eventName; - this.selector = selector; - this.callback = callback; - this.handler = this.handleEvent.bind(this); - }, - - start: function() { - Event.observe(this.element, this.eventName, this.handler); - return this; - }, - - stop: function() { - Event.stopObserving(this.element, this.eventName, this.handler); - return this; - }, - - handleEvent: function(event) { - var element = Event.findElement(event, this.selector); - if (element) this.callback.call(this.element, event, element); - } - }); - - function on(element, eventName, selector, callback) { - element = $(element); - if (Object.isFunction(selector) && Object.isUndefined(callback)) { - callback = selector, selector = null; - } - - return new Event.Handler(element, eventName, selector, callback).start(); - } - - Object.extend(Event, Event.Methods); - - Object.extend(Event, { - fire: fire, - observe: observe, - stopObserving: stopObserving, - on: on - }); - - Element.addMethods({ - fire: fire, - - observe: observe, - - stopObserving: stopObserving, - - on: on - }); - - Object.extend(document, { - fire: fire.methodize(), - - observe: observe.methodize(), - - stopObserving: stopObserving.methodize(), - - on: on.methodize(), - - loaded: false - }); - - if (window.Event) Object.extend(window.Event, Event); - else window.Event = Event; -})(); - -(function() { - /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - - var timer; - - function fireContentLoadedEvent() { - if (document.loaded) return; - if (timer) window.clearTimeout(timer); - document.loaded = true; - document.fire('dom:loaded'); - } - - function checkReadyState() { - if (document.readyState === 'complete') { - document.stopObserving('readystatechange', checkReadyState); - fireContentLoadedEvent(); - } - } - - function pollDoScroll() { - try { document.documentElement.doScroll('left'); } - catch(e) { - timer = pollDoScroll.defer(); - return; - } - fireContentLoadedEvent(); - } - - if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); - } else { - document.observe('readystatechange', checkReadyState); - if (window == top) - timer = pollDoScroll.defer(); - } - - Event.observe(window, 'load', fireContentLoadedEvent); -})(); - -Element.addMethods(); - -/*------------------------------- DEPRECATED -------------------------------*/ - -Hash.toQueryString = Object.toQueryString; - -var Toggle = { display: Element.toggle }; - -Element.Methods.childOf = Element.Methods.descendantOf; - -var Insertion = { - Before: function(element, content) { - return Element.insert(element, {before:content}); - }, - - Top: function(element, content) { - return Element.insert(element, {top:content}); - }, - - Bottom: function(element, content) { - return Element.insert(element, {bottom:content}); - }, - - After: function(element, content) { - return Element.insert(element, {after:content}); - } -}; - -var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); - -var Position = { - includeScrollOffsets: false, - - prepare: function() { - this.deltaX = window.pageXOffset - || document.documentElement.scrollLeft - || document.body.scrollLeft - || 0; - this.deltaY = window.pageYOffset - || document.documentElement.scrollTop - || document.body.scrollTop - || 0; - }, - - within: function(element, x, y) { - if (this.includeScrollOffsets) - return this.withinIncludingScrolloffsets(element, x, y); - this.xcomp = x; - this.ycomp = y; - this.offset = Element.cumulativeOffset(element); - - return (y >= this.offset[1] && - y < this.offset[1] + element.offsetHeight && - x >= this.offset[0] && - x < this.offset[0] + element.offsetWidth); - }, - - withinIncludingScrolloffsets: function(element, x, y) { - var offsetcache = Element.cumulativeScrollOffset(element); - - this.xcomp = x + offsetcache[0] - this.deltaX; - this.ycomp = y + offsetcache[1] - this.deltaY; - this.offset = Element.cumulativeOffset(element); - - return (this.ycomp >= this.offset[1] && - this.ycomp < this.offset[1] + element.offsetHeight && - this.xcomp >= this.offset[0] && - this.xcomp < this.offset[0] + element.offsetWidth); - }, - - overlap: function(mode, element) { - if (!mode) return 0; - if (mode == 'vertical') - return ((this.offset[1] + element.offsetHeight) - this.ycomp) / - element.offsetHeight; - if (mode == 'horizontal') - return ((this.offset[0] + element.offsetWidth) - this.xcomp) / - element.offsetWidth; - }, - - - cumulativeOffset: Element.Methods.cumulativeOffset, - - positionedOffset: Element.Methods.positionedOffset, - - absolutize: function(element) { - Position.prepare(); - return Element.absolutize(element); - }, - - relativize: function(element) { - Position.prepare(); - return Element.relativize(element); - }, - - realOffset: Element.Methods.cumulativeScrollOffset, - - offsetParent: Element.Methods.getOffsetParent, - - page: Element.Methods.viewportOffset, - - clone: function(source, target, options) { - options = options || { }; - return Element.clonePosition(target, source, options); - } -}; - -/*--------------------------------------------------------------------------*/ - -if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ - function iter(name) { - return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; - } - - instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? - function(element, className) { - className = className.toString().strip(); - var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); - return cond ? document._getElementsByXPath('.//*' + cond, element) : []; - } : function(element, className) { - className = className.toString().strip(); - var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); - if (!classNames && !className) return elements; - - var nodes = $(element).getElementsByTagName('*'); - className = ' ' + className + ' '; - - for (var i = 0, child, cn; child = nodes[i]; i++) { - if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || - (classNames && classNames.all(function(name) { - return !name.toString().blank() && cn.include(' ' + name + ' '); - })))) - elements.push(Element.extend(child)); - } - return elements; - }; - - return function(className, parentElement) { - return $(parentElement || document.body).getElementsByClassName(className); - }; -}(Element.Methods); - -/*--------------------------------------------------------------------------*/ - -Element.ClassNames = Class.create(); -Element.ClassNames.prototype = { - initialize: function(element) { - this.element = $(element); - }, - - _each: function(iterator) { - this.element.className.split(/\s+/).select(function(name) { - return name.length > 0; - })._each(iterator); - }, - - set: function(className) { - this.element.className = className; - }, - - add: function(classNameToAdd) { - if (this.include(classNameToAdd)) return; - this.set($A(this).concat(classNameToAdd).join(' ')); - }, - - remove: function(classNameToRemove) { - if (!this.include(classNameToRemove)) return; - this.set($A(this).without(classNameToRemove).join(' ')); - }, - - toString: function() { - return $A(this).join(' '); - } -}; - -Object.extend(Element.ClassNames.prototype, Enumerable); - -/*--------------------------------------------------------------------------*/ - -(function() { - window.Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - }, - - findElements: function(rootElement) { - return Prototype.Selector.select(this.expression, rootElement); - }, - - match: function(element) { - return Prototype.Selector.match(element, this.expression); - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Selector, { - matchElements: function(elements, expression) { - var match = Prototype.Selector.match, - results = []; - - for (var i = 0, length = elements.length; i < length; i++) { - var element = elements[i]; - if (match(element, expression)) { - results.push(Element.extend(element)); - } - } - return results; - }, - - findElement: function(elements, expression, index) { - index = index || 0; - var matchIndex = 0, element; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (Prototype.Selector.match(element, expression) && index === matchIndex++) { - return Element.extend(element); - } - } - }, - - findChildElements: function(element, expressions) { - var selector = expressions.toArray().join(', '); - return Prototype.Selector.select(selector, element || document); - } - }); -})(); diff --git a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js b/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js deleted file mode 100644 index 88eac6e458..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/vendor/assets/javascripts/prototype_ujs.js +++ /dev/null @@ -1,202 +0,0 @@ -(function() { - Ajax.Responders.register({ - onCreate: function(request) { - var token = $$('meta[name=csrf-token]')[0]; - if (token) { - if (!request.options.requestHeaders) request.options.requestHeaders = {}; - request.options.requestHeaders['X-CSRF-Token'] = token.readAttribute('content'); - } - } - }); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - function isEventSupported(eventName) { - var el = document.createElement('div'); - eventName = 'on' + eventName; - var isSupported = (eventName in el); - if (!isSupported) { - el.setAttribute(eventName, 'return;'); - isSupported = typeof el[eventName] == 'function'; - } - el = null; - return isSupported; - } - - function isForm(element) { - return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'; - } - - function isInput(element) { - if (Object.isElement(element)) { - var name = element.nodeName.toUpperCase(); - return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'; - } - else return false; - } - - var submitBubbles = isEventSupported('submit'), - changeBubbles = isEventSupported('change'); - - if (!submitBubbles || !changeBubbles) { - // augment the Event.Handler class to observe custom events when needed - Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap( - function(init, element, eventName, selector, callback) { - init(element, eventName, selector, callback); - // is the handler being attached to an element that doesn't support this event? - if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) || - (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) { - // "submit" => "emulated:submit" - this.eventName = 'emulated:' + this.eventName; - } - } - ); - } - - if (!submitBubbles) { - // discover forms on the page by observing focus events which always bubble - document.on('focusin', 'form', function(focusEvent, form) { - // special handler for the real "submit" event (one-time operation) - if (!form.retrieve('emulated:submit')) { - form.on('submit', function(submitEvent) { - var emulated = form.fire('emulated:submit', submitEvent, true); - // if custom event received preventDefault, cancel the real one too - if (emulated.returnValue === false) submitEvent.preventDefault(); - }); - form.store('emulated:submit', true); - } - }); - } - - if (!changeBubbles) { - // discover form inputs on the page - document.on('focusin', 'input, select, textarea', function(focusEvent, input) { - // special handler for real "change" events - if (!input.retrieve('emulated:change')) { - input.on('change', function(changeEvent) { - input.fire('emulated:change', changeEvent, true); - }); - input.store('emulated:change', true); - } - }); - } - - function handleRemote(element) { - var method, url, params; - - var event = element.fire("ajax:before"); - if (event.stopped) return false; - - if (element.tagName.toLowerCase() === 'form') { - method = element.readAttribute('method') || 'post'; - url = element.readAttribute('action'); - // serialize the form with respect to the submit button that was pressed - params = element.serialize({ submit: element.retrieve('rails:submit-button') }); - // clear the pressed submit button information - element.store('rails:submit-button', null); - } else { - method = element.readAttribute('data-method') || 'get'; - url = element.readAttribute('href'); - params = {}; - } - - new Ajax.Request(url, { - method: method, - parameters: params, - evalScripts: true, - - onCreate: function(response) { element.fire("ajax:create", response); }, - onComplete: function(response) { element.fire("ajax:complete", response); }, - onSuccess: function(response) { element.fire("ajax:success", response); }, - onFailure: function(response) { element.fire("ajax:failure", response); } - }); - - element.fire("ajax:after"); - } - - function insertHiddenField(form, name, value) { - form.insert(new Element('input', { type: 'hidden', name: name, value: value })); - } - - function handleMethod(element) { - var method = element.readAttribute('data-method'), - url = element.readAttribute('href'), - csrf_param = $$('meta[name=csrf-param]')[0], - csrf_token = $$('meta[name=csrf-token]')[0]; - - var form = new Element('form', { method: "POST", action: url, style: "display: none;" }); - $(element.parentNode).insert(form); - - if (method !== 'post') { - insertHiddenField(form, '_method', method); - } - - if (csrf_param) { - insertHiddenField(form, csrf_param.readAttribute('content'), csrf_token.readAttribute('content')); - } - - form.submit(); - } - - function disableFormElements(form) { - form.select('input[type=submit][data-disable-with]').each(function(input) { - input.store('rails:original-value', input.getValue()); - input.setValue(input.readAttribute('data-disable-with')).disable(); - }); - } - - function enableFormElements(form) { - form.select('input[type=submit][data-disable-with]').each(function(input) { - input.setValue(input.retrieve('rails:original-value')).enable(); - }); - } - - function allowAction(element) { - var message = element.readAttribute('data-confirm'); - return !message || confirm(message); - } - - document.on('click', 'a[data-confirm], a[data-remote], a[data-method]', function(event, link) { - if (!allowAction(link)) { - event.stop(); - return false; - } - - if (link.readAttribute('data-remote')) { - handleRemote(link); - event.stop(); - } else if (link.readAttribute('data-method')) { - handleMethod(link); - event.stop(); - } - }); - - document.on("click", "form input[type=submit], form button[type=submit], form button:not([type])", function(event, button) { - // register the pressed submit button - event.findElement('form').store('rails:submit-button', button.name || false); - }); - - document.on("submit", function(event) { - var form = event.findElement(); - - if (!allowAction(form)) { - event.stop(); - return false; - } - - if (form.readAttribute('data-remote')) { - handleRemote(form); - event.stop(); - } else { - disableFormElements(form); - } - }); - - document.on('ajax:create', 'form', function(event, form) { - if (form == event.findElement()) disableFormElements(form); - }); - - document.on('ajax:complete', 'form', function(event, form) { - if (form == event.findElement()) enableFormElements(form); - }); -})(); diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 126aadb88d..2bfe8b09f3 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -119,19 +119,10 @@ task :default => :test if mountable? copy_file "#{app_templates_dir}/app/assets/javascripts/application.js.tt", "app/assets/javascripts/application.js" - copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}.js", - "vendor/assets/javascripts/#{options[:javascript]}.js" - copy_file "#{app_templates_dir}/vendor/assets/javascripts/#{options[:javascript]}_ujs.js", - "vendor/assets/javascripts/#{options[:javascript]}_ujs.js" - - if options[:javascript] == "prototype" - copy_file "#{app_templates_dir}/vendor/assets/javascripts/controls.js", - "vendor/assets/javascripts/controls.js" - copy_file "#{app_templates_dir}/vendor/assets/javascripts/dragdrop.js", - "vendor/assets/javascripts/dragdrop.js" - copy_file "#{app_templates_dir}/vendor/assets/javascripts/effects.js", - "vendor/assets/javascripts/effects.js" - end + copy_file "#{app_templates_dir}/vendor/assets/javascripts/jquery.js", + "vendor/assets/javascripts/jquery.js" + copy_file "#{app_templates_dir}/vendor/assets/javascripts/jquery_ujs.js", + "vendor/assets/javascripts/jquery_ujs.js" elsif full? empty_directory_with_gitkeep "app/assets/javascripts" end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 1902484301..3cf92aed07 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -174,31 +174,12 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_javascript_is_skipped_if_required run_generator [destination_root, "--skip-javascript"] - assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/ + assert_file "config/application.rb", /^\s+# config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/ assert_file "app/assets/javascripts/application.js" assert_no_file "vendor/assets/javascripts/jquery.js" assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end - def test_config_prototype_javascript_library - run_generator [destination_root, "-j", "prototype"] - assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/ - assert_file "app/assets/javascripts/application.js" - assert_file "vendor/assets/javascripts/prototype.js" - assert_file "vendor/assets/javascripts/effects.js" - assert_file "vendor/assets/javascripts/dragdrop.js" - assert_file "vendor/assets/javascripts/controls.js" - assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/ - end - - def test_config_jquery_javascript_library - run_generator [destination_root, "-j", "jquery"] - assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(prototype effects dragdrop controls rails\)/ - assert_file "app/assets/javascripts/application.js" - assert_file "vendor/assets/javascripts/jquery.js" - assert_file "vendor/assets/javascripts/jquery_ujs.js", /jQuery/ - end - def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]) diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index fb956a8335..ae4fa6e596 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -114,16 +114,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end - def test_config_prototype_javascript_library - run_generator [destination_root, "-j", "prototype", "--mountable"] - assert_file "app/assets/javascripts/application.js" - assert_file "vendor/assets/javascripts/prototype.js" - assert_file "vendor/assets/javascripts/effects.js" - assert_file "vendor/assets/javascripts/dragdrop.js" - assert_file "vendor/assets/javascripts/controls.js" - assert_file "vendor/assets/javascripts/prototype_ujs.js", /prototype/ - end - def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) -- cgit v1.2.3 From 754c2dd2e139f13753565811aa55aa9fbedb1376 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 30 Apr 2011 02:16:50 -0300 Subject: Change from LH to github way of autoclosing issues --- railties/guides/source/contributing_to_ruby_on_rails.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index cbc4acfeca..4f7a58f01e 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -323,10 +323,10 @@ h4. Commit Your Changes When you're happy with the code on your computer, you need to commit the changes to git: -$ git commit -a -m "Here is a commit message [#ticket_number state:committed]" +$ git commit -a -m "Here is a commit message. Closes #issue_number" -NOTE: By adding '[#ticket_number state:committed]' at the end of your commit message, the ticket will automatically change its status to commited once your patch is pushed to the repository. +NOTE: By adding 'Closes #issue_number' at the end of your commit message, the issue will automatically change its status to closed once your patch is pushed to the repository. h4. Update master -- cgit v1.2.3 From b84b759ec60b5005fea183186639408fcf903450 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sat, 30 Apr 2011 20:35:25 -0300 Subject: Improved ActiveModel Observing docs. --- activemodel/lib/active_model/observing.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index c1ac4eb4af..cfe71074a5 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -56,6 +56,8 @@ module ActiveModel end # Add a new observer to the pool. + # The new observer needs to respond to 'update', otherwise it + # raises an +ArgumentError+ exception. def add_observer(observer) unless observer.respond_to? :update raise ArgumentError, "observer needs to respond to `update'" @@ -144,8 +146,8 @@ module ActiveModel # Observers will by default be mapped to the class with which they share a # name. So CommentObserver will be tied to observing Comment, ProductManagerObserver # to ProductManager, and so on. If you want to name your observer differently than - # the class you're interested in observing, you can use the Observer.observe class - # method which takes either the concrete class (Product) or a symbol for that + # the class you're interested in observing, you can use the Observer.observe + # class method which takes either the concrete class (Product) or a symbol for that # class (:product): # # class AuditObserver < ActiveModel::Observer -- cgit v1.2.3 From e14e696e9627b0e7fb903bc31b2b72070773c0a9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 30 Apr 2011 17:27:25 -0700 Subject: fixing arel requirements for released arel --- Gemfile | 2 +- activerecord/activerecord.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 8b6ddbd7c9..6cbc1bbebc 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ gemspec if ENV['AREL'] gem "arel", :path => ENV['AREL'] else - gem "arel", :git => "git://github.com/rails/arel.git" + gem "arel", '~> 2.1.0' end gem "rack", :git => "git://github.com/rack/rack.git" diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index c3cd76a714..9d4cbbc150 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -22,6 +22,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 2.0.2') + s.add_dependency('arel', '~> 2.1.0') s.add_dependency('tzinfo', '~> 0.3.23') end -- cgit v1.2.3 From 1d7c0336ef18cfde7b55911f60d912b5feba2b68 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 30 Apr 2011 17:26:42 -0700 Subject: using bind parameters for updates --- .../connection_adapters/abstract/database_statements.rb | 15 +++++++++++---- .../active_record/connection_adapters/mysql2_adapter.rb | 1 + .../active_record/connection_adapters/mysql_adapter.rb | 1 + .../connection_adapters/postgresql_adapter.rb | 1 + .../active_record/connection_adapters/sqlite_adapter.rb | 1 + activerecord/lib/active_record/relation.rb | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6d52cc344d..3045e30407 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -55,20 +55,27 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) end - # Executes insert +sql+ statement in the context of this connection using + # Executes insert +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is the logged along with # the executed +sql+ statement. def exec_insert(sql, name, binds) exec_query(sql, name, binds) end - # Executes delete +sql+ statement in the context of this connection using + # Executes delete +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is the logged along with # the executed +sql+ statement. def exec_delete(sql, name, binds) exec_query(sql, name, binds) end + # Executes update +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is the logged along with + # the executed +sql+ statement. + def exec_update(sql, name, binds) + exec_query(sql, name, binds) + end + # Returns the last auto-generated ID from the affected table. # # +id_value+ will be returned unless the value is nil, in @@ -84,8 +91,8 @@ module ActiveRecord end # Executes the update statement and returns the number of rows affected. - def update(sql, name = nil) - update_sql(sql, name) + def update(sql, name = nil, binds = []) + exec_update(sql, name, binds) end # Executes the delete statement and returns the number of rows affected. diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 199bf7d494..8973544028 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -297,6 +297,7 @@ module ActiveRecord execute sql.gsub('?') { quote(*binds.shift.reverse) }, name @connection.affected_rows end + alias :exec_update :exec_delete def last_inserted_id(result) @connection.last_id diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 07608ea6a7..356aaf0ebe 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -455,6 +455,7 @@ module ActiveRecord end end end + alias :exec_update :exec_delete def begin_db_transaction #:nodoc: exec_without_stmt "BEGIN" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f3e0248e39..3b0d960acc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -548,6 +548,7 @@ module ActiveRecord affected end end + alias :exec_update :exec_delete def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ad511a0731..04367657ec 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -182,6 +182,7 @@ module ActiveRecord exec_query(sql, name, binds) @connection.changes end + alias :exec_update :exec_delete def last_inserted_id(result) @connection.last_insert_row_id diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 658a949331..ae9afad48a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -220,7 +220,7 @@ module ActiveRecord stmt.take limit if limit stmt.order(*order) stmt.key = table[primary_key] - @klass.connection.update stmt.to_sql + @klass.connection.update stmt.to_sql, 'SQL', bind_values end end -- cgit v1.2.3 From 9274a5744be66581a1cf53c323c6d7555412b3d5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 30 Apr 2011 17:37:58 -0700 Subject: fixing test warnings --- activerecord/test/cases/identity_map_test.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index 199e59657d..2238529f0f 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -137,8 +137,6 @@ class IdentityMapTest < ActiveRecord::TestCase assert_equal(["name"], swistak.changed) assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) - s = Subscriber.find('swistak') - assert swistak.name_changed? assert_equal("Swistak Sreberkowiec", swistak.name) end @@ -149,8 +147,6 @@ class IdentityMapTest < ActiveRecord::TestCase Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"}) - s = Subscriber.find('swistak') - assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) assert_equal("Swistak Sreberkowiec", swistak.name) end @@ -163,8 +159,6 @@ class IdentityMapTest < ActiveRecord::TestCase Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"}) - s = Subscriber.find('swistak') - assert_equal("Swistak Sreberkowiec", swistak.name) assert_equal({"name"=>["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) assert swistak.name_changed? @@ -175,7 +169,7 @@ class IdentityMapTest < ActiveRecord::TestCase pirate.birds.create!(:name => 'Posideons Killer') pirate.birds.create!(:name => 'Killer bandita Dionne') - posideons, killer = pirate.birds + posideons, _ = pirate.birds pirate.reload -- cgit v1.2.3 From a0656989c47c29c87c4f243ce3695c39777c67b8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 30 Apr 2011 17:40:53 -0700 Subject: fixing more test warnings in 1.9.3 --- .../test/cases/associations/belongs_to_associations_test.rb | 6 +++--- activerecord/test/cases/associations/eager_test.rb | 4 ++-- .../test/cases/associations/has_many_through_associations_test.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 9006914508..7518bc19f9 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -576,9 +576,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_polymorphic_counter_cache - tagging = taggings(:welcome_general) - post = posts(:welcome) - comment = comments(:greetings) + tagging = taggings(:welcome_general) + post = post = posts(:welcome) + comment = comments(:greetings) assert_difference 'post.reload.taggings_count', -1 do assert_difference 'comment.reload.taggings_count', +1 do diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 9bc7910fc6..3e92a77830 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -170,10 +170,10 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal [comment], category.posts[0].comments end end - + def test_associations_loaded_for_all_records post = Post.create!(:title => 'foo', :body => "I like cars!") - comment = SpecialComment.create!(:body => 'Come on!', :post => post) + SpecialComment.create!(:body => 'Come on!', :post => post) first_category = Category.create! :name => 'First!', :posts => [post] second_category = Category.create! :name => 'Second!', :posts => [post] 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 70a4e06dbe..89117593fd 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -760,7 +760,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_primary_key_option_on_source post = posts(:welcome) category = categories(:general) - categorization = Categorization.create!(:post_id => post.id, :named_category_name => category.name) + Categorization.create!(:post_id => post.id, :named_category_name => category.name) assert_equal [category], post.named_categories assert_equal [category.name], post.named_category_ids # checks when target loaded -- cgit v1.2.3 From 4760464efb94297d14d6a35a53da0459ee4d7d7e Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 1 May 2011 15:46:46 +0530 Subject: fix GitHub guides url --- railties/guides/source/contributing_to_ruby_on_rails.textile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index d7090ef675..61a17b6a56 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -44,7 +44,7 @@ Ruby on Rails uses git for source code control. The "git homepage":http://git-sc * "Everyday Git":http://www.kernel.org/pub/software/scm/git/docs/everyday.html will teach you just enough about git to get by. * The "PeepCode screencast":https://peepcode.com/products/git on git ($9) is easier to follow. -* "GitHub":https://github.com/guides/home offers links to a variety of git resources. +* "GitHub":http://help.github.com offers links to a variety of git resources. * "Pro Git":http://progit.org/book/ is an entire book about git with a Creative Commons license. h4. Clone the Ruby on Rails Repository @@ -232,7 +232,7 @@ You can also help out by examining pull requests that have been submitted to Rub $ git checkout -b testing_branch
    -Then you can use their remote branch to update your codebase. For example, let's say the github user JohnSmith has forked and pushed to the master branch located at http://github.com/JohnSmith/rails. +Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to the master branch located at https://github.com/JohnSmith/rails. $ git remote add JohnSmith git://github.com/JohnSmith/rails.git -- cgit v1.2.3 From d1f765644891d6cf6555003de52cb6d58a4da33b Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sun, 1 May 2011 16:15:53 +0530 Subject: remove info about adding verified tag --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index 61a17b6a56..a7d792f88d 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -252,7 +252,7 @@ Once you're happy that the pull request contains a good change, comment on the G I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too.
    -If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the patch. Once three people have approved it, add the "verified" tag. This will bring it to the attention of a core team member who will review the changes looking for the same kinds of things. +If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. h3. Contributing to the Rails Documentation -- cgit v1.2.3 From e68b7a001de0959fcfb90b9d8c82b6b0bc1ccfb6 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sun, 1 May 2011 13:15:15 +0200 Subject: (temporary hack) generate a main file for RDoc escaping "Rails" RDoc autolinks the word "Rails" to the doc page for the Rails module. But README.rdoc is displayed in the home page at GitHub and the slashes are visible there, which is weird. We leave by now the slashes off in the file, and generate a second file for the API with them. --- .gitignore | 1 + README.rdoc | 16 ++++++++-------- Rakefile | 16 +++++++++++++--- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 8daa1e4dcd..be764143aa 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ railties/doc railties/guides/output railties/tmp .rvmrc +RDOC_MAIN.rdoc \ No newline at end of file diff --git a/README.rdoc b/README.rdoc index 216a122c66..143fdfeb75 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,6 +1,6 @@ -== Welcome to \Rails +== Welcome to Rails -\Rails is a web-application framework that includes everything needed to create +Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Control pattern. This pattern splits the view (also called the presentation) into "dumb" @@ -11,7 +11,7 @@ persist themselves to a database. The controller handles the incoming requests (such as Save New Account, Update Product, Show Post) by manipulating the model and directing data to the view. -In \Rails, the model is handled by what's called an object-relational mapping +In Rails, the model is handled by what's called an object-relational mapping layer entitled Active Record. This layer allows you to present the data from database rows as objects and embellish these data objects with business logic methods. You can read more about Active Record in its @@ -22,17 +22,17 @@ layers by its two parts: Action View and Action Controller. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack that is much more separate. Each of these packages can be used independently outside of -\Rails. You can read more about Action Pack in its +Rails. You can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. == Getting Started -1. Install \Rails at the command prompt if you haven't yet: +1. Install Rails at the command prompt if you haven't yet: gem install rails -2. At the command prompt, create a new \Rails application: +2. At the command prompt, create a new Rails application: rails new myapp @@ -59,10 +59,10 @@ more separate. Each of these packages can be used independently outside of == Contributing -We encourage you to contribute to Ruby on \Rails! Please check out the {Contributing to Rails +We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails guide}[http://edgeguides.rubyonrails.org/contributing_to_rails.html] for guidelines about how to proceed. {Join us}[http://contributors.rubyonrails.org]! == License -Ruby on \Rails is released under the MIT license. +Ruby on Rails is released under the MIT license. diff --git a/Rakefile b/Rakefile index 607ce01fd0..92b2e77963 100755 --- a/Rakefile +++ b/Rakefile @@ -49,14 +49,24 @@ end desc "Generate documentation for the Rails framework" RDoc::Task.new do |rdoc| + RDOC_MAIN = 'RDOC_MAIN.rdoc' + + rdoc.before_running_rdoc do + rdoc_main = File.read('README.rdoc') + rdoc_main.gsub!(/\b(?=Rails)\b/) { '\\' } + File.open(RDOC_MAIN, 'w') do |f| + f.write(rdoc_main) + end + + rdoc.rdoc_files.include(RDOC_MAIN) + end + rdoc.rdoc_dir = 'doc/rdoc' rdoc.title = "Ruby on Rails Documentation" rdoc.options << '-f' << 'horo' rdoc.options << '-c' << 'utf-8' - rdoc.options << '-m' << 'README.rdoc' - - rdoc.rdoc_files.include('README.rdoc') + rdoc.options << '-m' << RDOC_MAIN rdoc.rdoc_files.include('railties/CHANGELOG') rdoc.rdoc_files.include('railties/MIT-LICENSE') -- cgit v1.2.3 From d08f65118cc328de5493a68db33a155487f5fceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 10:33:30 +0200 Subject: Start abstracting the renderer. --- actionpack/lib/action_view.rb | 2 +- actionpack/lib/action_view/base.rb | 5 +- actionpack/lib/action_view/context.rb | 6 +- actionpack/lib/action_view/helpers/form_helper.rb | 5 + actionpack/lib/action_view/partials.rb | 226 --------------------- .../lib/action_view/renderer/abstract_renderer.rb | 4 +- .../lib/action_view/renderer/partial_renderer.rb | 217 +++++++++++++++++++- actionpack/lib/action_view/renderer/renderer.rb | 70 +++++++ actionpack/lib/action_view/rendering.rb | 46 +---- 9 files changed, 301 insertions(+), 280 deletions(-) delete mode 100644 actionpack/lib/action_view/partials.rb create mode 100644 actionpack/lib/action_view/renderer/renderer.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 4547aceb28..69c50a056c 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -34,13 +34,13 @@ module ActionView autoload :Context autoload :Helpers autoload :LookupContext - autoload :Partials autoload :PathSet autoload :Rendering autoload :Template autoload :TestCase autoload_under "renderer" do + autoload :Renderer autoload :AbstractRenderer autoload :PartialRenderer autoload :TemplateRenderer diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 87501d5b88..c1dbbe1613 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -131,7 +131,7 @@ module ActionView #:nodoc: # # More builder documentation can be found at http://builder.rubyforge.org. class Base - include Helpers, Rendering, Partials, ::ERB::Util, Context + include Helpers, Rendering, ::ERB::Util, Context # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc @@ -162,6 +162,7 @@ module ActionView #:nodoc: attr_accessor :_template, :_view_flow attr_internal :request, :controller, :config, :assigns, :lookup_context + # TODO Consider removing those setters once we have the renderer in place. delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, @@ -199,6 +200,8 @@ module ActionView #:nodoc: @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? lookup_context : ActionView::LookupContext.new(lookup_context) @_lookup_context.formats = formats if formats + + @_renderer = ActionView::Renderer.new(@_lookup_context, self) end def controller_path diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index a2a64de206..710cdc613d 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -31,10 +31,6 @@ module ActionView # template and provides the output buffer. module Context include CompiledTemplates - attr_accessor :output_buffer - - def convert_to_model(object) - object.respond_to?(:to_model) ? object.to_model : object - end + attr_accessor :output_buffer, :view_renderer, :view_flow end end \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index efe30441b1..68db17c254 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -102,6 +102,11 @@ module ActionView include FormTagHelper include UrlHelper + # Converts the given object to an ActiveModel compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + # Creates a form and a scope around a specific model object that is used # as a base for questioning about values for the fields. # diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb deleted file mode 100644 index c181689e62..0000000000 --- a/actionpack/lib/action_view/partials.rb +++ /dev/null @@ -1,226 +0,0 @@ -require 'active_support/core_ext/object/blank' - -module ActionView - # = Action View Partials - # - # There's also a convenience method for rendering sub templates within the current controller that depends on a - # single object (we call this kind of sub templates for partials). It relies on the fact that partials should - # follow the naming convention of being prefixed with an underscore -- as to separate them from regular - # templates that could be rendered on their own. - # - # In a template for Advertiser#account: - # - # <%= render :partial => "account" %> - # - # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable - # +account+ to the template for display. - # - # In another template for Advertiser#buy, we could have: - # - # <%= render :partial => "account", :locals => { :account => @buyer } %> - # - # <% @advertisements.each do |ad| %> - # <%= render :partial => "ad", :locals => { :ad => ad } %> - # <% end %> - # - # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then - # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. - # - # == The :as and :object options - # - # By default ActionView::Partials::PartialRenderer has its object in a local variable with the same - # name as the template. So, given - # - # <%= render :partial => "contract" %> - # - # within contract we'll get @contract in the local variable +contract+, as if we had written - # - # <%= render :partial => "contract", :locals => { :contract => @contract } %> - # - # With the :as option we can specify a different name for said local variable. For example, if we - # wanted it to be +agreement+ instead of +contract+ we'd do: - # - # <%= render :partial => "contract", :as => 'agreement' %> - # - # The :object option can be used to directly specify which object is rendered into the partial; - # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. - # - # Revisiting a previous example we could have written this code: - # - # <%= render :partial => "account", :object => @buyer %> - # - # <% @advertisements.each do |ad| %> - # <%= render :partial => "ad", :object => ad %> - # <% end %> - # - # The :object and :as options can be used together. - # - # == Rendering a collection of partials - # - # The example of partial use describes a familiar pattern where a template needs to iterate over an array and - # render a sub template for each of the elements. This pattern has been implemented as a single method that - # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined - # example in "Using partials" can be rewritten with a single line: - # - # <%= render :partial => "ad", :collection => @advertisements %> - # - # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An - # iteration counter will automatically be made available to the template with a name of the form - # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. - # - # The :as option may be used when rendering partials. - # - # You can specify a partial to be rendered between elements via the :spacer_template option. - # The following example will render advertiser/_ad_divider.html.erb between each ad partial: - # - # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %> - # - # If the given :collection is nil or empty, render will return nil. This will allow you - # to specify a text which will displayed instead by using this form: - # - # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %> - # - # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also - # just keep domain objects, like Active Records, in there. - # - # == Rendering shared partials - # - # Two controllers can share a set of partials and render them like this: - # - # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> - # - # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. - # - # == Rendering objects with the RecordIdentifier - # - # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if - # you're following its conventions for RecordIdentifier#partial_path. Examples: - # - # # @account is an Account instance, so it uses the RecordIdentifier to replace - # # <%= render :partial => "accounts/account", :locals => { :account => @account} %> - # <%= render :partial => @account %> - # - # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace - # # <%= render :partial => "posts/post", :collection => @posts %> - # <%= render :partial => @posts %> - # - # == Rendering the default case - # - # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand - # defaults of render to render partials. Examples: - # - # # Instead of <%= render :partial => "account" %> - # <%= render "account" %> - # - # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %> - # <%= render "account", :account => @buyer %> - # - # # @account is an Account instance, so it uses the RecordIdentifier to replace - # # <%= render :partial => "accounts/account", :locals => { :account => @account } %> - # <%= render(@account) %> - # - # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace - # # <%= render :partial => "posts/post", :collection => @posts %> - # <%= render(@posts) %> - # - # == Rendering partials with layouts - # - # Partials can have their own layouts applied to them. These layouts are different than the ones that are - # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types - # of users: - # - # <%# app/views/users/index.html.erb &> - # Here's the administrator: - # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %> - # - # Here's the editor: - # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %> - # - # <%# app/views/users/_user.html.erb &> - # Name: <%= user.name %> - # - # <%# app/views/users/_administrator.html.erb &> - #
    - # Budget: $<%= user.budget %> - # <%= yield %> - #
    - # - # <%# app/views/users/_editor.html.erb &> - #
    - # Deadline: <%= user.deadline %> - # <%= yield %> - #
    - # - # ...this will return: - # - # Here's the administrator: - #
    - # Budget: $<%= user.budget %> - # Name: <%= user.name %> - #
    - # - # Here's the editor: - #
    - # Deadline: <%= user.deadline %> - # Name: <%= user.name %> - #
    - # - # You can also apply a layout to a block within any template: - # - # <%# app/views/users/_chief.html.erb &> - # <%= render(:layout => "administrator", :locals => { :user => chief }) do %> - # Title: <%= chief.title %> - # <% end %> - # - # ...this will return: - # - #
    - # Budget: $<%= user.budget %> - # Title: <%= chief.name %> - #
    - # - # As you can see, the :locals hash is shared between both the partial and its layout. - # - # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass - # an array to layout and treat it as an enumerable. - # - # <%# app/views/users/_user.html.erb &> - #
    - # Budget: $<%= user.budget %> - # <%= yield user %> - #
    - # - # <%# app/views/users/index.html.erb &> - # <%= render :layout => @users do |user| %> - # Title: <%= user.title %> - # <% end %> - # - # This will render the layout for each user and yield to the block, passing the user, each time. - # - # You can also yield multiple times in one layout and use block arguments to differentiate the sections. - # - # <%# app/views/users/_user.html.erb &> - #
    - # <%= yield user, :header %> - # Budget: $<%= user.budget %> - # <%= yield user, :footer %> - #
    - # - # <%# app/views/users/index.html.erb &> - # <%= render :layout => @users do |user, section| %> - # <%- case section when :header -%> - # Title: <%= user.title %> - # <%- when :footer -%> - # Deadline: <%= user.deadline %> - # <%- end -%> - # <% end %> - module Partials - def _render_partial(options, &block) #:nodoc: - _partial_renderer.setup(options, block).render - end - - def _partial_renderer #:nodoc: - @_partial_renderer ||= PartialRenderer.new(self) - end - end -end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 4a52b3172e..37bc0ae244 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -3,9 +3,9 @@ module ActionView delegate :find_template, :template_exists?, :with_fallbacks, :update_details, :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context - def initialize(view) + def initialize(view, lookup_context) @view = view - @lookup_context = view.lookup_context + @lookup_context = lookup_context end def render diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 10cd37d56f..83efc95f39 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,8 +1,223 @@ +require 'active_support/core_ext/object/blank' + module ActionView + # = Action View Partials + # + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. + # + # In a template for Advertiser#account: + # + # <%= render :partial => "account" %> + # + # This would render "advertiser/_account.html.erb" and pass the instance variable @account in as a local variable + # +account+ to the template for display. + # + # In another template for Advertiser#buy, we could have: + # + # <%= render :partial => "account", :locals => { :account => @buyer } %> + # + # <% @advertisements.each do |ad| %> + # <%= render :partial => "ad", :locals => { :ad => ad } %> + # <% end %> + # + # This would first render "advertiser/_account.html.erb" with @buyer passed in as the local variable +account+, then + # render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. + # + # == The :as and :object options + # + # By default ActionView::Partials::PartialRenderer has its object in a local variable with the same + # name as the template. So, given + # + # <%= render :partial => "contract" %> + # + # within contract we'll get @contract in the local variable +contract+, as if we had written + # + # <%= render :partial => "contract", :locals => { :contract => @contract } %> + # + # With the :as option we can specify a different name for said local variable. For example, if we + # wanted it to be +agreement+ instead of +contract+ we'd do: + # + # <%= render :partial => "contract", :as => 'agreement' %> + # + # The :object option can be used to directly specify which object is rendered into the partial; + # useful when the template's object is elsewhere, in a different ivar or in a local variable for instance. + # + # Revisiting a previous example we could have written this code: + # + # <%= render :partial => "account", :object => @buyer %> + # + # <% @advertisements.each do |ad| %> + # <%= render :partial => "ad", :object => ad %> + # <% end %> + # + # The :object and :as options can be used together. + # + # == Rendering a collection of partials + # + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: + # + # <%= render :partial => "ad", :collection => @advertisements %> + # + # This will render "advertiser/_ad.html.erb" and pass the local variable +ad+ to the template for display. An + # iteration counter will automatically be made available to the template with a name of the form + # +partial_name_counter+. In the case of the example above, the template would be fed +ad_counter+. + # + # The :as option may be used when rendering partials. + # + # You can specify a partial to be rendered between elements via the :spacer_template option. + # The following example will render advertiser/_ad_divider.html.erb between each ad partial: + # + # <%= render :partial => "ad", :collection => @advertisements, :spacer_template => "ad_divider" %> + # + # If the given :collection is nil or empty, render will return nil. This will allow you + # to specify a text which will displayed instead by using this form: + # + # <%= render(:partial => "ad", :collection => @advertisements) || "There's no ad to be displayed" %> + # + # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also + # just keep domain objects, like Active Records, in there. + # + # == Rendering shared partials + # + # Two controllers can share a set of partials and render them like this: + # + # <%= render :partial => "advertisement/ad", :locals => { :ad => @advertisement } %> + # + # This will render the partial "advertisement/_ad.html.erb" regardless of which controller this is being called from. + # + # == Rendering objects with the RecordIdentifier + # + # Instead of explicitly naming the location of a partial, you can also let the RecordIdentifier do the work if + # you're following its conventions for RecordIdentifier#partial_path. Examples: + # + # # @account is an Account instance, so it uses the RecordIdentifier to replace + # # <%= render :partial => "accounts/account", :locals => { :account => @account} %> + # <%= render :partial => @account %> + # + # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace + # # <%= render :partial => "posts/post", :collection => @posts %> + # <%= render :partial => @posts %> + # + # == Rendering the default case + # + # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand + # defaults of render to render partials. Examples: + # + # # Instead of <%= render :partial => "account" %> + # <%= render "account" %> + # + # # Instead of <%= render :partial => "account", :locals => { :account => @buyer } %> + # <%= render "account", :account => @buyer %> + # + # # @account is an Account instance, so it uses the RecordIdentifier to replace + # # <%= render :partial => "accounts/account", :locals => { :account => @account } %> + # <%= render(@account) %> + # + # # @posts is an array of Post instances, so it uses the RecordIdentifier to replace + # # <%= render :partial => "posts/post", :collection => @posts %> + # <%= render(@posts) %> + # + # == Rendering partials with layouts + # + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: + # + # <%# app/views/users/index.html.erb &> + # Here's the administrator: + # <%= render :partial => "user", :layout => "administrator", :locals => { :user => administrator } %> + # + # Here's the editor: + # <%= render :partial => "user", :layout => "editor", :locals => { :user => editor } %> + # + # <%# app/views/users/_user.html.erb &> + # Name: <%= user.name %> + # + # <%# app/views/users/_administrator.html.erb &> + #
    + # Budget: $<%= user.budget %> + # <%= yield %> + #
    + # + # <%# app/views/users/_editor.html.erb &> + #
    + # Deadline: <%= user.deadline %> + # <%= yield %> + #
    + # + # ...this will return: + # + # Here's the administrator: + #
    + # Budget: $<%= user.budget %> + # Name: <%= user.name %> + #
    + # + # Here's the editor: + #
    + # Deadline: <%= user.deadline %> + # Name: <%= user.name %> + #
    + # + # You can also apply a layout to a block within any template: + # + # <%# app/views/users/_chief.html.erb &> + # <%= render(:layout => "administrator", :locals => { :user => chief }) do %> + # Title: <%= chief.title %> + # <% end %> + # + # ...this will return: + # + #
    + # Budget: $<%= user.budget %> + # Title: <%= chief.name %> + #
    + # + # As you can see, the :locals hash is shared between both the partial and its layout. + # + # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass + # an array to layout and treat it as an enumerable. + # + # <%# app/views/users/_user.html.erb &> + #
    + # Budget: $<%= user.budget %> + # <%= yield user %> + #
    + # + # <%# app/views/users/index.html.erb &> + # <%= render :layout => @users do |user| %> + # Title: <%= user.title %> + # <% end %> + # + # This will render the layout for each user and yield to the block, passing the user, each time. + # + # You can also yield multiple times in one layout and use block arguments to differentiate the sections. + # + # <%# app/views/users/_user.html.erb &> + #
    + # <%= yield user, :header %> + # Budget: $<%= user.budget %> + # <%= yield user, :footer %> + #
    + # + # <%# app/views/users/index.html.erb &> + # <%= render :layout => @users do |user, section| %> + # <%- case section when :header -%> + # Title: <%= user.title %> + # <%- when :footer -%> + # Deadline: <%= user.deadline %> + # <%- end -%> + # <% end %> class PartialRenderer < AbstractRenderer #:nodoc: PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - def initialize(view) + def initialize(view, *) super @partial_names = PARTIAL_NAMES[@view.controller.class.name] end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb new file mode 100644 index 0000000000..f0ee103d80 --- /dev/null +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -0,0 +1,70 @@ +module ActionView + # This is the main entry point for rendering. It basically delegates + # to other objects like TemplateRenderer and PartialRenderer which + # actually renders the template. + class Renderer + attr_accessor :lookup_context + + # TODO: render_context should not be an initialization parameter + def initialize(lookup_context, render_context) + @render_context = render_context + @lookup_context = lookup_context + @view_flow = OutputFlow.new + end + + # Returns the result of a render that's dictated by the options hash. The primary options are: + # + # * :partial - See ActionView::Partials. + # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. + # * :inline - Renders an inline template similar to how it's done in the controller. + # * :text - Renders the text passed in out. + # + # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter + # as the locals hash. + def render(options = {}, locals = {}, &block) + case options + when Hash + if block_given? + _render_partial(options.merge(:partial => options[:layout]), &block) + elsif options.key?(:partial) + _render_partial(options) + else + _render_template(options) + end + else + _render_partial(:partial => options, :locals => locals) + end + end + + # Render but returns a valid Rack body. If fibers are defined, we return + # a streaming body that renders the template piece by piece. + # + # Note that partials are not supported to be rendered with streaming, + # so in such cases, we just wrap them in an array. + def render_body(options) + if options.key?(:partial) + [_render_partial(options)] + else + StreamingTemplateRenderer.new(@render_context, @lookup_context).render(options) + end + end + + private + + def _render_template(options) #:nodoc: + _template_renderer.render(options) + end + + def _template_renderer #:nodoc: + @_template_renderer ||= TemplateRenderer.new(@render_context, @lookup_context) + end + + def _render_partial(options, &block) #:nodoc: + _partial_renderer.setup(options, block).render + end + + def _partial_renderer #:nodoc: + @_partial_renderer ||= PartialRenderer.new(@render_context, @lookup_context) + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb index 2bce2fb045..017a27976c 100644 --- a/actionpack/lib/action_view/rendering.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -3,42 +3,8 @@ require 'active_support/core_ext/object/try' module ActionView # = Action View Rendering module Rendering - # Returns the result of a render that's dictated by the options hash. The primary options are: - # - # * :partial - See ActionView::Partials. - # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. - # * :inline - Renders an inline template similar to how it's done in the controller. - # * :text - Renders the text passed in out. - # - # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter - # as the locals hash. - def render(options = {}, locals = {}, &block) - case options - when Hash - if block_given? - _render_partial(options.merge(:partial => options[:layout]), &block) - elsif options.key?(:partial) - _render_partial(options) - else - _render_template(options) - end - else - _render_partial(:partial => options, :locals => locals) - end - end - - # Render but returns a valid Rack body. If fibers are defined, we return - # a streaming body that renders the template piece by piece. - # - # Note that partials are not supported to be rendered with streaming, - # so in such cases, we just wrap them in an array. - def render_body(options) - if options.key?(:partial) - [_render_partial(options)] - else - StreamingTemplateRenderer.new(self).render(options) - end - end + # This is temporary until we remove the renderer dependency from AV. + delegate :render, :render_body, :to => :@_renderer # Returns the contents that are yielded to a layout, given a name or a block. # @@ -102,13 +68,5 @@ module ActionView _layout_for(*args) end end - - def _render_template(options) #:nodoc: - _template_renderer.render(options) - end - - def _template_renderer #:nodoc: - @_template_renderer ||= TemplateRenderer.new(self) - end end end -- cgit v1.2.3 From bebaccdf4a3a17f2ead349cca891032e245655ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 11:14:38 +0200 Subject: Remove dependency from _template. --- actionpack/lib/action_view/base.rb | 3 ++- actionpack/lib/action_view/helpers/form_helper.rb | 2 +- actionpack/lib/action_view/helpers/translation_helper.rb | 4 ++-- actionpack/lib/action_view/template.rb | 14 +++----------- actionpack/lib/action_view/test_case.rb | 16 +++++++++++++--- actionpack/test/template/template_test.rb | 10 ++++------ actionpack/test/template/url_helper_test.rb | 2 +- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index c1dbbe1613..0866e808e6 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -159,7 +159,7 @@ module ActionView #:nodoc: end end - attr_accessor :_template, :_view_flow + attr_accessor :_view_flow attr_internal :request, :controller, :config, :assigns, :lookup_context # TODO Consider removing those setters once we have the renderer in place. @@ -191,6 +191,7 @@ module ActionView #:nodoc: @_virtual_path = nil @_view_flow = OutputFlow.new @output_buffer = nil + @virtual_path = nil if @_controller = controller @_request = controller.request if controller.respond_to?(:request) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 68db17c254..b6bb90a3ce 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -1172,7 +1172,7 @@ module ActionView class FormBuilder # The methods which wrap a form helper call. class_attribute :field_helpers - self.field_helpers = (FormHelper.instance_method_names - ['form_for']) + self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model) attr_accessor :object_name, :object, :options diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 26ebae6546..ec9bdd5320 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -63,8 +63,8 @@ module ActionView private def scope_key_by_partial(key) if key.to_s.first == "." - if (path = @_template && @_template.virtual_path) - path.gsub(%r{/_?}, ".") + key.to_s + if @virtual_path + @virtual_path.gsub(%r{/_?}, ".") + key.to_s else raise "Cannot use t(#{key.inspect}) shortcut because path is not available" end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 6dfc4f68ae..98ecd15aa0 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -139,15 +139,12 @@ module ActionView # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. def render(view, locals, buffer=nil, &block) - old_template, view._template = view._template, self ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do compile!(view) view.send(method_name, locals, buffer, &block) end rescue Exception => e handle_render_error(view, e) - ensure - view._template = old_template end def mime_type @@ -174,12 +171,7 @@ module ActionView end def inspect - @inspect ||= - if defined?(Rails.root) - identifier.sub("#{Rails.root}/", '') - else - identifier - end + @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", '') : identifier end protected @@ -264,9 +256,9 @@ module ActionView # encoding of the code source = <<-end_src def #{method_name}(local_assigns, output_buffer) - _old_output_buffer = @output_buffer;#{locals_code};#{code} + _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} ensure - @output_buffer = _old_output_buffer + @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer end end_src diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 5c74bf843a..14e032790d 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -147,9 +147,19 @@ module ActionView module Locals attr_accessor :locals - def _render_partial(options) - locals[options[:partial]] = options[:locals] - super(options) + def render(options = {}, local_assigns = {}) + case options + when Hash + if block_given? + locals[options[:layout]] = options[:locals] + elsif options.key?(:partial) + locals[options[:partial]] = options[:locals] + end + else + locals[options] = local_assigns + end + + super end end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 5c655d5b69..81fb34b80f 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -11,11 +11,9 @@ class TestERBTemplate < ActiveSupport::TestCase end class Context - attr_accessor :_template - def initialize @output_buffer = "original" - @_virtual_path = nil + @virtual_path = nil end def hello @@ -24,7 +22,7 @@ class TestERBTemplate < ActiveSupport::TestCase def partial ActionView::Template.new( - "<%= @_template.virtual_path %>", + "<%= @virtual_path %>", "partial", ERBHandler, :virtual_path => "partial" @@ -86,9 +84,9 @@ class TestERBTemplate < ActiveSupport::TestCase end def test_virtual_path - @template = new_template("<%= @_template.virtual_path %>" \ + @template = new_template("<%= @virtual_path %>" \ "<%= partial.render(self, {}) %>" \ - "<%= @_template.virtual_path %>") + "<%= @virtual_path %>") assert_equal "hellopartialhello", render end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index fc330f7a73..8d0f0124c2 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -9,7 +9,7 @@ class UrlHelperTest < ActiveSupport::TestCase # or request. # # In those cases, we'll set up a simple mock - attr_accessor :controller, :request, :_template + attr_accessor :controller, :request routes = ActionDispatch::Routing::RouteSet.new routes.draw do -- cgit v1.2.3 From 2f683fd870d0e4c5aff38510ef03c7e5144a1ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 11:53:09 +0200 Subject: Remove more dependencies from the view. --- actionpack/lib/action_view/base.rb | 6 +----- actionpack/lib/action_view/renderer/partial_renderer.rb | 14 ++++++++++---- actionpack/lib/action_view/renderer/renderer.rb | 1 + actionpack/lib/action_view/rendering.rb | 17 ++++++----------- actionpack/test/template/log_subscriber_test.rb | 7 +++---- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 0866e808e6..37cd4d9ddc 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -202,17 +202,13 @@ module ActionView #:nodoc: lookup_context : ActionView::LookupContext.new(lookup_context) @_lookup_context.formats = formats if formats - @_renderer = ActionView::Renderer.new(@_lookup_context, self) + @view_renderer = ActionView::Renderer.new(@_lookup_context, self) end def controller_path @controller_path ||= controller && controller.controller_path end - def controller_prefixes - @controller_prefixes ||= controller && controller._prefixes - end - ActiveSupport.run_load_hooks(:action_view, self) end end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 83efc95f39..0eeead2e5d 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -217,9 +217,11 @@ module ActionView class PartialRenderer < AbstractRenderer #:nodoc: PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } + # TODO Controller should not come from the view def initialize(view, *) super - @partial_names = PARTIAL_NAMES[@view.controller.class.name] + @controller = @view.controller + @partial_names = PARTIAL_NAMES[@controller.class.name] end def setup(options, block) @@ -292,7 +294,7 @@ module ActionView locals[as] = object content = @template.render(view, locals) do |*name| - view._block_layout_for(*name, &block) + view._layout_for(*name, &block) end content = layout.render(view, locals){ content } if layout @@ -301,6 +303,10 @@ module ActionView private + def controller_prefixes + @controller_prefixes ||= @controller && @controller._prefixes + end + def collection if @options.key?(:collection) collection = @options[:collection] @@ -324,7 +330,7 @@ module ActionView end def find_template(path=@path, locals=@locals.keys) - prefixes = path.include?(?/) ? [] : @view.controller_prefixes + prefixes = path.include?(?/) ? [] : controller_prefixes @lookup_context.find_template(path, prefixes, true, locals) end @@ -365,7 +371,7 @@ module ActionView object = object.to_model if object.respond_to?(:to_model) object.class.model_name.partial_path.dup.tap do |partial| - path = @view.controller_prefixes.first + path = controller_prefixes.first partial.insert(0, "#{File.dirname(path)}/") if partial.include?(?/) && path.include?(?/) end end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb index f0ee103d80..7b95c2dd92 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -6,6 +6,7 @@ module ActionView attr_accessor :lookup_context # TODO: render_context should not be an initialization parameter + # TODO: controller should be received on initialization def initialize(lookup_context, render_context) @render_context = render_context @lookup_context = lookup_context diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb index 017a27976c..9850965456 100644 --- a/actionpack/lib/action_view/rendering.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -4,7 +4,7 @@ module ActionView # = Action View Rendering module Rendering # This is temporary until we remove the renderer dependency from AV. - delegate :render, :render_body, :to => :@_renderer + delegate :render, :render_body, :to => :@view_renderer # Returns the contents that are yielded to a layout, given a name or a block. # @@ -52,20 +52,15 @@ module ActionView # Hello David # # - def _layout_for(*args) - name = args.first - name = :layout unless name.is_a?(Symbol) - @_view_flow.get(name).html_safe - end - - # Handle layout for calls from partials that supports blocks. - def _block_layout_for(*args, &block) + def _layout_for(*args, &block) name = args.first - if !name.is_a?(Symbol) && block + if name.is_a?(Symbol) + @_view_flow.get(name).html_safe + elsif block capture(*args, &block) else - _layout_for(*args) + @_view_flow.get(:layout).html_safe end end end diff --git a/actionpack/test/template/log_subscriber_test.rb b/actionpack/test/template/log_subscriber_test.rb index 8b8b005a1d..50e1cccd3b 100644 --- a/actionpack/test/template/log_subscriber_test.rb +++ b/actionpack/test/template/log_subscriber_test.rb @@ -9,7 +9,9 @@ class AVLogSubscriberTest < ActiveSupport::TestCase def setup super @old_logger = ActionController::Base.logger - @view = ActionView::Base.new(ActionController::Base.view_paths, {}) + @controller = Object.new + @controller.stubs(:_prefixes).returns(%w(test)) + @view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller) Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) ActionView::LogSubscriber.attach_to :action_view end @@ -57,7 +59,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_partial_with_implicit_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render(Customer.new("david"), :greeting => "hi") wait @@ -74,7 +75,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_collection_with_implicit_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") wait @@ -83,7 +83,6 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_collection_template_without_path - @view.stubs(:controller_prefixes).returns(%w(test)) @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") wait -- cgit v1.2.3 From 33cc001f9158463389a9c9c321de0dbdccb1df8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 12:16:31 +0200 Subject: More cleanup and moving responsibilities around. --- actionpack/lib/action_view.rb | 1 - actionpack/lib/action_view/base.rb | 19 +++-- actionpack/lib/action_view/context.rb | 84 ++++++++++++++++------ actionpack/lib/action_view/flows.rb | 2 +- .../lib/action_view/helpers/capture_helper.rb | 6 +- .../renderer/streaming_template_renderer.rb | 4 +- .../lib/action_view/renderer/template_renderer.rb | 2 +- actionpack/lib/action_view/rendering.rb | 67 ----------------- actionpack/test/template/capture_helper_test.rb | 10 +-- 9 files changed, 83 insertions(+), 112 deletions(-) delete mode 100644 actionpack/lib/action_view/rendering.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 69c50a056c..92b6f7c770 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -35,7 +35,6 @@ module ActionView autoload :Helpers autoload :LookupContext autoload :PathSet - autoload :Rendering autoload :Template autoload :TestCase diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 37cd4d9ddc..cde1c36bd9 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -131,7 +131,7 @@ module ActionView #:nodoc: # # More builder documentation can be found at http://builder.rubyforge.org. class Base - include Helpers, Rendering, ::ERB::Util, Context + include Helpers, ::ERB::Util, Context # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc @@ -159,10 +159,10 @@ module ActionView #:nodoc: end end - attr_accessor :_view_flow - attr_internal :request, :controller, :config, :assigns, :lookup_context + attr_accessor :view_renderer + attr_internal :request, :controller, :config, :assigns - # TODO Consider removing those setters once we have the renderer in place. + delegate :lookup_context, :render, :render_body, :to => :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, @@ -187,22 +187,21 @@ module ActionView #:nodoc: assign(assigns_for_first_render) self.helpers = Module.new unless self.class.helpers - @_config = {} - @_virtual_path = nil - @_view_flow = OutputFlow.new + @view_flow = OutputFlow.new @output_buffer = nil @virtual_path = nil + @_config = {} if @_controller = controller @_request = controller.request if controller.respond_to?(:request) @_config = controller.config.inheritable_copy if controller.respond_to?(:config) end - @_lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? + _lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? lookup_context : ActionView::LookupContext.new(lookup_context) - @_lookup_context.formats = formats if formats + _lookup_context.formats = formats if formats - @view_renderer = ActionView::Renderer.new(@_lookup_context, self) + @view_renderer = ActionView::Renderer.new(_lookup_context, self) end def controller_path diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 710cdc613d..c4bebd7d95 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -8,29 +8,69 @@ module ActionView # Action View contexts are supplied to Action Controller to render template. # The default Action View context is ActionView::Base. # - # In order to work with ActionController, a Context must implement: - # - # Context#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options:: see _render_partial in ActionView::Base - # Context#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template:: The template to render - # layout:: The layout to render around the template - # options:: See _render_template_with_layout in ActionView::Base - # partial:: Whether or not the template to render is a partial - # - # An Action View context can also mix in Action View's helpers. In order to - # mix in helpers, a context must implement: - # - # Context#controller - # - Returns an instance of AbstractController - # - # In any case, a context must mix in ActionView::Context, which stores compiled - # template and provides the output buffer. + # In order to work with ActionController, a Context must just include this module. module Context include CompiledTemplates - attr_accessor :output_buffer, :view_renderer, :view_flow + attr_accessor :output_buffer, :view_flow + + # Returns the contents that are yielded to a layout, given a name or a block. + # + # You can think of a layout as a method that is called with a block. If the user calls + # yield :some_name, the block, by default, returns content_for(:some_name). + # If the user calls simply +yield+, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render :layout => "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # + # <%= yield %> + # + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render :layout, and the response + # would be + # + # + # Content + # + # + # Finally, the block can take block arguments, which can be passed in by +yield+: + # + # # The template + # <%= render :layout => "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # + # <%= yield Struct.new(:name).new("David") %> + # + # + # In this case, the layout would receive the block passed into render :layout, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # + # Hello David + # + # + def _layout_for(*args, &block) + name = args.first + + if name.is_a?(Symbol) + view_flow.get(name).html_safe + elsif block + # TODO Import capture into AV::Context or + # leave it as implicit dependency? + capture(*args, &block) + else + view_flow.get(:layout).html_safe + end + end end end \ No newline at end of file diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb index 386a06511f..a8f740713f 100644 --- a/actionpack/lib/action_view/flows.rb +++ b/actionpack/lib/action_view/flows.rb @@ -34,7 +34,7 @@ module ActionView @view = view @parent = nil @child = view.output_buffer - @content = view._view_flow.content + @content = view.view_flow.content @fiber = fiber @root = Fiber.current.object_id end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 0139714240..ead7feb091 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -135,7 +135,7 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - result = @_view_flow.append(name, content) if content + result = @view_flow.append(name, content) if content result unless content end @@ -146,7 +146,7 @@ module ActionView # the layout to stop looking for more contents. def provide(name, content = nil, &block) content = capture(&block) if block_given? - result = @_view_flow.append!(name, content) if content + result = @view_flow.append!(name, content) if content result unless content end @@ -169,7 +169,7 @@ module ActionView # # def content_for?(name) - @_view_flow.get(name).present? + @view_flow.get(name).present? end # Use an alternate output buffer for the duration of the block. diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index 03aab444f8..d60b42a284 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -106,7 +106,7 @@ module ActionView # Set the view flow to support streaming. It will be aware # when to stop rendering the layout because it needs to search # something in the template and vice-versa. - view._view_flow = StreamingFlow.new(view, fiber) + view.view_flow = StreamingFlow.new(view, fiber) # Yo! Start the fiber! fiber.resume @@ -118,7 +118,7 @@ module ActionView content = template.render(view, locals, &yielder) # Once rendering the template is done, sets its content in the :layout key. - view._view_flow.set(:layout, content) + view.view_flow.set(:layout, content) # In case the layout continues yielding, we need to resume # the fiber until all yields are handled. diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 6b5ead463f..471428ac9e 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -46,7 +46,7 @@ module ActionView if layout view = @view - view._view_flow.set(:layout, content) + view.view_flow.set(:layout, content) layout.render(view, locals){ |*name| view._layout_for(*name) } else content diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb deleted file mode 100644 index 9850965456..0000000000 --- a/actionpack/lib/action_view/rendering.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'active_support/core_ext/object/try' - -module ActionView - # = Action View Rendering - module Rendering - # This is temporary until we remove the renderer dependency from AV. - delegate :render, :render_body, :to => :@view_renderer - - # Returns the contents that are yielded to a layout, given a name or a block. - # - # You can think of a layout as a method that is called with a block. If the user calls - # yield :some_name, the block, by default, returns content_for(:some_name). - # If the user calls simply +yield+, the default block returns content_for(:layout). - # - # The user can override this default by passing a block to the layout: - # - # # The template - # <%= render :layout => "my_layout" do %> - # Content - # <% end %> - # - # # The layout - # - # <%= yield %> - # - # - # In this case, instead of the default block, which would return content_for(:layout), - # this method returns the block that was passed in to render :layout, and the response - # would be - # - # - # Content - # - # - # Finally, the block can take block arguments, which can be passed in by +yield+: - # - # # The template - # <%= render :layout => "my_layout" do |customer| %> - # Hello <%= customer.name %> - # <% end %> - # - # # The layout - # - # <%= yield Struct.new(:name).new("David") %> - # - # - # In this case, the layout would receive the block passed into render :layout, - # and the struct specified would be passed into the block as an argument. The result - # would be - # - # - # Hello David - # - # - def _layout_for(*args, &block) - name = args.first - - if name.is_a?(Symbol) - @_view_flow.get(name).html_safe - elsif block - capture(*args, &block) - else - @_view_flow.get(:layout).html_safe - end - end - end -end diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index a9a36e6e6b..592c7da060 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase def setup super @av = ActionView::Base.new - @_view_flow = ActionView::OutputFlow.new + @view_flow = ActionView::OutputFlow.new end def test_capture_captures_the_temporary_output_buffer_in_its_block @@ -49,14 +49,14 @@ class CaptureHelperTest < ActionView::TestCase assert !content_for?(:title) provide :title, "hi" assert content_for?(:title) - assert_equal "hi", @_view_flow.get(:title) + assert_equal "hi", @view_flow.get(:title) provide :title, "

    title

    " - assert_equal "hi<p>title</p>", @_view_flow.get(:title) + assert_equal "hi<p>title</p>", @view_flow.get(:title) - @_view_flow = ActionView::OutputFlow.new + @view_flow = ActionView::OutputFlow.new provide :title, "hi" provide :title, "

    title

    ".html_safe - assert_equal "hi

    title

    ", @_view_flow.get(:title) + assert_equal "hi

    title

    ", @view_flow.get(:title) end def test_with_output_buffer_swaps_the_output_buffer_given_no_argument -- cgit v1.2.3 From 367bdc53611fe1da9cedda3220a83d3f39409cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 12:37:57 +0200 Subject: Remove view dependency from AV::Renderer. --- actionpack/lib/action_view.rb | 1 + actionpack/lib/action_view/base.rb | 23 +++++--- .../lib/action_view/renderer/abstract_renderer.rb | 4 +- .../lib/action_view/renderer/partial_renderer.rb | 69 +++++++++++----------- actionpack/lib/action_view/renderer/renderer.rb | 46 ++++++--------- .../lib/action_view/renderer/template_renderer.rb | 4 +- actionpack/lib/action_view/rendering.rb | 27 +++++++++ 7 files changed, 100 insertions(+), 74 deletions(-) create mode 100644 actionpack/lib/action_view/rendering.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 92b6f7c770..69c50a056c 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -35,6 +35,7 @@ module ActionView autoload :Helpers autoload :LookupContext autoload :PathSet + autoload :Rendering autoload :Template autoload :TestCase diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index cde1c36bd9..1890259ca0 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -131,7 +131,7 @@ module ActionView #:nodoc: # # More builder documentation can be found at http://builder.rubyforge.org. class Base - include Helpers, ::ERB::Util, Context + include Helpers, Rendering, ::ERB::Util, Context # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc @@ -162,7 +162,7 @@ module ActionView #:nodoc: attr_accessor :view_renderer attr_internal :request, :controller, :config, :assigns - delegate :lookup_context, :render, :render_body, :to => :view_renderer + delegate :lookup_context, :to => :view_renderer delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers, @@ -183,10 +183,11 @@ module ActionView #:nodoc: @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: + def initialize(context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc: assign(assigns_for_first_render) self.helpers = Module.new unless self.class.helpers + # Context vars initialization @view_flow = OutputFlow.new @output_buffer = nil @virtual_path = nil @@ -197,13 +198,19 @@ module ActionView #:nodoc: @_config = controller.config.inheritable_copy if controller.respond_to?(:config) end - _lookup_context = lookup_context.is_a?(ActionView::LookupContext) ? - lookup_context : ActionView::LookupContext.new(lookup_context) - _lookup_context.formats = formats if formats - - @view_renderer = ActionView::Renderer.new(_lookup_context, self) + # Handle all these for backwards compatibility. + # TODO Provide a new API for AV::Base and deprecate this one. + if context.is_a?(ActionView::Renderer) + @view_renderer = context + elsif + lookup_context = context.is_a?(ActionView::LookupContext) ? + context : ActionView::LookupContext.new(context) + lookup_context.formats = formats if formats + @view_renderer = ActionView::Renderer.new(lookup_context, controller) + end end + # TODO Is this needed anywhere? Maybe deprecate it? def controller_path @controller_path ||= controller && controller.controller_path end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 37bc0ae244..d389105a7a 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -3,9 +3,9 @@ module ActionView delegate :find_template, :template_exists?, :with_fallbacks, :update_details, :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context - def initialize(view, lookup_context) - @view = view + def initialize(lookup_context, controller) @lookup_context = lookup_context + @controller = controller end def render diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 0eeead2e5d..70327b16c4 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -217,45 +217,14 @@ module ActionView class PartialRenderer < AbstractRenderer #:nodoc: PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } - # TODO Controller should not come from the view - def initialize(view, *) + def initialize(*) super - @controller = @view.controller @partial_names = PARTIAL_NAMES[@controller.class.name] end - def setup(options, block) - partial = options[:partial] - - @options = options - @locals = options[:locals] || {} - @block = block - - if String === partial - @object = options[:object] - @path = partial - @collection = collection - else - @object = partial - - if @collection = collection_from_object || collection - paths = @collection_data = @collection.map { |o| partial_path(o) } - @path = paths.uniq.size == 1 ? paths.first : nil - else - @path = partial_path - end - end + def render(context, options, block) + setup(context, options, block) - if @path - @variable, @variable_counter = retrieve_variable(@path) - else - paths.map! { |path| retrieve_variable(path).unshift(path) } - end - - self - end - - def render wrap_formats(@path) do identifier = ((@template = find_partial) ? @template.identifier : @path) @@ -303,6 +272,38 @@ module ActionView private + def setup(context, options, block) + @view = context + partial = options[:partial] + + @options = options + @locals = options[:locals] || {} + @block = block + + if String === partial + @object = options[:object] + @path = partial + @collection = collection + else + @object = partial + + if @collection = collection_from_object || collection + paths = @collection_data = @collection.map { |o| partial_path(o) } + @path = paths.uniq.size == 1 ? paths.first : nil + else + @path = partial_path + end + end + + if @path + @variable, @variable_counter = retrieve_variable(@path) + else + paths.map! { |path| retrieve_variable(path).unshift(path) } + end + + self + end + def controller_prefixes @controller_prefixes ||= @controller && @controller._prefixes end diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb index 7b95c2dd92..582ed2f9f8 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -3,37 +3,25 @@ module ActionView # to other objects like TemplateRenderer and PartialRenderer which # actually renders the template. class Renderer - attr_accessor :lookup_context + attr_accessor :lookup_context, :controller - # TODO: render_context should not be an initialization parameter - # TODO: controller should be received on initialization - def initialize(lookup_context, render_context) - @render_context = render_context + def initialize(lookup_context, controller) @lookup_context = lookup_context - @view_flow = OutputFlow.new + @controller = controller end - # Returns the result of a render that's dictated by the options hash. The primary options are: - # - # * :partial - See ActionView::Partials. - # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. - # * :inline - Renders an inline template similar to how it's done in the controller. - # * :text - Renders the text passed in out. - # - # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter - # as the locals hash. - def render(options = {}, locals = {}, &block) + def render(context, options = {}, locals = {}, &block) case options when Hash if block_given? - _render_partial(options.merge(:partial => options[:layout]), &block) + _render_partial(context, options.merge(:partial => options[:layout]), &block) elsif options.key?(:partial) - _render_partial(options) + _render_partial(context, options) else - _render_template(options) + _render_template(context, options) end else - _render_partial(:partial => options, :locals => locals) + _render_partial(context, :partial => options, :locals => locals) end end @@ -42,30 +30,30 @@ module ActionView # # Note that partials are not supported to be rendered with streaming, # so in such cases, we just wrap them in an array. - def render_body(options) + def render_body(context, options) if options.key?(:partial) - [_render_partial(options)] + [_render_partial(context, options)] else - StreamingTemplateRenderer.new(@render_context, @lookup_context).render(options) + StreamingTemplateRenderer.new(@lookup_context, @controller).render(context, options) end end private - def _render_template(options) #:nodoc: - _template_renderer.render(options) + def _render_template(context, options) #:nodoc: + _template_renderer.render(context, options) end def _template_renderer #:nodoc: - @_template_renderer ||= TemplateRenderer.new(@render_context, @lookup_context) + @_template_renderer ||= TemplateRenderer.new(@lookup_context, @controller) end - def _render_partial(options, &block) #:nodoc: - _partial_renderer.setup(options, block).render + def _render_partial(context, options, &block) #:nodoc: + _partial_renderer.render(context, options, block) end def _partial_renderer #:nodoc: - @_partial_renderer ||= PartialRenderer.new(@render_context, @lookup_context) + @_partial_renderer ||= PartialRenderer.new(@lookup_context, @controller) end end end \ No newline at end of file diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 471428ac9e..6c55a865a1 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -3,7 +3,9 @@ require 'active_support/core_ext/array/wrap' module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: - def render(options) + def render(context, options) + @view = context + wrap_formats(options[:template] || options[:file]) do template = determine_template(options) freeze_formats(template.formats, true) diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb new file mode 100644 index 0000000000..2f420dc992 --- /dev/null +++ b/actionpack/lib/action_view/rendering.rb @@ -0,0 +1,27 @@ +module ActionView + # = Action View Rendering + # + # Implements methods that allow rendering from a view context. + # In order to use this module, all you need is to implement + # view_renderer that returns an ActionView::Renderer object. + module Rendering + # Returns the result of a render that's dictated by the options hash. The primary options are: + # + # * :partial - See ActionView::Partials. + # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. + # * :inline - Renders an inline template similar to how it's done in the controller. + # * :text - Renders the text passed in out. + # + # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter + # as the locals hash. + # def render(options = {}, locals = {}, &block) + def render(*args, &block) + view_renderer.render(self, *args, &block) + end + + # TODO: This is temporary, but the previous render is sticking. + def render_body(*args, &block) + view_renderer.render_body(self, *args, &block) + end + end +end \ No newline at end of file -- cgit v1.2.3 From b73576138529b1344a38f4e4b16c642f3510d514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 12:56:04 +0200 Subject: Introduce view renderer. --- actionpack/lib/abstract_controller/rendering.rb | 13 +++++++-- .../lib/action_controller/metal/streaming.rb | 2 +- actionpack/lib/action_view/context.rb | 2 ++ actionpack/lib/action_view/renderer/renderer.rb | 33 +++++++--------------- actionpack/lib/action_view/rendering.rb | 20 ++++++++----- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 306bd41e2d..db6ff41f55 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -99,7 +99,12 @@ module AbstractController # # Override this method in a module to change the default behavior. def view_context - view_context_class.new(lookup_context, view_assigns, self) + view_context_class.new(view_renderer, view_assigns, self) + end + + # Returns an object that is able to render templates. + def view_renderer + @view_renderer ||= ActionView::Renderer.new(lookup_context, self) end # Normalize arguments, options and then delegates render_to_body and @@ -127,7 +132,11 @@ module AbstractController # Find and renders a template based on the options given. # :api: private def _render_template(options) #:nodoc: - view_context.render(options) + if options.key?(:partial) + view_renderer.render_partial(view_context, options) + else + view_renderer.render_template(view_context, options) + end end # The prefixes used in render "foo" shortcuts. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index b9bd49f670..5b8111f30d 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -51,7 +51,7 @@ module ActionController #:nodoc: # Call render_to_body if we are streaming instead of usual +render+. def _render_template(options) #:nodoc: if options.delete(:stream) - Rack::Chunked::Body.new view_context.render_body(options) + Rack::Chunked::Body.new view_renderer.render_body(view_context, options) else super end diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index c4bebd7d95..01be294284 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -9,6 +9,8 @@ module ActionView # The default Action View context is ActionView::Base. # # In order to work with ActionController, a Context must just include this module. + # The initialization of the variables used by the context (@output_buffer, @view_flow, + # and @virtual_path) is responsibility of the object that includes this module. module Context include CompiledTemplates attr_accessor :output_buffer, :view_flow diff --git a/actionpack/lib/action_view/renderer/renderer.rb b/actionpack/lib/action_view/renderer/renderer.rb index 582ed2f9f8..3c0126f6bb 100644 --- a/actionpack/lib/action_view/renderer/renderer.rb +++ b/actionpack/lib/action_view/renderer/renderer.rb @@ -10,21 +10,6 @@ module ActionView @controller = controller end - def render(context, options = {}, locals = {}, &block) - case options - when Hash - if block_given? - _render_partial(context, options.merge(:partial => options[:layout]), &block) - elsif options.key?(:partial) - _render_partial(context, options) - else - _render_template(context, options) - end - else - _render_partial(context, :partial => options, :locals => locals) - end - end - # Render but returns a valid Rack body. If fibers are defined, we return # a streaming body that renders the template piece by piece. # @@ -32,24 +17,26 @@ module ActionView # so in such cases, we just wrap them in an array. def render_body(context, options) if options.key?(:partial) - [_render_partial(context, options)] + [render_partial(context, options)] else StreamingTemplateRenderer.new(@lookup_context, @controller).render(context, options) end end - private - - def _render_template(context, options) #:nodoc: + # Direct accessor to template rendering. + def render_template(context, options) #:nodoc: _template_renderer.render(context, options) end - def _template_renderer #:nodoc: - @_template_renderer ||= TemplateRenderer.new(@lookup_context, @controller) + # Direct access to partial rendering. + def render_partial(context, options, &block) #:nodoc: + _partial_renderer.render(context, options, block) end - def _render_partial(context, options, &block) #:nodoc: - _partial_renderer.render(context, options, block) + private + + def _template_renderer #:nodoc: + @_template_renderer ||= TemplateRenderer.new(@lookup_context, @controller) end def _partial_renderer #:nodoc: diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb index 2f420dc992..25ec450e6e 100644 --- a/actionpack/lib/action_view/rendering.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -15,13 +15,19 @@ module ActionView # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. # def render(options = {}, locals = {}, &block) - def render(*args, &block) - view_renderer.render(self, *args, &block) - end - - # TODO: This is temporary, but the previous render is sticking. - def render_body(*args, &block) - view_renderer.render_body(self, *args, &block) + def render(options = {}, locals = {}, &block) + case options + when Hash + if block_given? + view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) + elsif options.key?(:partial) + view_renderer.render_partial(self, options) + else + view_renderer.render_template(self, options) + end + else + view_renderer.render_partial(self, :partial => options, :locals => locals) + end end end end \ No newline at end of file -- cgit v1.2.3 From 6960a230fa2c2b7cf59266fc903ce0c11e887a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 13:00:51 +0200 Subject: Add a test for rendering from the controller context. --- .../controller/new_base/render_context_test.rb | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 actionpack/test/controller/new_base/render_context_test.rb diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb new file mode 100644 index 0000000000..3174bf42ab --- /dev/null +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -0,0 +1,54 @@ +require 'abstract_unit' + +module RenderContext + class BasicController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "render_context/basic/hello_world.html.erb" => "<%= @value %> from <%= self.__controller_method__ %>", + "layouts/basic.html.erb" => "?<%= yield %>?" + )] + + # Include ViewContext + include ActionView::Context + + # And initialize the required variables + before_filter do + @output_buffer = nil + @virtual_path = nil + @view_flow = ActionView::OutputFlow.new + end + + def hello_world + @value = "Hello" + render :action => "hello_world", :layout => false + end + + def with_layout + @value = "Hello" + render :action => "hello_world", :layout => "basic" + end + + protected + + def view_context + self + end + + def __controller_method__ + "controller context!" + end + end + + class RenderContextTest < Rack::TestCase + test "rendering using the controller as context" do + get "/render_context/basic/hello_world" + assert_body "Hello from controller context!" + assert_status 200 + end + + test "rendering using the controller as context with layout" do + get "/render_context/basic/with_layout" + assert_body "?Hello from controller context!?" + assert_status 200 + end + end +end -- cgit v1.2.3 From 46611a995d91abf7bb2a64c62af13b6449c75b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 13:39:15 +0200 Subject: log errors when an exception happens when streaming. --- actionpack/lib/action_view/context.rb | 2 ++ .../action_view/renderer/streaming_template_renderer.rb | 17 ++++++++++++++++- .../test/controller/new_base/render_streaming_test.rb | 13 +++++++++++++ actionpack/test/template/streaming_render_test.rb | 8 ++++++-- 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 01be294284..d501a6eaf9 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -15,6 +15,8 @@ module ActionView include CompiledTemplates attr_accessor :output_buffer, :view_flow + # TODO Provide an easy method that initializes all variables? + # Returns the contents that are yielded to a layout, given a name or a block. # # You can think of a layout as a method that is called with a block. If the user calls diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index d60b42a284..d987bc122a 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -60,11 +60,26 @@ module ActionView def each(&block) begin @start.call(block) - rescue + rescue Exception => exception + log_error(exception) block.call ActionView::Base.streaming_completion_on_exception end self end + + private + + # This is the same logging logic as in ShowExceptions middleware. + # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron. + def log_error(exception) #:nodoc: + logger = ActionController::Base.logger + return unless logger + + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end end # For streaming, instead of rendering a given a template, we return a Body diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index fed8d40b47..48cf0ab9cb 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -83,6 +83,19 @@ module RenderStreaming assert_streaming! end + test "rendering with template exception logs the exception" do + io = StringIO.new + _old, ActionController::Base.logger = ActionController::Base.logger, Logger.new(io) + + begin + get "/render_streaming/basic/template_exception" + io.rewind + assert_match "(undefined method `invalid!' for nil:NilClass)", io.read + ensure + ActionController::Base.logger = _old + end + end + test "do not stream on HTTP/1.0" do get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" assert_body "Hello world, I'm here!" diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb index 4d69081570..b2df8efee3 100644 --- a/actionpack/test/template/streaming_render_test.rb +++ b/actionpack/test/template/streaming_render_test.rb @@ -13,8 +13,12 @@ class FiberedTest < ActiveSupport::TestCase @controller_view = TestController.new.view_context end + def render_body(options) + @view.view_renderer.render_body(@view, options) + end + def buffered_render(options) - body = @view.render_body(options) + body = render_body(options) string = "" body.each do |piece| string << piece @@ -24,7 +28,7 @@ class FiberedTest < ActiveSupport::TestCase def test_streaming_works content = [] - body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield") + body = render_body(:template => "test/hello_world.erb", :layout => "layouts/yield") body.each do |piece| content << piece -- cgit v1.2.3 From 8dbee3aba6920edcae20abf3af7f803e528f9ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 14:50:42 +0200 Subject: Streaming docs. --- .../lib/action_controller/metal/streaming.rb | 202 ++++++++++++++++++++- actionpack/lib/action_view/base.rb | 1 - .../renderer/streaming_template_renderer.rb | 41 +---- 3 files changed, 202 insertions(+), 42 deletions(-) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 5b8111f30d..0dd847f967 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,7 +2,207 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Methods for sending streaming templates back to the client. + # Allow views to be streamed back to the client as they are rendered. + # + # The default way Rails renders views is by first rendering the template + # and then the layout. The first chunk of response is sent to the client + # just after the whole template is rendered, all queries are made and the + # layout is processed. + # + # Streaming inverts the rendering flow by rendering the layout first and + # streaming each part of the layout as they are processed. This allows the + # header of the html (which is usually in the layout) to be streamed back + # to client very quickly, allowing javascripts and stylesheets to be loaded + # earlier than usual. + # + # This approach was introduced in Rails 3.1 and is still improving. Several + # Rack middlewares may not work and you need to be careful when streaming. + # Those points are going to be addressed soon. + # + # In order to use streaming, you will need to use a Ruby version that + # supports Fibers (Fibers are supported since version 1.9.2 of the main + # Ruby implementation). + # + # == Examples + # + # Streaming can be added to a controller easily, all you need to do is + # call stream at the controller class: + # + # class PostsController + # stream + # end + # + # The +stream+ method accepts the same options as +before_filter+ and friends: + # + # class PostsController + # stream :only => :index + # end + # + # You can also selectively turn on streaming for specific actions: + # + # class PostsController + # def index + # @post = Post.scoped + # render :stream => true + # end + # end + # + # == When to use streaming + # + # Streaming may be considering an overkill for common actions like + # new or edit. The real benefit of streaming is on expensive actions + # that, for example, does a lot of queries on the database. + # + # In such actions, you want to delay queries execution as much as you can. + # For example, imagine the following dashboard action: + # + # def dashboard + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all + # end + # + # Most of the queries here are happening in the controller. In order to benefit + # most of streaming, you would want to rewrite it as: + # + # def dashboard + # # Allow lazily execution of the query + # @posts = Post.scoped + # @pages = Page.scoped + # @articles = Article.scoped + # render :stream => true + # end + # + # == Communication between layout and template + # + # When streaming, the layout is rendered first than the template. + # This means that, if your application currently rely on variables set + # in the template to be used in the layout, they won't work once you + # move to streaming. The proper way to communicate between layout and + # template, regardless if you use streaming or not, is by using + # +content_for+, +provide+ and +yield+. + # + # Take a simple example where the layout expects the template to tell + # which title to use: + # + # + # <%= yield :title %> + # <%= yield %> + # + # + # You would use +content_for+ in your template to specify the title: + # + # <%= content_for :title, "Main" %> + # Hello + # + # And the final result would be: + # + # + # Main + # Hello + # + # + # However, if +content_for+ is called several times, the final result + # would have all calls concatenated. For instance, if we have the following + # template: + # + # <%= content_for :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # The final result would be: + # + # + # Main page + # Hello + # + # + # This means that, if you have yield :title in your layout + # and you want to use streaming, you would have to render the whole template + # (and eventually trigger all queries) before streaming the title and all + # assets, which kills the purpose of streaming. For this reason Rails 3.1 + # introduces a helper called +provide+ that does the same as +content_for+ + # but tells the layout to stop searching for other entries and continue rendering. + # + # For instance, the template below, using +provide+: + # + # <%= provide :title, "Main" %> + # Hello + # <%= content_for :title, " page" %> + # + # Has as final result: + # + # + # Main + # Hello + # + # + # That said, when streaming, you need to properly check your templates + # and chose when to use +provide+ and +content_for+. + # + # == Headers, cookies, session and flash + # + # When streaming, the HTTP headers are sent to the client right before + # it renders the first line. This means that, modifying headers, cookies, + # session or flash after the template start rendering will not propagate + # to the client. + # + # If you try to modify cookies, session or flash, a ClosedError will be + # raised, showing those objects are closed for modification. + # + # == Middlewares + # + # Middlewares that need to manipulate the body won't work with streaming. + # You should disable those middlewares whenever streaming in development + # or production. For instance, Rack::Bug won't work when streaming as it + # needs to inject contents in the HTML body. + # + # Also Rack::Cache won't work with streaming as it does not support + # streaming bodies yet. So, whenever streaming, Cache-Control is automatically + # set to "no-cache". + # + # == Errors + # + # When it comes to streaming, exceptions get a bit more complicated. This + # happens because part of the template was already rendered and streamed to + # the client, making it impossible to render a whole exception page. + # + # Currently, when an exception happens in development or production, Rails + # will automatically stream to the client: + # + # "> + # + # The first two characters (">) are required in case the exception happens + # while rendering attributes for a given tag. You can check the real cause + # for the exception in your logger. + # + # == Web server support + # + # Not all web servers support streaming out-of-the-box. You need to check + # the instructions for each of them. + # + # ==== Unicorn + # + # Unicorn supports streaming but it needs to be configured. For this, you + # need to create a config file as follow: + # + # # unicorn.config.rb + # listen 3000, :tcp_nopush => false + # + # And use it on initialization: + # + # unicorn_rails --config-file unicorn.config.rb + # + # You may also want to configure other parameters like :tcp_nodelay. Please + # check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen + # + # If you are using unicorn with nginx, you may need to tweak nginx. + # Streaming should work out of the box on Rainbows. + # + # ==== Passenger + # + # To be described. + # module Streaming extend ActiveSupport::Concern diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 1890259ca0..baa2c5729f 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -139,7 +139,6 @@ module ActionView #:nodoc: # How to complete the streaming when an exception occurs. # This is our best guess: first try to close the attribute, then the tag. - # Currently this is private API and may be changed at *any* time. cattr_accessor :streaming_completion_on_exception @@streaming_completion_on_exception = %(">) diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index d987bc122a..1ccf5a8ddb 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -4,50 +4,11 @@ require 'fiber' if defined?(Fiber) module ActionView - # Consider the following layout: - # - # <%= yield :header %> - # 2 - # <%= yield %> - # 5 - # <%= yield :footer %> - # - # And template: - # - # <%= provide :header, "1" %> - # 3 - # 4 - # <%= provide :footer, "6" %> - # - # It will stream: - # - # "1\n", "2\n", "3\n4\n", "5\n", "6\n" - # - # Notice that once you <%= yield %>, it will render the whole template - # before streaming again. In the future, we can also support streaming - # from the template and not only the layout. - # - # Also, notice we use +provide+ instead of +content_for+, as +provide+ - # gives the control back to the layout as soon as it is called. - # With +content_for+, it would render all the template to find all - # +content_for+ calls. For instance, consider this layout: - # - # <%= yield :header %> - # - # With this template: - # - # <%= content_for :header, "1" %> - # <%= provide :header, "2" %> - # <%= provide :header, "3" %> - # - # It will return "12\n" because +content_for+ continues rendering the - # template but it is returns back to the layout as soon as it sees the - # first +provide+. - # # == TODO # # * Support streaming from child templates, partials and so on. # * Integrate exceptions with exceptron + # * Rack::Cache needs to support streaming bodies class StreamingTemplateRenderer < TemplateRenderer #:nodoc: # A valid Rack::Body (i.e. it responds to each). # It is initialized with a block that, when called, starts -- cgit v1.2.3 From ca608b8c9c9bc47160f0413ce02d3315f89ef537 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 1 May 2011 12:28:16 -0300 Subject: Bring this back on the contributing guide. --- railties/guides/source/contributing_to_ruby_on_rails.textile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/guides/source/contributing_to_ruby_on_rails.textile b/railties/guides/source/contributing_to_ruby_on_rails.textile index a7d792f88d..cb09b180a2 100644 --- a/railties/guides/source/contributing_to_ruby_on_rails.textile +++ b/railties/guides/source/contributing_to_ruby_on_rails.textile @@ -252,7 +252,7 @@ Once you're happy that the pull request contains a good change, comment on the G I like the way you've restructured that code in generate_finder_sql, much nicer. The tests look good too. -If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. +If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. h3. Contributing to the Rails Documentation -- cgit v1.2.3 From 13df194c002f362556560303da9c73a24ebb189e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 1 May 2011 19:39:57 +0200 Subject: Tidy up pending TODOs after discussion with Mr. Gatoz (@wycats). --- actionpack/lib/action_view.rb | 1 - actionpack/lib/action_view/base.rb | 9 +-- actionpack/lib/action_view/context.rb | 76 ++++-------------- actionpack/lib/action_view/helpers.rb | 2 + .../lib/action_view/helpers/rendering_helper.rb | 92 ++++++++++++++++++++++ actionpack/lib/action_view/rendering.rb | 33 -------- .../controller/new_base/render_context_test.rb | 14 ++-- 7 files changed, 120 insertions(+), 107 deletions(-) create mode 100644 actionpack/lib/action_view/helpers/rendering_helper.rb delete mode 100644 actionpack/lib/action_view/rendering.rb diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 69c50a056c..92b6f7c770 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -35,7 +35,6 @@ module ActionView autoload :Helpers autoload :LookupContext autoload :PathSet - autoload :Rendering autoload :Template autoload :TestCase diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index baa2c5729f..ca0a89c8a5 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -131,7 +131,7 @@ module ActionView #:nodoc: # # More builder documentation can be found at http://builder.rubyforge.org. class Base - include Helpers, Rendering, ::ERB::Util, Context + include Helpers, ::ERB::Util, Context # Specify the proc used to decorate input tags that refer to attributes with errors. cattr_accessor :field_error_proc @@ -186,11 +186,6 @@ module ActionView #:nodoc: assign(assigns_for_first_render) self.helpers = Module.new unless self.class.helpers - # Context vars initialization - @view_flow = OutputFlow.new - @output_buffer = nil - @virtual_path = nil - @_config = {} if @_controller = controller @_request = controller.request if controller.respond_to?(:request) @@ -207,6 +202,8 @@ module ActionView #:nodoc: lookup_context.formats = formats if formats @view_renderer = ActionView::Renderer.new(lookup_context, controller) end + + _prepare_context end # TODO Is this needed anywhere? Maybe deprecate it? diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index d501a6eaf9..083856b2ca 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -10,71 +10,27 @@ module ActionView # # In order to work with ActionController, a Context must just include this module. # The initialization of the variables used by the context (@output_buffer, @view_flow, - # and @virtual_path) is responsibility of the object that includes this module. + # and @virtual_path) is responsibility of the object that includes this module + # (although you can call _prepare_context defined below). module Context include CompiledTemplates attr_accessor :output_buffer, :view_flow - # TODO Provide an easy method that initializes all variables? - - # Returns the contents that are yielded to a layout, given a name or a block. - # - # You can think of a layout as a method that is called with a block. If the user calls - # yield :some_name, the block, by default, returns content_for(:some_name). - # If the user calls simply +yield+, the default block returns content_for(:layout). - # - # The user can override this default by passing a block to the layout: - # - # # The template - # <%= render :layout => "my_layout" do %> - # Content - # <% end %> - # - # # The layout - # - # <%= yield %> - # - # - # In this case, instead of the default block, which would return content_for(:layout), - # this method returns the block that was passed in to render :layout, and the response - # would be - # - # - # Content - # - # - # Finally, the block can take block arguments, which can be passed in by +yield+: - # - # # The template - # <%= render :layout => "my_layout" do |customer| %> - # Hello <%= customer.name %> - # <% end %> - # - # # The layout - # - # <%= yield Struct.new(:name).new("David") %> - # - # - # In this case, the layout would receive the block passed into render :layout, - # and the struct specified would be passed into the block as an argument. The result - # would be - # - # - # Hello David - # - # - def _layout_for(*args, &block) - name = args.first + # Prepares the context by setting the appropriate instance variables. + # :api: plugin + def _prepare_context + @view_flow = OutputFlow.new + @output_buffer = nil + @virtual_path = nil + end - if name.is_a?(Symbol) - view_flow.get(name).html_safe - elsif block - # TODO Import capture into AV::Context or - # leave it as implicit dependency? - capture(*args, &block) - else - view_flow.get(:layout).html_safe - end + # Encapsulates the interaction with the view flow so it + # returns the correct buffer on yield. This is usually + # overwriten by helpers to add more behavior. + # :api: plugin + def _layout_for(name=nil) + name ||= :layout + view_flow.get(name).html_safe end end end \ No newline at end of file diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index 205116f610..3ef9826d30 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -19,6 +19,7 @@ module ActionView #:nodoc: autoload :NumberHelper autoload :OutputSafetyHelper autoload :RecordTagHelper + autoload :RenderingHelper autoload :SanitizeHelper autoload :SprocketsHelper autoload :TagHelper @@ -48,6 +49,7 @@ module ActionView #:nodoc: include NumberHelper include OutputSafetyHelper include RecordTagHelper + include RenderingHelper include SanitizeHelper include SprocketsHelper include TagHelper diff --git a/actionpack/lib/action_view/helpers/rendering_helper.rb b/actionpack/lib/action_view/helpers/rendering_helper.rb new file mode 100644 index 0000000000..c91e03b7e8 --- /dev/null +++ b/actionpack/lib/action_view/helpers/rendering_helper.rb @@ -0,0 +1,92 @@ +module ActionView + module Helpers + # = Action View Rendering + # + # Implements methods that allow rendering from a view context. + # In order to use this module, all you need is to implement + # view_renderer that returns an ActionView::Renderer object. + module RenderingHelper + # Returns the result of a render that's dictated by the options hash. The primary options are: + # + # * :partial - See ActionView::Partials. + # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. + # * :inline - Renders an inline template similar to how it's done in the controller. + # * :text - Renders the text passed in out. + # + # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter + # as the locals hash. + def render(options = {}, locals = {}, &block) + case options + when Hash + if block_given? + view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) + elsif options.key?(:partial) + view_renderer.render_partial(self, options) + else + view_renderer.render_template(self, options) + end + else + view_renderer.render_partial(self, :partial => options, :locals => locals) + end + end + + # Overwrites _layout_for in the context object so it supports the case a block is + # passed to a partial. Returns the contents that are yielded to a layout, given a + # name or a block. + # + # You can think of a layout as a method that is called with a block. If the user calls + # yield :some_name, the block, by default, returns content_for(:some_name). + # If the user calls simply +yield+, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render :layout => "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # + # <%= yield %> + # + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render :layout, and the response + # would be + # + # + # Content + # + # + # Finally, the block can take block arguments, which can be passed in by +yield+: + # + # # The template + # <%= render :layout => "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # + # <%= yield Struct.new(:name).new("David") %> + # + # + # In this case, the layout would receive the block passed into render :layout, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # + # Hello David + # + # + def _layout_for(*args, &block) + name = args.first + + if block && !name.is_a?(Symbol) + capture(*args, &block) + else + super + end + end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb deleted file mode 100644 index 25ec450e6e..0000000000 --- a/actionpack/lib/action_view/rendering.rb +++ /dev/null @@ -1,33 +0,0 @@ -module ActionView - # = Action View Rendering - # - # Implements methods that allow rendering from a view context. - # In order to use this module, all you need is to implement - # view_renderer that returns an ActionView::Renderer object. - module Rendering - # Returns the result of a render that's dictated by the options hash. The primary options are: - # - # * :partial - See ActionView::Partials. - # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. - # * :inline - Renders an inline template similar to how it's done in the controller. - # * :text - Renders the text passed in out. - # - # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter - # as the locals hash. - # def render(options = {}, locals = {}, &block) - def render(options = {}, locals = {}, &block) - case options - when Hash - if block_given? - view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block) - elsif options.key?(:partial) - view_renderer.render_partial(self, options) - else - view_renderer.render_template(self, options) - end - else - view_renderer.render_partial(self, :partial => options, :locals => locals) - end - end - end -end \ No newline at end of file diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index 3174bf42ab..f41b14d5d6 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -1,5 +1,8 @@ require 'abstract_unit' +# This is testing the decoupling of view renderer and view context +# by allowing the controller to be used as view context. This is +# similar to the way sinatra renders templates. module RenderContext class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( @@ -7,15 +10,11 @@ module RenderContext "layouts/basic.html.erb" => "?<%= yield %>?" )] - # Include ViewContext + # 1) Include ActionView::Context to bring the required dependencies include ActionView::Context - # And initialize the required variables - before_filter do - @output_buffer = nil - @virtual_path = nil - @view_flow = ActionView::OutputFlow.new - end + # 2) Call _prepare_context that will do the required initialization + before_filter :_prepare_context def hello_world @value = "Hello" @@ -29,6 +28,7 @@ module RenderContext protected + # 3) Set view_context to self def view_context self end -- cgit v1.2.3 From 31110c50e499067ccb8b4c0693d81f4153e6e155 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 1 May 2011 15:33:27 -0300 Subject: Fix punctuation errors. --- activeresource/lib/active_resource/validations.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb index a373e53f11..ca265d053e 100644 --- a/activeresource/lib/active_resource/validations.rb +++ b/activeresource/lib/active_resource/validations.rb @@ -8,9 +8,9 @@ module ActiveResource # Active Resource validation is reported to and from this object, which is used by Base#save # to determine whether the object in a valid state to be saved. See usage example in Validations. class Errors < ActiveModel::Errors - # Grabs errors from an array of messages (like ActiveRecord::Validations) + # Grabs errors from an array of messages (like ActiveRecord::Validations). # The second parameter directs the errors cache to be cleared (default) - # or not (by passing true) + # or not (by passing true). def from_array(messages, save_cache = false) clear unless save_cache humanized_attributes = Hash[@base.attributes.keys.map { |attr_name| [attr_name.humanize, attr_name] }] @@ -73,7 +73,7 @@ module ActiveResource # clear the remote validations so they don't interfere with the local # ones. Otherwise we get an endless loop and can never change the - # fields so as to make the resource valid + # fields so as to make the resource valid. @remote_errors = nil if perform_validation && valid? || !perform_validation save_without_validation @@ -84,7 +84,7 @@ module ActiveResource rescue ResourceInvalid => error # cache the remote errors because every call to valid? clears # all errors. We must keep a copy to add these back after local - # validations + # validations. @remote_errors = error load_remote_errors(@remote_errors, true) false @@ -92,7 +92,7 @@ module ActiveResource # Loads the set of remote errors into the object's Errors based on the - # content-type of the error-block received + # content-type of the error-block received. def load_remote_errors(remote_errors, save_cache = false ) #:nodoc: case self.class.format when ActiveResource::Formats[:xml] -- cgit v1.2.3 From 24724288812c8c8892536d0519c019d8a3aea8d1 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 1 May 2011 15:46:01 -0300 Subject: Remove extra whitespaces. --- activeresource/lib/active_resource/http_mock.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index e085a05f6d..02b1192d7e 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -9,8 +9,8 @@ module ActiveResource # requests. # # To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to - # method with an attached block. The block declares a set of URIs with expected input, and the output - # each request should return. The passed in block has any number of entries in the following generalized + # method with an attached block. The block declares a set of URIs with expected input, and the output + # each request should return. The passed in block has any number of entries in the following generalized # format: # # mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {}) @@ -29,7 +29,7 @@ module ActiveResource # request_headers listed above. # # In order for a mock to deliver its content, the incoming request must match by the http_method, - # +path+ and request_headers. If no match is found an InvalidRequestError exception + # +path+ and request_headers. If no match is found an +InvalidRequestError+ exception # will be raised showing you what request it could not find a response for and also what requests and response # pairs have been recorded so you can create a new mock for that request. # @@ -80,7 +80,7 @@ module ActiveResource class << self - # Returns an array of all request objects that have been sent to the mock. You can use this to check + # Returns an array of all request objects that have been sent to the mock. You can use this to check # if your model actually sent an HTTP request. # # ==== Example @@ -105,7 +105,7 @@ module ActiveResource end # Returns the list of requests and their mocked responses. Look up a - # response for a request using responses.assoc(request). + # response for a request using responses.assoc(request). def responses @@responses ||= [] end -- cgit v1.2.3 From 825dae1f0b32bac0e5afde61578534d1a3df8b7b Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 1 May 2011 15:50:53 -0300 Subject: Documented ActiveResource#observing --- activeresource/lib/active_resource/observing.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/activeresource/lib/active_resource/observing.rb b/activeresource/lib/active_resource/observing.rb index 3c74d49c80..1bfceb8dc8 100644 --- a/activeresource/lib/active_resource/observing.rb +++ b/activeresource/lib/active_resource/observing.rb @@ -5,6 +5,14 @@ module ActiveResource included do %w( create save update destroy ).each do |method| + # def create_with_notifications(*args, &block) + # notify_observers(:before_create) + # if result = create_without_notifications(*args, &block) + # notify_observers(:after_create) + # end + # result + # end + # alias_method_chain(create, :notifications) class_eval(<<-EOS, __FILE__, __LINE__ + 1) def #{method}_with_notifications(*args, &block) notify_observers(:before_#{method}) -- cgit v1.2.3 From 23eb81a3d1eb154a3aefe569408d1bb2ed63c554 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 1 May 2011 11:55:13 -0700 Subject: assert_difference can take a callable piece of code rather than just evaling a string --- .../associations/belongs_to_associations_test.rb | 8 ++++---- .../lib/active_support/testing/assertions.rb | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 7518bc19f9..ddcc36c841 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -576,11 +576,11 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end def test_polymorphic_counter_cache - tagging = taggings(:welcome_general) - post = post = posts(:welcome) - comment = comments(:greetings) + tagging = taggings(:welcome_general) + post = posts(:welcome) + comment = comments(:greetings) - assert_difference 'post.reload.taggings_count', -1 do + assert_difference lambda { post.reload.taggings_count }, -1 do assert_difference 'comment.reload.taggings_count', +1 do tagging.taggable = comment end diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index a4e0361a32..bfc2024529 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -29,6 +29,16 @@ module ActiveSupport # post :create, :article => {...} # end # + # A lambda or a list of lambdas can be passed in and evaluated: + # + # assert_difference lambda { Article.count }, 2 do + # post :create, :article => {...} + # end + # + # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do + # post :create, :article => {...} + # end + # # A error message can be specified. # # assert_difference 'Article.count', -1, "An Article should be destroyed" do @@ -37,14 +47,15 @@ module ActiveSupport def assert_difference(expression, difference = 1, message = nil, &block) b = block.send(:binding) exps = Array.wrap(expression) - before = exps.map { |e| eval(e, b) } + before = exps.map { |e| e.respond_to?(:call) ? e.call : eval(e, b) } yield exps.each_with_index do |e, i| - error = "#{e.inspect} didn't change by #{difference}" - error = "#{message}.\n#{error}" if message - assert_equal(before[i] + difference, eval(e, b), error) + error = "#{e.inspect} didn't change by #{difference}" + error = "#{message}.\n#{error}" if message + actual = e.respond_to?(:call) ? e.call : eval(e, b) + assert_equal(before[i] + difference, actual, error) end end -- cgit v1.2.3 From 06b91381889fcbd32018ab045b1795ada8718cad Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Sun, 1 May 2011 15:59:27 -0300 Subject: Added some docs on ActiveResource::HttpMock --- activeresource/lib/active_resource/http_mock.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activeresource/lib/active_resource/http_mock.rb b/activeresource/lib/active_resource/http_mock.rb index 02b1192d7e..3bfd536b29 100644 --- a/activeresource/lib/active_resource/http_mock.rb +++ b/activeresource/lib/active_resource/http_mock.rb @@ -299,6 +299,8 @@ module ActiveResource end end + # Returns true if code is 2xx, + # false otherwise. def success? code.in?(200..299) end @@ -311,6 +313,8 @@ module ActiveResource headers[key] = value end + # Returns true if the other is a Response with an equal body, equal message + # and equal headers. Otherwise it returns false. def ==(other) if (other.is_a?(Response)) other.body == body && other.message == message && other.headers == headers -- cgit v1.2.3 From b8ccd0552473fbe0f346334e37b7d84481dd3533 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 1 May 2011 12:16:12 -0700 Subject: convert strings to lambdas so we can use a consistent interface to the objects in the collection --- activesupport/lib/active_support/testing/assertions.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index bfc2024529..05da50e150 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -45,17 +45,17 @@ module ActiveSupport # post :delete, :id => ... # end def assert_difference(expression, difference = 1, message = nil, &block) - b = block.send(:binding) - exps = Array.wrap(expression) - before = exps.map { |e| e.respond_to?(:call) ? e.call : eval(e, b) } + exps = Array.wrap(expression).map { |e| + e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } + } + before = exps.map { |e| e.call } yield exps.each_with_index do |e, i| error = "#{e.inspect} didn't change by #{difference}" error = "#{message}.\n#{error}" if message - actual = e.respond_to?(:call) ? e.call : eval(e, b) - assert_equal(before[i] + difference, actual, error) + assert_equal(before[i] + difference, e.call, error) end end -- cgit v1.2.3 From 7c5ae0a88fc9406857ee362c827c57eb23fd5f95 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sun, 1 May 2011 20:20:22 +0200 Subject: Added mass-assignment security :as and :without_protection support to AR.new and AR.create --- activerecord/lib/active_record/base.rb | 33 ++++- .../test/cases/mass_assignment_security_test.rb | 138 +++++++++++++++++---- 2 files changed, 142 insertions(+), 29 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 04c12f86b6..0ba401584d 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -475,10 +475,19 @@ module ActiveRecord #:nodoc: # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the # attributes on the objects that are to be created. # + # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # # ==== Examples # # Create a single new object # User.create(:first_name => 'Jamie') # + # # Create a single new object using the :admin mass-assignment security scope + # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Create a single new object bypassing mass-assignment security + # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + # # # Create an Array of new objects # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) # @@ -491,11 +500,11 @@ module ActiveRecord #:nodoc: # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false # end - def create(attributes = nil, &block) + def create(attributes = nil, options = {}, &block) if attributes.is_a?(Array) - attributes.collect { |attr| create(attr, &block) } + attributes.collect { |attr| create(attr, options, &block) } else - object = new(attributes) + object = new(attributes, options) yield(object) if block_given? object.save object @@ -1484,7 +1493,20 @@ end # attributes but not yet saved (pass a hash with key names matching the associated table column names). # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. - def initialize(attributes = nil) + # + # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options + # in the +options+ parameter. + # + # ==== Examples + # # Instantiates a single new object + # User.new(:first_name => 'Jamie') + # + # # Instantiates a single new object using the :admin mass-assignment security scope + # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin) + # + # # Instantiates a single new object bypassing mass-assignment security + # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) + def initialize(attributes = nil, options = {}) @attributes = attributes_from_column_definition @association_cache = {} @aggregation_cache = {} @@ -1500,7 +1522,8 @@ end set_serialized_attributes populate_with_current_scope_attributes - self.attributes = attributes unless attributes.nil? + + assign_attributes(attributes, options) if attributes result = yield self if block_given? run_callbacks :initialize diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 2c051bff84..67950c8068 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -7,6 +7,12 @@ require 'models/person' class MassAssignmentSecurityTest < ActiveRecord::TestCase + def setup + # another AR test modifies the columns which causes issues with create calls + TightPerson.reset_column_information + LoosePerson.reset_column_information + end + def test_customized_primary_key_remains_protected subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') assert_nil subscriber.id @@ -35,60 +41,114 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase p = LoosePerson.new p.assign_attributes(attributes_hash) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_skips_mass_assignment_security_protection_when_without_protection_is_used p = LoosePerson.new p.assign_attributes(attributes_hash, :without_protection => true) - assert_equal 5, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_all_attributes(p) end def test_assign_attributes_with_default_scope_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :default) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_with_admin_scope_and_attr_protected_attributes p = LoosePerson.new p.assign_attributes(attributes_hash, :as => :admin) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_admin_attributes(p) end def test_assign_attributes_with_default_scope_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :default) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal nil, p.comments + assert_default_attributes(p) end def test_assign_attributes_with_admin_scope_and_attr_accessible_attributes p = TightPerson.new p.assign_attributes(attributes_hash, :as => :admin) - assert_equal nil, p.id - assert_equal 'Josh', p.first_name - assert_equal 'm', p.gender - assert_equal 'rides a sweet bike', p.comments + assert_admin_attributes(p) + end + + def test_new_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash) + + assert_default_attributes(p) + end + + def test_new_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash) + + assert_default_attributes(p) + end + + def test_create_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash) + + assert_default_attributes(p, true) + end + + def test_create_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash) + + assert_default_attributes(p, true) + end + + def test_new_with_admin_scope_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash, :as => :admin) + + assert_admin_attributes(p) + end + + def test_new_with_admin_scope_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash, :as => :admin) + + assert_admin_attributes(p) + end + + def test_create_with_admin_scope_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + + def test_create_with_admin_scope_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash, :as => :admin) + + assert_admin_attributes(p, true) + end + + def test_new_with_without_protection_with_attr_accessible_attributes + p = TightPerson.new(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_new_with_without_protection_with_attr_protected_attributes + p = LoosePerson.new(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_create_with_without_protection_with_attr_accessible_attributes + p = TightPerson.create(attributes_hash, :without_protection => true) + + assert_all_attributes(p) + end + + def test_create_with_without_protection_with_attr_protected_attributes + p = LoosePerson.create(attributes_hash, :without_protection => true) + + assert_all_attributes(p) end def test_protection_against_class_attribute_writers @@ -111,4 +171,34 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase :comments => 'rides a sweet bike' } end + + def assert_default_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_nil person.comments + end + + def assert_admin_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end + + def assert_all_attributes(person) + assert_equal 5, person.id + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end + end \ No newline at end of file -- cgit v1.2.3 From 86d7ed33754f80690395309dd307c6d9ecc0022f Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Sun, 1 May 2011 23:30:07 +0200 Subject: singular and collection relations in AR can now specify mass-assignment security options (:as and :without_protection) in build, create and create! methods. --- .../associations/collection_association.rb | 20 +- .../associations/has_many_through_association.rb | 4 +- .../associations/singular_association.rb | 16 +- .../test/cases/mass_assignment_security_test.rb | 340 +++++++++++++++++++-- activerecord/test/models/person.rb | 23 +- activerecord/test/schema/schema.rb | 2 + 6 files changed, 346 insertions(+), 59 deletions(-) diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 33a184d48d..6cdec8c487 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -93,20 +93,20 @@ module ActiveRecord first_or_last(:last, *args) end - def build(attributes = {}, &block) - build_or_create(attributes, :build, &block) + def build(attributes = {}, options = {}, &block) + build_or_create(:build, attributes, options, &block) end - def create(attributes = {}, &block) + def create(attributes = {}, options = {}, &block) unless owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end - build_or_create(attributes, :create, &block) + build_or_create(:create, attributes, options, &block) end - def create!(attrs = {}, &block) - record = create(attrs, &block) + def create!(attrs = {}, options = {}, &block) + record = create(attrs, options, &block) Array.wrap(record).each(&:save!) record end @@ -403,9 +403,9 @@ module ActiveRecord end + existing end - def build_or_create(attributes, method) + def build_or_create(method, attributes, options) records = Array.wrap(attributes).map do |attrs| - record = build_record(attrs) + record = build_record(attrs, options) add_to_target(record) do yield(record) if block_given? @@ -421,8 +421,8 @@ module ActiveRecord raise NotImplementedError end - def build_record(attributes) - reflection.build_association(scoped.scope_for_create.merge(attributes)) + def build_record(attributes, options) + reflection.build_association(scoped.scope_for_create.merge(attributes), options) end def delete_or_destroy(records, method) 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 9d2b29685b..7708228d23 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -60,10 +60,10 @@ module ActiveRecord through_record end - def build_record(attributes) + def build_record(attributes, options = {}) ensure_not_nested - record = super(attributes) + record = super(attributes, options) inverse = source_reflection.inverse_of if inverse diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 4edbe216be..ea4d73d414 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -17,16 +17,16 @@ module ActiveRecord replace(record) end - def create(attributes = {}) - new_record(:create, attributes) + def create(attributes = {}, options = {}) + new_record(:create, attributes, options) end - def create!(attributes = {}) - build(attributes).tap { |record| record.save! } + def create!(attributes = {}, options = {}) + build(attributes, options).tap { |record| record.save! } end - def build(attributes = {}) - new_record(:build, attributes) + def build(attributes = {}, options = {}) + new_record(:build, attributes, options) end private @@ -44,9 +44,9 @@ module ActiveRecord replace(record) end - def new_record(method, attributes) + def new_record(method, attributes, options) attributes = scoped.scope_for_create.merge(attributes || {}) - record = reflection.send("#{method}_association", attributes) + record = reflection.send("#{method}_association", attributes, options) set_new_record(record) record end diff --git a/activerecord/test/cases/mass_assignment_security_test.rb b/activerecord/test/cases/mass_assignment_security_test.rb index 67950c8068..fbbae99e8b 100644 --- a/activerecord/test/cases/mass_assignment_security_test.rb +++ b/activerecord/test/cases/mass_assignment_security_test.rb @@ -5,14 +5,64 @@ require 'models/keyboard' require 'models/task' require 'models/person' -class MassAssignmentSecurityTest < ActiveRecord::TestCase +module MassAssignmentTestHelpers def setup # another AR test modifies the columns which causes issues with create calls TightPerson.reset_column_information LoosePerson.reset_column_information end + def attributes_hash + { + :id => 5, + :first_name => 'Josh', + :gender => 'm', + :comments => 'rides a sweet bike' + } + end + + def assert_default_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_nil person.comments + end + + def assert_admin_attributes(person, create = false) + unless create + assert_nil person.id + else + assert !!person.id + end + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end + + def assert_all_attributes(person) + assert_equal 5, person.id + assert_equal 'Josh', person.first_name + assert_equal 'm', person.gender + assert_equal 'rides a sweet bike', person.comments + end +end + +module MassAssignmentRelationTestHelpers + def setup + super + @person = LoosePerson.create(attributes_hash) + end +end + + +class MassAssignmentSecurityTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + def test_customized_primary_key_remains_protected subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try') assert_nil subscriber.id @@ -161,44 +211,268 @@ class MassAssignmentSecurityTest < ActiveRecord::TestCase end end - private +end - def attributes_hash - { - :id => 5, - :first_name => 'Josh', - :gender => 'm', - :comments => 'rides a sweet bike' - } + +class MassAssignmentSecurityHasOneRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.build_best_friend(attributes_hash) + assert_default_attributes(best_friend) end - def assert_default_attributes(person, create = false) - unless create - assert_nil person.id - else - assert !!person.id - end - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_nil person.comments + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.build_best_friend(attributes_hash) + assert_default_attributes(best_friend) end - def assert_admin_attributes(person, create = false) - unless create - assert_nil person.id - else - assert !!person.id - end - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'rides a sweet bike', person.comments + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.build_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) end - def assert_all_attributes(person) - assert_equal 5, person.id - assert_equal 'Josh', person.first_name - assert_equal 'm', person.gender - assert_equal 'rides a sweet bike', person.comments + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.build_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.build_best_friend(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.create_best_friend(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.create_best_friend(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_without_protection + best_friend = @person.create_best_friend(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + +end + + +class MassAssignmentSecurityBelongsToRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.build_best_friend_of(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.build_best_friend_of(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.build_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.build_best_friend_of(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.create_best_friend_of(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.create_best_friend_of(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend_of(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_without_protection + best_friend = @person.create_best_friend_of(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.create_best_friend!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.create_best_friend!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + +end + + +class MassAssignmentSecurityHasManyRelationsTest < ActiveRecord::TestCase + include MassAssignmentTestHelpers + include MassAssignmentRelationTestHelpers + + # build + + def test_has_one_build_with_attr_protected_attributes + best_friend = @person.best_friends.build(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_attr_accessible_attributes + best_friend = @person.best_friends.build(attributes_hash) + assert_default_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.build(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.build(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend) + end + + def test_has_one_build_without_protection + best_friend = @person.best_friends.build(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create + + def test_has_one_create_with_attr_protected_attributes + best_friend = @person.best_friends.create(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_attr_accessible_attributes + best_friend = @person.best_friends.create(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.create(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.create(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_without_protection + best_friend = @person.best_friends.create(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) + end + + # create! + + def test_has_one_create_with_bang_with_attr_protected_attributes + best_friend = @person.best_friends.create!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_attr_accessible_attributes + best_friend = @person.best_friends.create!(attributes_hash) + assert_default_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_protected_attributes + best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_with_admin_scope_with_attr_accessible_attributes + best_friend = @person.best_friends.create!(attributes_hash, :as => :admin) + assert_admin_attributes(best_friend, true) + end + + def test_has_one_create_with_bang_without_protection + best_friend = @person.best_friends.create!(attributes_hash, :without_protection => true) + assert_all_attributes(best_friend) end -end \ No newline at end of file +end diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 9c4794902d..a58c9bf572 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -1,6 +1,6 @@ class Person < ActiveRecord::Base has_many :readers - has_one :reader + has_one :reader has_many :posts, :through => :readers has_many :posts_with_no_comments, :through => :readers, :source => :post, :include => :comments, :conditions => 'comments.id is null' @@ -8,23 +8,23 @@ class Person < ActiveRecord::Base has_many :references has_many :bad_references has_many :fixed_bad_references, :conditions => { :favourite => true }, :class_name => 'BadReference' - has_one :favourite_reference, :class_name => 'Reference', :conditions => ['favourite=?', true] + 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' has_many :jobs, :through => :references - has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy + has_many :jobs_with_dependent_destroy, :source => :job, :through => :references, :dependent => :destroy has_many :jobs_with_dependent_delete_all, :source => :job, :through => :references, :dependent => :delete_all - has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify + has_many :jobs_with_dependent_nullify, :source => :job, :through => :references, :dependent => :nullify belongs_to :primary_contact, :class_name => 'Person' has_many :agents, :class_name => 'Person', :foreign_key => 'primary_contact_id' has_many :agents_of_agents, :through => :agents, :source => :agents belongs_to :number1_fan, :class_name => 'Person' - has_many :agents_posts, :through => :agents, :source => :posts + has_many :agents_posts, :through => :agents, :source => :posts has_many :agents_posts_authors, :through => :agents_posts, :source => :author - scope :males, :conditions => { :gender => 'M' } + scope :males, :conditions => { :gender => 'M' } scope :females, :conditions => { :gender => 'F' } end @@ -56,14 +56,25 @@ class LoosePerson < ActiveRecord::Base attr_protected :comments attr_protected :as => :admin + + has_one :best_friend, :class_name => 'LoosePerson', :foreign_key => :best_friend_id + belongs_to :best_friend_of, :class_name => 'LoosePerson', :foreign_key => :best_friend_of_id + + has_many :best_friends, :class_name => 'LoosePerson', :foreign_key => :best_friend_id end class LooseDescendant < LoosePerson; end class TightPerson < ActiveRecord::Base self.table_name = 'people' + attr_accessible :first_name, :gender attr_accessible :first_name, :gender, :comments, :as => :admin + + has_one :best_friend, :class_name => 'TightPerson', :foreign_key => :best_friend_id + belongs_to :best_friend_of, :class_name => 'TightPerson', :foreign_key => :best_friend_of_id + + has_many :best_friends, :class_name => 'TightPerson', :foreign_key => :best_friend_id end class TightDescendant < TightPerson; end \ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ceadb05644..9479242e4f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -438,6 +438,8 @@ ActiveRecord::Schema.define do t.references :number1_fan t.integer :lock_version, :null => false, :default => 0 t.string :comments + t.references :best_friend + t.references :best_friend_of t.timestamps end -- cgit v1.2.3 From 86ea94e4d0c228c79b3709f0c667ba90b02e41cd Mon Sep 17 00:00:00 2001 From: Nick Howard Date: Sun, 1 May 2011 16:22:46 -0600 Subject: Fix for lighthouse #6741 - adds tests for find_or_create_by and find_or_initialize_by on has_many associations - changes the behavior of ActiveRecord::Associations::CollectionProxy#method_missing to differ to ActiveRecord::FinderMethods#find_or_instantiator_by_attributes for arg processing and saving so find_or_create_by's api on associations will be consistent w/ the api for model classes. --- .../active_record/associations/collection_proxy.rb | 9 +++++--- .../associations/has_many_associations_test.rb | 24 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 388173c1fb..adfc71d435 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -64,9 +64,12 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) - if match && match.creator? - attributes = match.attribute_names - return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)]) + if match && match.instantiator? + record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r| + @association.send :set_owner_attributes, r + @association.send :add_to_target, r + yield(r) if block_given? + end end if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method)) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 007f11b535..247decc67b 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -605,6 +605,30 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size end + def test_find_or_initialize_updates_collection_size + number_of_clients = companies(:first_firm).clients_of_firm.size + companies(:first_firm).clients_of_firm.find_or_initialize_by_name("name" => "Another Client") + assert_equal number_of_clients + 1, companies(:first_firm).clients_of_firm.size + end + + def test_find_or_create_with_hash + post = authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') + assert_equal post, authors(:david).posts.find_or_create_by_title(:title => 'Yet another post', :body => 'somebody') + assert post.persisted? + end + + def test_find_or_create_with_one_attribute_followed_by_hash + post = authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post', :body => 'somebody') + assert post.persisted? + end + + def test_find_or_create_should_work_with_block + post = authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert_equal post, authors(:david).posts.find_or_create_by_title('Yet another post') {|p| p.body = 'somebody'} + assert post.persisted? + end + def test_deleting force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first) -- cgit v1.2.3 From 2db538c8a86d0a15d37d830179bce2452a1531b7 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 2 May 2011 01:26:53 +0200 Subject: made a copy-edit pass on the streaming RDoc --- .../lib/action_controller/metal/streaming.rb | 68 +++++++++++----------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0dd847f967..1d27c3aa51 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -2,17 +2,16 @@ require 'active_support/core_ext/file/path' require 'rack/chunked' module ActionController #:nodoc: - # Allow views to be streamed back to the client as they are rendered. + # Allows views to be streamed back to the client as they are rendered. # # The default way Rails renders views is by first rendering the template - # and then the layout. The first chunk of response is sent to the client - # just after the whole template is rendered, all queries are made and the - # layout is processed. + # and then the layout. The response is sent to the client after the whole + # template is rendered, all queries are made, and the layout is processed. # # Streaming inverts the rendering flow by rendering the layout first and # streaming each part of the layout as they are processed. This allows the - # header of the html (which is usually in the layout) to be streamed back - # to client very quickly, allowing javascripts and stylesheets to be loaded + # header of the HTML (which is usually in the layout) to be streamed back + # to client very quickly, allowing JavaScripts and stylesheets to be loaded # earlier than usual. # # This approach was introduced in Rails 3.1 and is still improving. Several @@ -20,13 +19,13 @@ module ActionController #:nodoc: # Those points are going to be addressed soon. # # In order to use streaming, you will need to use a Ruby version that - # supports Fibers (Fibers are supported since version 1.9.2 of the main + # supports fibers (fibers are supported since version 1.9.2 of the main # Ruby implementation). # # == Examples # # Streaming can be added to a controller easily, all you need to do is - # call stream at the controller class: + # call +stream+ in the controller class: # # class PostsController # stream @@ -42,19 +41,19 @@ module ActionController #:nodoc: # # class PostsController # def index - # @post = Post.scoped + # @posts = Post.scoped # render :stream => true # end # end # # == When to use streaming # - # Streaming may be considering an overkill for common actions like - # new or edit. The real benefit of streaming is on expensive actions - # that, for example, does a lot of queries on the database. + # Streaming may be considered to be overkill for lightweight actions like + # +new+ or +edit+. The real benefit of streaming is on expensive actions + # that, for example, do a lot of queries on the database. # # In such actions, you want to delay queries execution as much as you can. - # For example, imagine the following dashboard action: + # For example, imagine the following +dashboard+ action: # # def dashboard # @posts = Post.all @@ -63,10 +62,10 @@ module ActionController #:nodoc: # end # # Most of the queries here are happening in the controller. In order to benefit - # most of streaming, you would want to rewrite it as: + # from streaming you would want to rewrite it as: # # def dashboard - # # Allow lazily execution of the query + # # Allow lazy execution of the queries # @posts = Post.scoped # @pages = Page.scoped # @articles = Article.scoped @@ -75,12 +74,15 @@ module ActionController #:nodoc: # # == Communication between layout and template # - # When streaming, the layout is rendered first than the template. - # This means that, if your application currently rely on variables set - # in the template to be used in the layout, they won't work once you - # move to streaming. The proper way to communicate between layout and - # template, regardless if you use streaming or not, is by using - # +content_for+, +provide+ and +yield+. + # When streaming, rendering happens top-down instead of inside-out. + # Rails starts with the layout, and the template is rendered later, + # when its +yield+ is reached. + # + # This means that, if your application currently relies on instance + # variables set in the template to be used in the layout, they won't + # work once you move to streaming. The proper way to communicate + # between layout and template, regardless of whether you use streaming + # or not, is by using +content_for+, +provide+ and +yield+. # # Take a simple example where the layout expects the template to tell # which title to use: @@ -121,16 +123,16 @@ module ActionController #:nodoc: # and you want to use streaming, you would have to render the whole template # (and eventually trigger all queries) before streaming the title and all # assets, which kills the purpose of streaming. For this reason Rails 3.1 - # introduces a helper called +provide+ that does the same as +content_for+ + # introduces a new helper called +provide+ that does the same as +content_for+ # but tells the layout to stop searching for other entries and continue rendering. # - # For instance, the template below, using +provide+: + # For instance, the template above using +provide+ would be: # # <%= provide :title, "Main" %> # Hello # <%= content_for :title, " page" %> # - # Has as final result: + # Giving: # # # Main @@ -138,26 +140,26 @@ module ActionController #:nodoc: # # # That said, when streaming, you need to properly check your templates - # and chose when to use +provide+ and +content_for+. + # and choose when to use +provide+ and +content_for+. # # == Headers, cookies, session and flash # # When streaming, the HTTP headers are sent to the client right before # it renders the first line. This means that, modifying headers, cookies, - # session or flash after the template start rendering will not propagate + # session or flash after the template starts rendering will not propagate # to the client. # - # If you try to modify cookies, session or flash, a ClosedError will be - # raised, showing those objects are closed for modification. + # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+ + # will be raised, showing those objects are closed for modification. # # == Middlewares # # Middlewares that need to manipulate the body won't work with streaming. # You should disable those middlewares whenever streaming in development - # or production. For instance, Rack::Bug won't work when streaming as it + # or production. For instance, +Rack::Bug+ won't work when streaming as it # needs to inject contents in the HTML body. # - # Also Rack::Cache won't work with streaming as it does not support + # Also +Rack::Cache+ won't work with streaming as it does not support # streaming bodies yet. So, whenever streaming, Cache-Control is automatically # set to "no-cache". # @@ -193,10 +195,10 @@ module ActionController #:nodoc: # # unicorn_rails --config-file unicorn.config.rb # - # You may also want to configure other parameters like :tcp_nodelay. Please - # check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen + # You may also want to configure other parameters like :tcp_nodelay. + # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen # - # If you are using unicorn with nginx, you may need to tweak nginx. + # If you are using Unicorn with Nginx, you may need to tweak Nginx. # Streaming should work out of the box on Rainbows. # # ==== Passenger -- cgit v1.2.3 From 635abc7194a0847fee1592eff9fa2fe84bafc5bf Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 1 May 2011 18:22:43 -0600 Subject: Remove schema noise from logging (TODO: Deprecate/remove the name attribute to all these methods -- will take a pull request!) --- .../lib/active_record/connection_adapters/mysql2_adapter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index d3a054c29b..b6f838e49c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -412,7 +412,7 @@ module ActiveRecord def tables(name = nil) tables = [] - execute("SHOW TABLES", name).each do |field| + execute("SHOW TABLES", 'SCHEMA').each do |field| tables << field.first end tables @@ -426,7 +426,7 @@ module ActiveRecord def indexes(table_name, name = nil) indexes = [] current_index = nil - result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) + result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| if current_index != row[:Key_name] next if row[:Key_name] == PRIMARY # skip the primary key @@ -444,7 +444,7 @@ module ActiveRecord def columns(table_name, name = nil) sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" columns = [] - result = execute(sql) + result = execute(sql, 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) { |field| columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") } @@ -546,7 +546,7 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) keys = [] - result = execute("describe #{quote_table_name(table)}") + result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') result.each(:symbolize_keys => true, :as => :hash) do |row| keys << row[:Field] if row[:Key] == "PRI" end -- cgit v1.2.3 From 31155eeb3ce00e5f830171d34b2037fb7a1104f0 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 1 May 2011 19:10:21 -0600 Subject: Make the identity map use the instrumentation infrastructure so we can style the messages nicely with colors (FIXME: Can someone look into why the test is not working?) --- activerecord/lib/active_record/identity_map.rb | 15 +++++++++------ activerecord/lib/active_record/log_subscriber.rb | 9 +++++++++ activerecord/test/cases/identity_map_test.rb | 17 +++++++++++++---- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb index 95a8e5cff7..4ad0b0d205 100644 --- a/activerecord/lib/active_record/identity_map.rb +++ b/activerecord/lib/active_record/identity_map.rb @@ -49,12 +49,15 @@ module ActiveRecord end def get(klass, primary_key) - obj = repository[klass.symbolized_base_class][primary_key] - if obj.is_a?(klass) - if ActiveRecord::Base.logger - ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map" - end - obj + record = repository[klass.symbolized_base_class][primary_key] + + if record.is_a?(klass) + ActiveSupport::Notifications.instrument("identity.active_record", + :line => "From Identity Map (id: #{primary_key})", + :name => "#{klass} Loaded", + :connection_id => object_id) + + record else nil end diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index d31e321440..3a015ee8c2 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -46,6 +46,15 @@ module ActiveRecord debug " #{name} #{sql}#{binds}" end + def identity(event) + return unless logger.debug? + + name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true) + line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line] + + debug " #{name} #{line}" + end + def odd? @odd_or_even = !@odd_or_even end diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index 2238529f0f..959c303d88 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -1,4 +1,6 @@ require "cases/helper" +require "active_support/log_subscriber/test_helper" + require 'models/developer' require 'models/project' require 'models/company' @@ -26,6 +28,8 @@ class IdentityMapTest < ActiveRecord::TestCase :developers_projects, :computers, :authors, :author_addresses, :posts, :tags, :taggings, :comments, :subscribers + include ActiveSupport::LogSubscriber::TestHelper + ############################################################################## # Basic tests checking if IM is functioning properly on basic find operations# ############################################################################## @@ -383,12 +387,17 @@ class IdentityMapTest < ActiveRecord::TestCase end def test_log - log = StringIO.new - ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.logger.level = Logger::DEBUG + # FIXME: Can't seem to figure out why it isn't logging in test, works fine in a real app + pending "LogSubscriber wonkiness" + @old_logger = ActiveRecord::Base.logger + ActiveRecord::LogSubscriber.attach_to(:active_record) + Post.find 1 Post.find 1 - assert_match(/Post with ID = 1 loaded from Identity Map/, log.string) + assert_match(/From Identity Map/, @logger.logged(:debug).last) + ensure + ActiveRecord::LogSubscriber.log_subscribers.pop + ActiveRecord::Base.logger = @old_logger end # Currently AR is not allowing changing primary key (see Persistence#update) -- cgit v1.2.3 From 9fa080e703362876b5afe9a7627bf3ffb6fa131e Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 2 May 2011 00:15:36 -0300 Subject: Update security guide with #new and #create respect mass-assignment --- railties/guides/source/security.textile | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index f87ffdb20d..40fe764ae9 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -441,7 +441,7 @@ params[:user] # => {:name => "ow3ned", :admin => true} @user.admin # => true
    -When assigning attributes in Active Record using +new+, +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: +When assigning attributes in Active Record using +attributes=+, or +update_attributes+ the :default scope will be used. To assign attributes using different scopes you should use +assign_attributes+ which accepts an optional :as options parameter. If no :as option is provided then the :default scope will be used. You can also bypass mass-assignment security by using the +:without_protection+ option. Here is an example: @user = User.new @@ -459,7 +459,19 @@ When assigning attributes in Active Record using +new+, +attributes=+, or +updat @user.is_admin # => true -A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of: +In a similar way, +new+, +create+ and create! methods respect mass-assignment security and accepts either +:as+ or +:without_protection+ options. For example: + + +@user = User.new({ :name => 'Sebastian', :is_admin => true }, :as => :admin) +@user.name # => Sebastian +@user.is_admin # => true + +@user = User.create({ :name => 'Sebastian', :is_admin => true }, :without_protection => true) +@user.name # => Sebastian +@user.is_admin # => true + + +A more paranoid technique to protect your whole project would be to enforce that all models define their accessible attributes. This can be easily achieved with a very simple application config option of: config.active_record.whitelist_attributes = true -- cgit v1.2.3 From a962bfe47232200c20dce02047201247d24d77f7 Mon Sep 17 00:00:00 2001 From: Chris Griego Date: Sun, 1 May 2011 23:05:42 -0500 Subject: Optimize ActiveResource::Base.new(attributes) * Add performance benchmark similar to ActiveRecord * Lazily find_or_create_resource_for_collection to not incur the overhead for empty arrays and arrays of primatives * #duplicable? is faster than inline rescues when the object is not duplicable * Don't constantly raise and handle NameError, raising is expensive * Even when a resource is nested inside a module, always look inside the class first for the resource definition so we don't overwrite classes all the time Before: user system total real Model.new (instantiation) 0.120000 0.000000 0.120000 ( 0.119961) Nested::Model.new (instantiation) 0.150000 0.010000 0.160000 ( 0.151183) Model.new (setting attributes) 28.540000 0.680000 29.220000 ( 29.271775) Nested::Model.new (setting attributes) 29.740000 0.580000 30.320000 ( 30.486210) After: user system total real Model.new (instantiation) 0.120000 0.000000 0.120000 ( 0.121249) Nested::Model.new (instantiation) 0.150000 0.010000 0.160000 ( 0.152429) Model.new (setting attributes) 11.480000 0.170000 11.650000 ( 11.656163) Nested::Model.new (setting attributes) 11.510000 0.210000 11.720000 ( 11.724249) --- activeresource/examples/performance.rb | 70 ++++++++++++++++++++++++++++++ activeresource/lib/active_resource/base.rb | 39 ++++++++++------- 2 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 activeresource/examples/performance.rb diff --git a/activeresource/examples/performance.rb b/activeresource/examples/performance.rb new file mode 100644 index 0000000000..e4df7a38a4 --- /dev/null +++ b/activeresource/examples/performance.rb @@ -0,0 +1,70 @@ +require 'rubygems' +require 'active_resource' +require 'benchmark' + +TIMES = (ENV['N'] || 10_000).to_i + +# deep nested resource +attrs = { + :id => 1, + :name => 'Luis', + :age => 21, + :friends => [ + { + :name => 'JK', + :age => 24, + :colors => ['red', 'green', 'blue'], + :brothers => [ + { + :name => 'Mateo', + :age => 35, + :children => [{ :name => 'Edith', :age => 5 }, { :name => 'Martha', :age => 4 }] + }, + { + :name => 'Felipe', + :age => 33, + :children => [{ :name => 'Bryan', :age => 1 }, { :name => 'Luke', :age => 0 }] + } + ] + }, + { + :name => 'Eduardo', + :age => 20, + :colors => [], + :brothers => [ + { + :name => 'Sebas', + :age => 23, + :children => [{ :name => 'Andres', :age => 0 }, { :name => 'Jorge', :age => 2 }] + }, + { + :name => 'Elsa', + :age => 19, + :children => [{ :name => 'Natacha', :age => 1 }] + }, + { + :name => 'Milena', + :age => 16, + :children => [] + } + ] + } + ] +} + +class Customer < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" +end + +module Nested + class Customer < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" + end +end + +Benchmark.bm(40) do |x| + x.report('Model.new (instantiation)') { TIMES.times { Customer.new } } + x.report('Nested::Model.new (instantiation)') { TIMES.times { Nested::Customer.new } } + x.report('Model.new (setting attributes)') { TIMES.times { Customer.new attrs } } + x.report('Nested::Model.new (setting attributes)') { TIMES.times { Nested::Customer.new attrs } } +end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 160763779e..7f2a844723 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1239,9 +1239,10 @@ module ActiveResource @attributes[key.to_s] = case value when Array - resource = find_or_create_resource_for_collection(key) + resource = nil value.map do |attrs| if attrs.is_a?(Hash) + resource ||= find_or_create_resource_for_collection(key) resource.new(attrs) else attrs.duplicable? ? attrs.dup : attrs @@ -1251,7 +1252,7 @@ module ActiveResource resource = find_or_create_resource_for(key) resource.new(value) else - value.dup rescue value + value.duplicable? ? value.dup : value end end self @@ -1367,36 +1368,44 @@ module ActiveResource end # Tries to find a resource in a non empty list of nested modules - # Raises a NameError if it was not found in any of the given nested modules - def find_resource_in_modules(resource_name, module_names) + # if it fails, then the resource is created + def find_or_create_resource_in_modules(resource_name, module_names) receiver = Object namespaces = module_names[0, module_names.size-1].map do |module_name| receiver = receiver.const_get(module_name) end const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] if namespace = namespaces.reverse.detect { |ns| ns.const_defined?(*const_args) } - return namespace.const_get(*const_args) + namespace.const_get(*const_args) else - raise NameError + create_resource_for(resource_name) end end # Tries to find a resource for a given name; if it fails, then the resource is created def find_or_create_resource_for(name) resource_name = name.to_s.camelize - ancestors = self.class.name.split("::") - if ancestors.size > 1 - find_resource_in_modules(resource_name, ancestors) - else - self.class.const_get(resource_name) - end - rescue NameError + const_args = RUBY_VERSION < "1.9" ? [resource_name] : [resource_name, false] if self.class.const_defined?(*const_args) - resource = self.class.const_get(*const_args) + self.class.const_get(*const_args) else - resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) + ancestors = self.class.name.split("::") + if ancestors.size > 1 + find_or_create_resource_in_modules(resource_name, ancestors) + else + if Object.const_defined?(*const_args) + Object.const_get(*const_args) + else + create_resource_for(resource_name) + end + end end + end + + # Create and return a class definition for a resource inside the current resource + def create_resource_for(resource_name) + resource = self.class.const_set(resource_name, Class.new(ActiveResource::Base)) resource.prefix = self.class.prefix resource.site = self.class.site resource -- cgit v1.2.3 From f76726c09d74c4de937054b75464857d8b8edb76 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 1 May 2011 23:23:31 -0700 Subject: Use sprockets beta gem --- actionpack/actionpack.gemspec | 1 + railties/lib/rails/generators/app_base.rb | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 0d667a76a7..a2570587ce 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -25,6 +25,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.2') + s.add_dependency('sprockets', '~> 2.0.0.beta.1') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 324199e71c..689ef921e1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -132,14 +132,12 @@ module Rails gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', :git => 'git://github.com/rails/rails.git' gem 'arel', :git => 'git://github.com/rails/arel.git' gem 'rack', :git => 'git://github.com/rack/rack.git' - gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE else <<-GEMFILE.strip_heredoc @@ -149,7 +147,6 @@ module Rails # gem 'rails', :git => 'git://github.com/rails/rails.git' # gem 'arel', :git => 'git://github.com/rails/arel.git' # gem 'rack', :git => 'git://github.com/rack/rack.git' - # gem 'sprockets', :git => 'git://github.com/sstephenson/sprockets.git' GEMFILE end end @@ -167,7 +164,7 @@ module Rails else options[:database] end end - + def gem_for_ruby_debugger if RUBY_VERSION < "1.9.2" "gem 'ruby-debug'" @@ -175,7 +172,7 @@ module Rails "gem 'ruby-debug19', :require => 'ruby-debug'" end end - + def gem_for_turn unless RUBY_VERSION < "1.9.2" <<-GEMFILE.strip_heredoc @@ -200,7 +197,7 @@ module Rails empty_directory(destination, config) git_keep(destination) end - + def git_keep(destination) create_file("#{destination}/.gitkeep") unless options[:skip_git] end @@ -216,4 +213,4 @@ module Rails end end end -end \ No newline at end of file +end -- cgit v1.2.3 From 3fc37e8ef96b3f139427189b58c426ec7fff65be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 May 2011 09:00:54 +0200 Subject: Fix failing AR test. --- activerecord/test/cases/identity_map_test.rb | 17 ----------------- activerecord/test/cases/log_subscriber_test.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/activerecord/test/cases/identity_map_test.rb b/activerecord/test/cases/identity_map_test.rb index 959c303d88..649715fbb5 100644 --- a/activerecord/test/cases/identity_map_test.rb +++ b/activerecord/test/cases/identity_map_test.rb @@ -1,5 +1,4 @@ require "cases/helper" -require "active_support/log_subscriber/test_helper" require 'models/developer' require 'models/project' @@ -28,8 +27,6 @@ class IdentityMapTest < ActiveRecord::TestCase :developers_projects, :computers, :authors, :author_addresses, :posts, :tags, :taggings, :comments, :subscribers - include ActiveSupport::LogSubscriber::TestHelper - ############################################################################## # Basic tests checking if IM is functioning properly on basic find operations# ############################################################################## @@ -386,20 +383,6 @@ class IdentityMapTest < ActiveRecord::TestCase assert_not_nil post.title end - def test_log - # FIXME: Can't seem to figure out why it isn't logging in test, works fine in a real app - pending "LogSubscriber wonkiness" - @old_logger = ActiveRecord::Base.logger - ActiveRecord::LogSubscriber.attach_to(:active_record) - - Post.find 1 - Post.find 1 - assert_match(/From Identity Map/, @logger.logged(:debug).last) - ensure - ActiveRecord::LogSubscriber.log_subscribers.pop - ActiveRecord::Base.logger = @old_logger - end - # Currently AR is not allowing changing primary key (see Persistence#update) # So we ignore it. If this changes, this test needs to be uncommented. # def test_updating_of_pkey diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb index 5f55299065..c6c6079490 100644 --- a/activerecord/test/cases/log_subscriber_test.rb +++ b/activerecord/test/cases/log_subscriber_test.rb @@ -1,11 +1,14 @@ require "cases/helper" require "models/developer" +require "models/post" require "active_support/log_subscriber/test_helper" class LogSubscriberTest < ActiveRecord::TestCase include ActiveSupport::LogSubscriber::TestHelper include ActiveSupport::BufferedLogger::Severity + fixtures :posts + def setup @old_logger = ActiveRecord::Base.logger @using_identity_map = ActiveRecord::IdentityMap.enabled? @@ -91,4 +94,13 @@ class LogSubscriberTest < ActiveRecord::TestCase def test_initializes_runtime Thread.new { assert_equal 0, ActiveRecord::LogSubscriber.runtime }.join end + + def test_log + ActiveRecord::IdentityMap.use do + Post.find 1 + Post.find 1 + end + wait + assert_match(/From Identity Map/, @logger.logged(:debug).last) + end end -- cgit v1.2.3 From 527036ebd106fdb4d5890f00f2576a99c57b1514 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Mon, 2 May 2011 17:31:00 +0530 Subject: removed verify docs (feature removed in Rails3) --- .../source/action_controller_overview.textile | 39 ---------------------- 1 file changed, 39 deletions(-) diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index f8b586c151..3a1a4ee66e 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -488,45 +488,6 @@ end Again, this is not an ideal example for this filter, because it's not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method +filter+ which gets run before or after the action, depending on if it's a before or after filter. Classes used as around filters can also use the same +filter+ method, which will get run in the same way. The method must +yield+ to execute the action. Alternatively, it can have both a +before+ and an +after+ method that are run before and after the action. -h3. Verification - -Verifications make sure certain criteria are met in order for a controller or action to run. They can specify that a certain key (or several keys in the form of an array) is present in the +params+, +session+ or +flash+ hashes or that a certain HTTP method was used or that the request was made using +XMLHttpRequest+ (Ajax). The default action taken when these criteria are not met is to render a 400 Bad Request response, but you can customize this by specifying a redirect URL or rendering something else and you can also add flash messages and HTTP headers to the response. - -Here's an example of using verification to make sure the user supplies a username and a password in order to log in: - - -class LoginsController < ApplicationController - verify :params => [:username, :password], - :render => {:action => "new"}, - :add_flash => { - :error => "Username and password required to log in" - } - - def create - @user = User.authenticate(params[:username], params[:password]) - if @user - flash[:notice] = "You're logged in" - redirect_to root_url - else - render :action => "new" - end - end -end - - -Now the +create+ action won't run unless the "username" and "password" parameters are present, and if they're not, an error message will be added to the flash and the +new+ action will be rendered. But there's something rather important missing from the verification above: It will be used for *every* action in LoginsController, which is not what we want. You can limit which actions it will be used for with the +:only+ and +:except+ options just like a filter: - - -class LoginsController < ApplicationController - verify :params => [:username, :password], - :render => {:action => "new"}, - :add_flash => { - :error => "Username and password required to log in" - }, - :only => :create # Run only for the "create" action -end - - h3. Request Forgery Protection Cross-site request forgery is a type of attack in which a site tricks a user into making requests on another site, possibly adding, modifying or deleting data on that site without the user's knowledge or permission. -- cgit v1.2.3 From 09edaf49646c14b6162726c1fb2bc0e980c3962f Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Mon, 2 May 2011 17:39:46 +0530 Subject: removed reference to verify method --- railties/guides/source/security.textile | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile index 40fe764ae9..8c408ec06b 100644 --- a/railties/guides/source/security.textile +++ b/railties/guides/source/security.textile @@ -211,15 +211,7 @@ The HTTP protocol basically provides two main types of requests - GET and POST ( If your web application is RESTful, you might be used to additional HTTP verbs, such as PUT or DELETE. Most of today‘s web browsers, however do not support them - only GET and POST. Rails uses a hidden +_method+ field to handle this barrier. -_(highlight)The verify method in a controller can make sure that specific actions may not be used over GET_. Here is an example to verify the use of the transfer action over POST. If the action comes in using any other verb, it redirects to the list action. - - -verify :method => :post, :only => [:transfer], :redirect_to => {:action => :list} - - -With this precaution, the attack from above will not work, because the browser sends a GET request for images, which will not be accepted by the web application. - -But this was only the first step, because _(highlight)POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request. +_(highlight)POST requests can be sent automatically, too_. Here is an example for a link which displays www.harmless.com as destination in the browser's status bar. In fact it dynamically creates a new form that sends a POST request. where
    , includes, and joins operations in Relation, which are merged. # # joins operations are uniqued so multiple scopes can join in the same table without table aliasing - # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the + # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the # array of strings format for your joins. # # class Article < ActiveRecord::Base @@ -1400,7 +1400,7 @@ end end.join(', ') end - # Accepts an array of conditions. The array has each value + # Accepts an array of conditions. The array has each value # sanitized and interpolated into the SQL statement. # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" def sanitize_sql_array(ary) @@ -1508,7 +1508,7 @@ end end # Populate +coder+ with attributes about this record that should be - # serialized. The structure of +coder+ defined in this method is + # serialized. The structure of +coder+ defined in this method is # guaranteed to match the structure of +coder+ passed to the +init_with+ # method. # @@ -1523,8 +1523,8 @@ end coder['attributes'] = attributes end - # Initialize an empty model object from +coder+. +coder+ must contain - # the attributes necessary for initializing an empty model object. For + # Initialize an empty model object from +coder+. +coder+ must contain + # the attributes necessary for initializing an empty model object. For # example: # # class Post < ActiveRecord::Base -- cgit v1.2.3 From 3066ea8b44503c16b1a0ad5860ae01470912151a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 2 May 2011 11:13:49 -0700 Subject: add a couple test cases surrounding query cache middleware --- activerecord/test/cases/query_cache_test.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 287f7e255b..ebe7cae04a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -12,6 +12,24 @@ class QueryCacheTest < ActiveRecord::TestCase Task.connection.clear_query_cache end + def test_middleware_delegates + called = false + mw = ActiveRecord::QueryCache.new lambda { |env| + called = true + } + mw.call({}) + assert called, 'middleware should delegate' + end + + def test_middleware + mw = ActiveRecord::QueryCache.new lambda { |env| + Task.find 1 + Task.find 1 + assert_equal 1, ActiveRecord::Base.connection.query_cache.length + } + mw.call({}) + end + def test_find_queries assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) } end -- cgit v1.2.3 From 4300855e7dccb06017e6d8de203c60497e5a5321 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 2 May 2011 11:17:31 -0700 Subject: more tests around caching --- activerecord/test/cases/query_cache_test.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index ebe7cae04a..66ec592cca 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -21,7 +21,7 @@ class QueryCacheTest < ActiveRecord::TestCase assert called, 'middleware should delegate' end - def test_middleware + def test_middleware_caches mw = ActiveRecord::QueryCache.new lambda { |env| Task.find 1 Task.find 1 @@ -30,6 +30,15 @@ class QueryCacheTest < ActiveRecord::TestCase mw.call({}) end + def test_cache_enabled_during_call + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off' + + mw = ActiveRecord::QueryCache.new lambda { |env| + assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on' + } + mw.call({}) + end + def test_find_queries assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) } end -- cgit v1.2.3 From 951e18abea9c116fc5d6b330ca1dcd2890abe006 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 2 May 2011 11:30:49 -0700 Subject: introduce a body proxy to ensure that query cache is enabled during streaming --- .../connection_adapters/abstract/query_cache.rb | 8 +++++++ activerecord/lib/active_record/query_cache.rb | 27 +++++++++++++++++++--- activerecord/test/cases/query_cache_test.rb | 26 +++++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 1db397f584..093c30aa42 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -29,6 +29,14 @@ module ActiveRecord @query_cache_enabled = old end + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + end + # Disable the query cache within the block. def uncached old, @query_cache_enabled = @query_cache_enabled, false diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index d9f85a4e5e..929998eb85 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -27,10 +27,31 @@ module ActiveRecord @app = app end - def call(env) - ActiveRecord::Base.cache do - @app.call(env) + class BodyProxy # :nodoc: + def initialize(original_cache_value, target) + @original_cache_value = original_cache_value + @target = target + end + + def each(&block) + @target.each(&block) + end + + def close + @target.close if @target.respond_to?(:close) + ensure + unless @original_cache_value + ActiveRecord::Base.connection.disable_query_cache! + end end end + + def call(env) + old = ActiveRecord::Base.connection.query_cache_enabled + ActiveRecord::Base.connection.enable_query_cache! + + status, headers, body = @app.call(env) + [status, headers, BodyProxy.new(old, body)] + end end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 66ec592cca..b2e40c6b22 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -10,6 +10,7 @@ class QueryCacheTest < ActiveRecord::TestCase def setup Task.connection.clear_query_cache + ActiveRecord::Base.connection.disable_query_cache! end def test_middleware_delegates @@ -39,6 +40,31 @@ class QueryCacheTest < ActiveRecord::TestCase mw.call({}) end + def test_cache_on_during_body_write + streaming = Class.new do + def each + yield ActiveRecord::Base.connection.query_cache_enabled + end + end + + mw = ActiveRecord::QueryCache.new lambda { |env| + [200, {}, streaming.new] + } + body = mw.call({}).last + body.each { |x| assert x, 'cache should be on' } + body.close + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' + end + + def test_cache_off_after_close + mw = ActiveRecord::QueryCache.new lambda { |env| } + body = mw.call({}).last + + assert ActiveRecord::Base.connection.query_cache_enabled, 'cache enabled' + body.close + assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache disabled' + end + def test_find_queries assert_queries(ActiveRecord::IdentityMap.enabled? ? 1 : 2) { Task.find(1); Task.find(1) } end -- cgit v1.2.3 From c577f186aa4b7bcf12acfcc5b7fc7760d96cc663 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 3 May 2011 02:33:31 +0700 Subject: Update CHANGELOG file to mention my name On the commit for updating :format parameters, seems like I've left out the credit name after the CHANGELOG entry. This should fix it, and in case someone has a problem they could seek assistance easier. --- actionpack/CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 76dbfe7895..07a23ddb63 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -4,7 +4,9 @@ * Implicit actions named not_implemented can be rendered [Santiago Pastorino] -* Wildcard route will always matching the optional format segment by default. For example if you have this route: +* Wildcard route will always matching the optional format segment by default. [Prem Sichanugrist] + + For example if you have this route: map '*pages' => 'pages#show' -- cgit v1.2.3 From 8c9e4d520291871e5319bc0e0a890527d8aea099 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 28 Apr 2011 15:56:11 +0700 Subject: Add `ActionController::ParamsWrapper` to wrap parameters into a nested hash This will allow us to do a rootless JSON/XML request to server. --- actionpack/CHANGELOG | 4 + actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/base.rb | 1 + .../lib/action_controller/metal/params_wrapper.rb | 197 +++++++++++++++++++++ actionpack/test/controller/params_wrapper_test.rb | 187 +++++++++++++++++++ .../dispatch/request/json_params_parsing_test.rb | 53 ++++++ .../dispatch/request/xml_params_parsing_test.rb | 38 ++++ .../config/initializers/wrap_parameters.rb.tt | 12 ++ railties/test/application/configuration_test.rb | 10 ++ 9 files changed, 503 insertions(+) create mode 100644 actionpack/lib/action_controller/metal/params_wrapper.rb create mode 100644 actionpack/test/controller/params_wrapper_test.rb create mode 100644 railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 76dbfe7895..f692b169df 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.1.0 (unreleased)* +* Add `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist] + + This can be customizabled by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb` + * RJS has been extracted out to a gem. [fxn] * Implicit actions named not_implemented can be rendered [Santiago Pastorino] diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index aab2b9dc25..eba5e9377b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -23,6 +23,7 @@ module ActionController autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds + autoload :ParamsWrapper autoload :RackDelegation autoload :Redirecting autoload :Renderers diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index ca0dccf575..373df7fb55 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -194,6 +194,7 @@ module ActionController Caching, MimeResponds, ImplicitRender, + ParamsWrapper, Cookies, Flash, diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb new file mode 100644 index 0000000000..29ff546139 --- /dev/null +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -0,0 +1,197 @@ +require 'active_support/core_ext/class/attribute' +require 'action_dispatch/http/mime_types' + +module ActionController + # Wraps parameters hash into nested hash. This will allow client to submit + # POST request without having to specify a root element in it. + # + # By default, this functionality won't be enabled by default. You can enable + # it globally by setting +ActionController::Base.wrap_parameters+: + # + # ActionController::Base.wrap_parameters = [:json] + # + # You could also turn it on per controller by setting the format array to + # non-empty array: + # + # class UsersController < ApplicationController + # wrap_parameters :format => [:json, :xml] + # end + # + # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to + # send JSON parameters like this: + # + # {"user": {"name": "Konata"}} + # + # You can now just send a parameters like this: + # + # {"name": "Konata"} + # + # And it will be wrapped into a nested hash with the key name matching + # controller's name. For example, if you're posting to +UsersController+, + # your new +params+ hash will look like this: + # + # {"name" => "Konata", "user" => {"name" => "Konata"}} + # + # You can also specify the key in which the parameters should be wrapped to, + # and also the list of attributes it should wrap by using either +:only+ or + # +:except+ options like this: + # + # class UsersController < ApplicationController + # wrap_parameters :person, :only => [:username, :password] + # end + # + # If you're going to pass the parameters to an +ActiveModel+ object (such as + # +User.new(params[:user])+), you might consider passing the model class to + # the method instead. The +ParamsWrapper+ will actually try to determine the + # list of attribute names from the model and only wrap those attributes: + # + # class UsersController < ApplicationController + # wrap_parameters Person + # end + # + # You still could pass +:only+ and +:except+ to set the list of attributes + # you want to wrap. + # + # By default, if you don't specify the key in which the parameters would be + # wrapped to, +ParamsWrapper+ will actually try to determine if there's + # a model related to it or not. This controller, for example: + # + # class Admin::UsersController < ApplicationController + # end + # + # will try to check if +Admin::User+ or +User+ model exists, and use it to + # determine the wrapper key respectively. If both of the model doesn't exists, + # it will then fallback to use +user+ as the key. + module ParamsWrapper + extend ActiveSupport::Concern + + EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) + + included do + class_attribute :_wrapper_options + self._wrapper_options = {:format => []} + end + + module ClassMethods + # Sets the name of the wrapper key, or the model which +ParamsWrapper+ + # would use to determine the attribute names from. + # + # ==== Examples + # wrap_parameters :format => :xml + # # enables the parmeter wrappes for XML format + # + # wrap_parameters :person + # # wraps parameters into +params[:person]+ hash + # + # wrap_parameters Person + # # wraps parameters by determine the wrapper key from Person class + # (+person+, in this case) and the list of attribute names + # + # wrap_parameters :only => [:username, :title] + # # wraps only +:username+ and +:title+ attributes from parameters. + # + # wrap_parameters false + # # disable parameters wrapping for this controller altogether. + # + # ==== Options + # * :format - The list of formats in which the parameters wrapper + # will be enabled. + # * :only - The list of attribute names which parmeters wrapper + # will wrap into a nested hash. + # * :only - The list of attribute names which parmeters wrapper + # will exclude from a nested hash. + def wrap_parameters(name_or_model_or_options, options = {}) + if !name_or_model_or_options.is_a? Hash + if name_or_model_or_options != false + options = options.merge(:name_or_model => name_or_model_or_options) + else + options = opions.merge(:format => []) + end + else + options = name_or_model_or_options + end + + options[:name_or_model] ||= _default_wrap_model + self._wrapper_options = self._wrapper_options.merge(options) + end + + # Sets the default wrapper key or model which will be used to determine + # wrapper key and attribute names. Will be called automatically when the + # module is inherited. + def inherited(klass) + if klass._wrapper_options[:format].present? + klass._wrapper_options = klass._wrapper_options.merge(:name_or_model => klass._default_wrap_model) + end + super + end + + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singularize name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + def _default_wrap_model + model_name = self.name.sub(/Controller$/, '').singularize + + begin + model_klass = model_name.constantize + rescue NameError => e + unscoped_model_name = model_name.split("::", 2).last + break if unscoped_model_name == model_name + model_name = unscoped_model_name + end until model_klass + + model_klass + end + end + + # Performs parameters wrapping upon the request. Will be called automatically + # by the metal call stack. + def process_action(*args) + if _wrapper_enabled? + wrapped_hash = { _wrapper_key => request.request_parameters.slice(*_wrapped_keys) } + wrapped_filtered_hash = { _wrapper_key => request.filtered_parameters.slice(*_wrapped_keys) } + + # This will make the wrapped hash accessible from controller and view + request.parameters.merge! wrapped_hash + request.request_parameters.merge! wrapped_hash + + # This will make the wrapped hash displayed in the log file + request.filtered_parameters.merge! wrapped_filtered_hash + end + super + end + + private + # Returns the wrapper key which will use to stored wrapped parameters. + def _wrapper_key + @_wrapper_key ||= if _wrapper_options[:name_or_model] + _wrapper_options[:name_or_model].to_s.demodulize.underscore + else + self.class.controller_name.singularize + end + end + + # Returns the list of parameters which will be selected for wrapped. + def _wrapped_keys + @_wrapped_keys ||= if _wrapper_options[:only] + Array(_wrapper_options[:only]).collect(&:to_s) + elsif _wrapper_options[:except] + request.request_parameters.keys - Array(_wrapper_options[:except]).collect(&:to_s) - EXCLUDE_PARAMETERS + elsif _wrapper_options[:name_or_model].respond_to?(:column_names) + _wrapper_options[:name_or_model].column_names + else + request.request_parameters.keys - EXCLUDE_PARAMETERS + end + end + + # Returns the list of enabled formats. + def _wrapper_formats + Array(_wrapper_options[:format]) + end + + # Checks if we should perform parameters wrapping. + def _wrapper_enabled? + _wrapper_formats.any?{ |format| format == request.content_mime_type.try(:ref) } && request.request_parameters[_wrapper_key].nil? + end + end +end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb new file mode 100644 index 0000000000..2e5d096fcd --- /dev/null +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -0,0 +1,187 @@ +require 'abstract_unit' + +module Admin; class User; end; end + +class ParamsWrapperTest < ActionController::TestCase + class UsersController < ActionController::Base + def test + render :json => params.except(:controller, :action) + end + end + + class User; end + class Person; end + + tests UsersController + + def test_derivered_name_from_controller + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu' } + assert_equal '{"username":"sikachu","user":{"username":"sikachu"}}', @response.body + end + end + + def test_specify_wrapper_name + with_default_wrapper_options do + UsersController.wrap_parameters :person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu' } + assert_equal '{"username":"sikachu","person":{"username":"sikachu"}}', @response.body + end + end + + def test_specify_wrapper_model + with_default_wrapper_options do + UsersController.wrap_parameters Person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu' } + assert_equal '{"username":"sikachu","person":{"username":"sikachu"}}', @response.body + end + end + + def test_specify_only_option + with_default_wrapper_options do + UsersController.wrap_parameters :only => :username + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + end + end + + def test_specify_except_option + with_default_wrapper_options do + UsersController.wrap_parameters :except => :title + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + end + end + + def test_specify_both_wrapper_name_and_only_option + with_default_wrapper_options do + UsersController.wrap_parameters :person, :only => :username + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","person":{"username":"sikachu"}}', @response.body + end + end + + def test_not_enabled_format + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/xml' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer"}', @response.body + end + end + + def test_specify_format + with_default_wrapper_options do + UsersController.wrap_parameters :format => :xml + + @request.env['CONTENT_TYPE'] = 'application/xml' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu","title":"Developer"}}', @response.body + end + end + + def test_not_wrap_reserved_parameters + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } + assert_equal '{"authenticity_token":"pwned","_method":"put","utf8":"☃","username":"sikachu","user":{"username":"sikachu"}}', @response.body + end + end + + def test_no_double_wrap_if_key_exists + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'user' => { 'username' => 'sikachu' }} + assert_equal '{"user":{"username":"sikachu"}}', @response.body + end + end + + def test_nested_params + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'person' => { 'username' => 'sikachu' }} + assert_equal '{"person":{"username":"sikachu"},"user":{"person":{"username":"sikachu"}}}', @response.body + end + end + + def test_derived_wrapped_keys_from_matching_model + with_default_wrapper_options do + User.expects(:respond_to?).with(:column_names).returns(true) + User.expects(:column_names).returns(["username"]) + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + end + end + + def test_derived_wrapped_keys_from_specified_model + with_default_wrapper_options do + Person.expects(:respond_to?).with(:column_names).returns(true) + Person.expects(:column_names).returns(["username"]) + + UsersController.wrap_parameters Person + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","person":{"username":"sikachu"}}', @response.body + end + end + + private + def with_default_wrapper_options(&block) + @controller.class._wrapper_options = {:format => [:json]} + @controller.class.inherited(@controller.class) + yield + end +end + +class NamespacedParamsWrapperTest < ActionController::TestCase + module Admin + class UsersController < ActionController::Base + def test + render :json => params.except(:controller, :action) + end + end + + class User; end + end + class User; end + class Person; end + + tests Admin::UsersController + + def test_derivered_name_from_controller + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu' } + assert_equal '{"username":"sikachu","user":{"username":"sikachu"}}', @response.body + end + end + + def test_namespace_lookup_when_namespaced_model_available + with_default_wrapper_options do + Admin::User.expects(:respond_to?).with(:column_names).returns(false) + + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu' } + end + end + + private + def with_default_wrapper_options(&block) + @controller.class._wrapper_options = {:format => [:json]} + @controller.class.inherited(@controller.class) + yield + end +end diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index 34db7a4c66..d854d55173 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -63,3 +63,56 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest end end end + +class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest + class UsersController < ActionController::Base + wrap_parameters :format => :json + + class << self + attr_accessor :last_request_parameters, :last_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + self.class.last_parameters = params + head :ok + end + end + + def teardown + UsersController.last_request_parameters = nil + end + + test "parses json params for application json" do + assert_parses( + {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, + "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + + test "parses json params for application jsonrequest" do + assert_parses( + {"user" => {"username" => "sikachu"}, "username" => "sikachu"}, + "{\"username\": \"sikachu\"}", { 'CONTENT_TYPE' => 'application/jsonrequest' } + ) + end + + private + def assert_parses(expected, actual, headers = {}) + with_test_routing(UsersController) do + post "/parse", actual, headers + assert_response :ok + assert_equal(expected, UsersController.last_request_parameters) + assert_equal(expected.merge({"action" => "parse"}), UsersController.last_parameters) + end + end + + def with_test_routing(controller) + with_routing do |set| + set.draw do + match ':action', :to => controller + end + yield + end + end +end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index ad9de02eb4..38453dfe48 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -115,3 +115,41 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest {'HTTP_X_POST_DATA_FORMAT' => 'xml'} end end + +class RootLessXmlParamsParsingTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + wrap_parameters :person, :format => :xml + + class << self + attr_accessor :last_request_parameters + end + + def parse + self.class.last_request_parameters = request.request_parameters + head :ok + end + end + + def teardown + TestController.last_request_parameters = nil + end + + test "parses hash params" do + with_test_routing do + xml = "David" + post "/parse", xml, {'CONTENT_TYPE' => 'application/xml'} + assert_response :ok + assert_equal({"name" => "David", "person" => {"name" => "David"}}, TestController.last_request_parameters) + end + end + + private + def with_test_routing + with_routing do |set| + set.draw do + match ':action', :to => ::RootLessXmlParamsParsingTest::TestController + end + yield + end + end +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt new file mode 100644 index 0000000000..60137ed2bb --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -0,0 +1,12 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains the settings for ActionController::ParametersWrapper +# which will be enabled by default in the upcoming version of Ruby on Rails. + +# Enable parameter wrapping for JSON. You can disable this by set :format to empty array. +ActionController::Base.wrap_parameters :format => [:json] + +# Disable root element in JSON by default. +if defined?(ActiveRecord) + ActiveRecord::Base.include_root_in_json = false +end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index ab3eb4c9e7..8f96f4c57b 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -432,5 +432,15 @@ module ApplicationTests get "/" assert_equal 'true', last_response.body end + + test "config.action_controller.wrap_parameters is set in ActionController::Base" do + app_file 'config/initializers/wrap_parameters.rb', <<-RUBY + ActionController::Base.wrap_parameters :format => [:json] + RUBY + require "#{app_path}/config/environment" + require 'action_controller/base' + + assert_equal [:json], ActionController::Base._wrapper_options[:format] + end end end -- cgit v1.2.3 From 73c94ed97ab6639d06dade1738aa5b9f49294340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 May 2011 23:33:58 +0200 Subject: Add ignore_accept_header config to AD::Request. --- .../lib/action_dispatch/http/mime_negotiation.rb | 27 ++++++++++++++++++---- actionpack/test/dispatch/request_test.rb | 22 ++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 68ba1a81b5..980c658ab7 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -1,6 +1,13 @@ module ActionDispatch module Http module MimeNegotiation + extend ActiveSupport::Concern + + included do + mattr_accessor :ignore_accept_header + self.ignore_accept_header = false + end + # The MIME type of the HTTP request, such as Mime::XML. # # For backward compatibility, the post \format is extracted from the @@ -42,16 +49,14 @@ module ActionDispatch formats.first end - BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ - def formats - accept = @env['HTTP_ACCEPT'] - @env["action_dispatch.request.formats"] ||= if parameters[:format] Array(Mime[parameters[:format]]) - elsif xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) + elsif use_accept_header && valid_accept_header accepts + elsif xhr? + [Mime::JS] else [Mime::HTML] end @@ -87,6 +92,18 @@ module ActionDispatch order.include?(Mime::ALL) ? formats.first : nil end + + protected + + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + + def valid_accept_header + xhr? || (accept && accept !~ BROWSER_LIKE_ACCEPTS) + end + + def use_accept_header + !self.class.ignore_accept_header + end end end end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f03ae7f2b3..86bbbab175 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -452,6 +452,28 @@ class RequestTest < ActiveSupport::TestCase assert request.formats.empty? end + test "ignore_accept_header" do + ActionDispatch::Request.ignore_accept_header = true + + begin + request = stub_request 'HTTP_ACCEPT' => 'application/xml' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/xml', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::JS ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/xml', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({:format => :json}) + assert_equal [ Mime::JSON ], request.formats + ensure + ActionDispatch::Request.ignore_accept_header = false + end + end + test "negotiate_mime" do request = stub_request 'HTTP_ACCEPT' => 'text/html', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" -- cgit v1.2.3 From 83e35b9c08b97db7605542e69a8fa8d23c7df211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 May 2011 23:38:39 +0200 Subject: Allow ignore_accept_header through configuration option. --- actionpack/lib/action_dispatch/railtie.rb | 2 ++ railties/test/application/configuration_test.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 0a3bd5fe40..f51cc3711b 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -9,10 +9,12 @@ module ActionDispatch config.action_dispatch.show_exceptions = true config.action_dispatch.best_standards_support = true config.action_dispatch.tld_length = 1 + config.action_dispatch.ignore_accept_header = false config.action_dispatch.rack_cache = {:metastore => "rails:/", :entitystore => "rails:/", :verbose => true} initializer "action_dispatch.configure" do |app| ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length + ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header end end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 8f96f4c57b..b1f7076776 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -442,5 +442,26 @@ module ApplicationTests assert_equal [:json], ActionController::Base._wrapper_options[:format] end + + test "config.action_dispatch.ignore_accept_header" do + make_basic_app do |app| + app.config.action_dispatch.ignore_accept_header = true + end + + class ::OmgController < ActionController::Base + def index + respond_to do |format| + format.html { render :text => "HTML" } + format.xml { render :text => "XML" } + end + end + end + + get "/", {}, "HTTP_ACCEPT" => "application/xml" + assert_equal 'HTML', last_response.body + + get "/", { :format => :xml }, "HTTP_ACCEPT" => "application/xml" + assert_equal 'XML', last_response.body + end end end -- cgit v1.2.3 From 11db2ae4e0f6e60c9619a808350c1550c4c78c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 May 2011 23:44:23 +0200 Subject: Koz asked to test something crazy. --- actionpack/test/dispatch/request_test.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 86bbbab175..fb2faf7a4e 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -460,6 +460,18 @@ class RequestTest < ActiveSupport::TestCase request.expects(:parameters).at_least_once.returns({}) assert_equal [ Mime::HTML ], request.formats + request = stub_request 'HTTP_ACCEPT' => 'koz-asked/something-crazy' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => '*/*;q=0.1' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + + request = stub_request 'HTTP_ACCEPT' => 'application/jxw' + request.expects(:parameters).at_least_once.returns({}) + assert_equal [ Mime::HTML ], request.formats + request = stub_request 'HTTP_ACCEPT' => 'application/xml', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) -- cgit v1.2.3 From b29a905f949dbed5052c55184bd5e0838517ec8d Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 2 May 2011 17:04:21 -0500 Subject: Flunk makes a lot more sense, doesnt it (hat tip @tenderlove) --- actionpack/lib/action_dispatch/testing/assertions/response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index e209978fb7..3335742d47 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -42,7 +42,7 @@ module ActionDispatch elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert(false, build_message(message, "Expected response to be a , but was ", type, @response.response_code)) + flunk(build_message(message, "Expected response to be a , but was ", type, @response.response_code)) end end -- cgit v1.2.3 From c894fff60a9146754fc9f7c4ddf992634c2bedd3 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 2 May 2011 19:07:48 -0300 Subject: Fix ParamsWrapper docs errors --- actionpack/lib/action_controller/metal/params_wrapper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 29ff546139..7827ed598e 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -5,7 +5,7 @@ module ActionController # Wraps parameters hash into nested hash. This will allow client to submit # POST request without having to specify a root element in it. # - # By default, this functionality won't be enabled by default. You can enable + # By default this functionality won't be enabled. You can enable # it globally by setting +ActionController::Base.wrap_parameters+: # # ActionController::Base.wrap_parameters = [:json] @@ -78,7 +78,7 @@ module ActionController # # ==== Examples # wrap_parameters :format => :xml - # # enables the parmeter wrappes for XML format + # # enables the parmeter wrapper for XML format # # wrap_parameters :person # # wraps parameters into +params[:person]+ hash -- cgit v1.2.3 From b5824a4d04893077efe0dfe5ce61b3d666d64e99 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 3 May 2011 00:33:39 +0200 Subject: favor collect over each in mysql* adapters --- .../lib/active_record/connection_adapters/mysql2_adapter.rb | 6 ++---- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index b6f838e49c..98a8dd6453 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -411,11 +411,9 @@ module ActiveRecord end def tables(name = nil) - tables = [] - execute("SHOW TABLES", 'SCHEMA').each do |field| - tables << field.first + execute("SHOW TABLES", 'SCHEMA').collect do |field| + field.first end - tables end def drop_table(table_name, options = {}) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 862ce852e6..052ccde3d1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -560,9 +560,8 @@ module ActiveRecord end def tables(name = nil, database = nil) #:nodoc: - tables = [] result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA') - result.each { |field| tables << field[0] } + tables = result.collect { |field| field[0] } result.free tables end @@ -607,9 +606,8 @@ module ActiveRecord # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+. def columns(table_name, name = nil)#:nodoc: sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" - columns = [] result = execute(sql, 'SCHEMA') - result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } + columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } result.free columns end -- cgit v1.2.3 From 81cfbf4146d3c5a58054b64112b8ce196f2fc061 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 2 May 2011 15:42:38 -0700 Subject: removing auto_link and moving it to the rails_autolink gem. :bomb: --- actionpack/CHANGELOG | 6 + actionpack/lib/action_view/helpers/text_helper.rb | 125 ----------- actionpack/test/template/text_helper_test.rb | 254 ---------------------- 3 files changed, 6 insertions(+), 379 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 184b2af018..448ee878b1 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,11 @@ *Rails 3.1.0 (unreleased)* +* auto_link has been removed with no replacement. If you still use auto_link + please install the rails_autolink gem: + http://github.com/tenderlove/rails_autolink + + [tenderlove] + * Add `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist] This can be customizabled by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb` diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 06e2b027da..ca09c77b5c 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -265,60 +265,6 @@ module ActionView text.html_safe.safe_concat("

    ") end - # Turns all URLs and e-mail addresses into clickable links. The :link option - # will limit what should be linked. You can add HTML attributes to the links using - # :html. Possible values for :link are :all (default), - # :email_addresses, and :urls. If a block is given, each URL and - # e-mail address is yielded and the result is used as the link text. - # - # ==== Examples - # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") - # # => "Go to
    http://www.rubyonrails.org and - # # say hello to david@loudthinking.com" - # - # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :urls) - # # => "Visit http://www.loudthinking.com/ - # # or e-mail david@loudthinking.com" - # - # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :link => :email_addresses) - # # => "Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com" - # - # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :html => { :target => '_blank' }) do |text| - # truncate(text, :length => 15) - # end - # # => "Welcome to my new blog at http://www.m.... - # Please e-mail me at me@email.com." - # - # - # You can still use auto_link with the old API that accepts the - # +link+ as its optional second parameter and the +html_options+ hash - # as its optional third parameter: - # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :urls) - # # => "Welcome to my new blog at http://www.myblog.com. - # Please e-mail me at me@email.com." - # - # auto_link(post_body, :all, :target => "_blank") - # # => "Welcome to my new blog at http://www.myblog.com. - # Please e-mail me at me@email.com." - def auto_link(text, *args, &block)#link = :all, html = {}, &block) - return '' if text.blank? - - options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter - unless args.empty? - options[:link] = args[0] || :all - options[:html] = args[1] || {} - end - options.reverse_merge!(:link => :all, :html => {}) - - case options[:link].to_sym - when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], options, &block), options[:html], &block) - when :email_addresses then auto_link_email_addresses(text, options[:html], &block) - when :urls then auto_link_urls(text, options[:html], options, &block) - end - end - # Creates a Cycle object whose _to_s_ method cycles through elements of an # array every time it is called. This can be used for example, to alternate # classes for table rows. You can use named cycles to allow nesting in loops. @@ -464,77 +410,6 @@ module ActionView @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object end - - AUTO_LINK_RE = %r{ - (?: ([0-9A-Za-z+.:-]+:)// | www\. ) - [^\s<]+ - }x - - # regexps for determining context, used high-volume - AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, //i, /<\/a>/i] - - AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/ - - BRACKETS = { ']' => '[', ')' => '(', '}' => '{' } - - # Turns all urls into clickable links. If a block is given, each url - # is yielded and the result is used as the link text. - def auto_link_urls(text, html_options = {}, options = {}) - link_attributes = html_options.stringify_keys - text.gsub(AUTO_LINK_RE) do - scheme, href = $1, $& - punctuation = [] - - if auto_linked?($`, $') - # do not change string; URL is already linked - href - else - # don't include trailing punctuation character as part of the URL - while href.sub!(/[^\w\/-]$/, '') - punctuation.push $& - if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size - href << punctuation.pop - break - end - end - - link_text = block_given?? yield(href) : href - href = 'http://' + href unless scheme - - unless options[:sanitize] == false - link_text = sanitize(link_text) - href = sanitize(href) - end - content_tag(:a, link_text, link_attributes.merge('href' => href), !!options[:sanitize]) + punctuation.reverse.join('') - end - end - end - - # Turns all email addresses into clickable links. If a block is given, - # each email is yielded and the result is used as the link text. - def auto_link_email_addresses(text, html_options = {}, options = {}) - text.gsub(AUTO_EMAIL_RE) do - text = $& - - if auto_linked?($`, $') - text.html_safe - else - display_text = (block_given?) ? yield(text) : text - - unless options[:sanitize] == false - text = sanitize(text) - display_text = sanitize(display_text) unless text == display_text - end - mail_to text, display_text, html_options - end - end - end - - # Detects already linked context or position in the middle of a tag - def auto_linked?(left, right) - (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or - (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3]) - end end end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index a4fcff5167..740f577a6e 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -278,260 +278,6 @@ class TextHelperTest < ActionView::TestCase assert_equal("12 berries", pluralize(12, "berry")) end - def test_auto_link_parsing - urls = %w( - http://www.rubyonrails.com - http://www.rubyonrails.com:80 - http://www.rubyonrails.com/~minam - https://www.rubyonrails.com/~minam - http://www.rubyonrails.com/~minam/url%20with%20spaces - http://www.rubyonrails.com/foo.cgi?something=here - http://www.rubyonrails.com/foo.cgi?something=here&and=here - http://www.rubyonrails.com/contact;new - http://www.rubyonrails.com/contact;new%20with%20spaces - http://www.rubyonrails.com/contact;new?with=query&string=params - http://www.rubyonrails.com/~minam/contact;new?with=query&string=params - http://en.wikipedia.org/wiki/Wikipedia:Today%27s_featured_picture_%28animation%29/January_20%2C_2007 - 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/Texas_hold'em - https://www.google.com/doku.php?id=gps:resource:scs:start - http://connect.oraclecorp.com/search?search[q]=green+france&search[type]=Group - http://of.openfoundry.org/projects/492/download#4th.Release.3 - http://maps.google.co.uk/maps?f=q&q=the+london+eye&ie=UTF8&ll=51.503373,-0.11939&spn=0.007052,0.012767&z=16&iwloc=A - ) - - urls.each do |url| - assert_equal generate_result(url), auto_link(url) - end - end - - def generate_result(link_text, href = nil, escape = false) - href ||= link_text - if escape - %{#{CGI::escapeHTML link_text}} - else - %{#{link_text}} - end - end - - def test_auto_link_should_not_be_html_safe - email_raw = 'santiago@wyeworks.com' - link_raw = 'http://www.rubyonrails.org' - - assert !auto_link(nil).html_safe?, 'should not be html safe' - assert !auto_link('').html_safe?, 'should not be html safe' - assert !auto_link("#{link_raw} #{link_raw} #{link_raw}").html_safe?, 'should not be html safe' - assert !auto_link("hello #{email_raw}").html_safe?, 'should not be html safe' - end - - def test_auto_link_email_address - email_raw = 'aaron@tenderlovemaking.com' - email_result = %{#{email_raw}} - assert !auto_link_email_addresses(email_result).html_safe?, 'should not be html safe' - end - - def test_auto_link - email_raw = 'david@loudthinking.com' - email_result = %{#{email_raw}} - link_raw = 'http://www.rubyonrails.com' - link_result = generate_result(link_raw) - link_result_with_options = %{#{link_raw}} - - assert_equal '', auto_link(nil) - assert_equal '', auto_link('') - assert_equal "#{link_result} #{link_result} #{link_result}", auto_link("#{link_raw} #{link_raw} #{link_raw}") - - assert_equal %(hello #{email_result}), auto_link("hello #{email_raw}", :email_addresses) - assert_equal %(Go to #{link_result}), auto_link("Go to #{link_raw}", :urls) - assert_equal %(Go to #{link_raw}), auto_link("Go to #{link_raw}", :email_addresses) - assert_equal %(Go to #{link_result} and say hello to #{email_result}), auto_link("Go to #{link_raw} and say hello to #{email_raw}") - assert_equal %(

    Link #{link_result}

    ), auto_link("

    Link #{link_raw}

    ") - assert_equal %(

    #{link_result} Link

    ), auto_link("

    #{link_raw} Link

    ") - assert_equal %(

    Link #{link_result_with_options}

    ), auto_link("

    Link #{link_raw}

    ", :all, {:target => "_blank"}) - assert_equal %(Go to #{link_result}.), auto_link(%(Go to #{link_raw}.)) - assert_equal %(

    Go to #{link_result}, then say hello to #{email_result}.

    ), auto_link(%(

    Go to #{link_raw}, then say hello to #{email_raw}.

    )) - assert_equal %(#{link_result} #{link_result}), auto_link(%(#{link_result} #{link_raw})) - - email2_raw = '+david@loudthinking.com' - email2_result = %{#{email2_raw}} - assert_equal email2_result, auto_link(email2_raw) - - email3_raw = '+david@loudthinking.com' - email3_result = %{#{email3_raw}} - assert_equal email3_result, auto_link(email3_raw, :all, :encode => :hex) - assert_equal email3_result, auto_link(email3_raw, :email_addresses, :encode => :hex) - - link2_raw = 'www.rubyonrails.com' - link2_result = generate_result(link2_raw, "http://#{link2_raw}") - assert_equal %(Go to #{link2_result}), auto_link("Go to #{link2_raw}", :urls) - assert_equal %(Go to #{link2_raw}), auto_link("Go to #{link2_raw}", :email_addresses) - assert_equal %(

    Link #{link2_result}

    ), auto_link("

    Link #{link2_raw}

    ") - assert_equal %(

    #{link2_result} Link

    ), auto_link("

    #{link2_raw} Link

    ") - assert_equal %(Go to #{link2_result}.), auto_link(%(Go to #{link2_raw}.)) - assert_equal %(

    Say hello to #{email_result}, then go to #{link2_result}.

    ), auto_link(%(

    Say hello to #{email_raw}, then go to #{link2_raw}.

    )) - - link3_raw = 'http://manuals.ruby-on-rails.com/read/chapter.need_a-period/103#page281' - link3_result = generate_result(link3_raw) - assert_equal %(Go to #{link3_result}), auto_link("Go to #{link3_raw}", :urls) - assert_equal %(Go to #{link3_raw}), auto_link("Go to #{link3_raw}", :email_addresses) - assert_equal %(

    Link #{link3_result}

    ), auto_link("

    Link #{link3_raw}

    ") - assert_equal %(

    #{link3_result} Link

    ), auto_link("

    #{link3_raw} Link

    ") - assert_equal %(Go to #{link3_result}.), auto_link(%(Go to #{link3_raw}.)) - assert_equal %(

    Go to #{link3_result}. Seriously, #{link3_result}? I think I'll say hello to #{email_result}. Instead.

    ), - auto_link(%(

    Go to #{link3_raw}. Seriously, #{link3_raw}? I think I'll say hello to #{email_raw}. Instead.

    )) - - link4_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor123' - link4_result = generate_result(link4_raw) - assert_equal %(

    Link #{link4_result}

    ), auto_link("

    Link #{link4_raw}

    ") - assert_equal %(

    #{link4_result} Link

    ), auto_link("

    #{link4_raw} Link

    ") - - link5_raw = 'http://foo.example.com:3000/controller/action' - link5_result = generate_result(link5_raw) - assert_equal %(

    #{link5_result} Link

    ), auto_link("

    #{link5_raw} Link

    ") - - link6_raw = 'http://foo.example.com:3000/controller/action+pack' - link6_result = generate_result(link6_raw) - assert_equal %(

    #{link6_result} Link

    ), auto_link("

    #{link6_raw} Link

    ") - - link7_raw = 'http://foo.example.com/controller/action?parm=value&p2=v2#anchor-123' - link7_result = generate_result(link7_raw) - assert_equal %(

    #{link7_result} Link

    ), auto_link("

    #{link7_raw} Link

    ") - - link8_raw = 'http://foo.example.com:3000/controller/action.html' - link8_result = generate_result(link8_raw) - assert_equal %(Go to #{link8_result}), auto_link("Go to #{link8_raw}", :urls) - assert_equal %(Go to #{link8_raw}), auto_link("Go to #{link8_raw}", :email_addresses) - assert_equal %(

    Link #{link8_result}

    ), auto_link("

    Link #{link8_raw}

    ") - assert_equal %(

    #{link8_result} Link

    ), auto_link("

    #{link8_raw} Link

    ") - assert_equal %(Go to #{link8_result}.), auto_link(%(Go to #{link8_raw}.)) - assert_equal %(

    Go to #{link8_result}. Seriously, #{link8_result}? I think I'll say hello to #{email_result}. Instead.

    ), - auto_link(%(

    Go to #{link8_raw}. Seriously, #{link8_raw}? I think I'll say hello to #{email_raw}. Instead.

    )) - - link9_raw = 'http://business.timesonline.co.uk/article/0,,9065-2473189,00.html' - link9_result = generate_result(link9_raw) - assert_equal %(Go to #{link9_result}), auto_link("Go to #{link9_raw}", :urls) - assert_equal %(Go to #{link9_raw}), auto_link("Go to #{link9_raw}", :email_addresses) - assert_equal %(

    Link #{link9_result}

    ), auto_link("

    Link #{link9_raw}

    ") - assert_equal %(

    #{link9_result} Link

    ), auto_link("

    #{link9_raw} Link

    ") - assert_equal %(Go to #{link9_result}.), auto_link(%(Go to #{link9_raw}.)) - assert_equal %(

    Go to #{link9_result}. Seriously, #{link9_result}? I think I'll say hello to #{email_result}. Instead.

    ), - auto_link(%(

    Go to #{link9_raw}. Seriously, #{link9_raw}? I think I'll say hello to #{email_raw}. Instead.

    )) - - link10_raw = 'http://www.mail-archive.com/ruby-talk@ruby-lang.org/' - link10_result = generate_result(link10_raw) - assert_equal %(

    #{link10_result} Link

    ), auto_link("

    #{link10_raw} Link

    ") - - link11_raw = 'http://asakusa.rubyist.net/' - link11_result = generate_result(link11_raw) - with_kcode 'u' do - assert_equal %(浅草.rbの公式サイトはこちら#{link11_result}), auto_link("浅草.rbの公式サイトはこちら#{link11_raw}") - end - end - - def test_auto_link_should_sanitize_input_when_sanitize_option_is_not_false - link_raw = %{http://www.rubyonrails.com?id=1&num=2} - assert_equal %{http://www.rubyonrails.com?id=1&num=2}, auto_link(link_raw) - end - - def test_auto_link_should_not_sanitize_input_when_sanitize_option_is_false - link_raw = %{http://www.rubyonrails.com?id=1&num=2} - assert_equal %{http://www.rubyonrails.com?id=1&num=2}, auto_link(link_raw, :sanitize => false) - end - - def test_auto_link_other_protocols - ftp_raw = 'ftp://example.com/file.txt' - assert_equal %(Download #{generate_result(ftp_raw)}), auto_link("Download #{ftp_raw}") - - file_scheme = 'file:///home/username/RomeoAndJuliet.pdf' - z39_scheme = 'z39.50r://host:696/db' - chrome_scheme = 'chrome://package/section/path' - view_source = 'view-source:http://en.wikipedia.org/wiki/URI_scheme' - assert_equal generate_result(file_scheme), auto_link(file_scheme) - assert_equal generate_result(z39_scheme), auto_link(z39_scheme) - assert_equal generate_result(chrome_scheme), auto_link(chrome_scheme) - assert_equal generate_result(view_source), auto_link(view_source) - end - - def test_auto_link_already_linked - linked1 = generate_result('Ruby On Rails', 'http://www.rubyonrails.com') - linked2 = %('www.example.com') - linked3 = %('www.example.com') - linked4 = %('www.example.com') - linked5 = %('close www.example.com') - assert_equal linked1, auto_link(linked1) - assert_equal linked2, auto_link(linked2) - assert_equal linked3, auto_link(linked3) - assert_equal linked4, auto_link(linked4) - assert_equal linked5, auto_link(linked5) - - linked_email = %Q(Mail me) - assert_equal linked_email, auto_link(linked_email) - end - - def test_auto_link_within_tags - link_raw = 'http://www.rubyonrails.org/images/rails.png' - link_result = %Q() - assert_equal link_result, auto_link(link_result) - end - - def test_auto_link_with_brackets - link1_raw = 'http://en.wikipedia.org/wiki/Sprite_(computer_graphics)' - link1_result = generate_result(link1_raw) - assert_equal link1_result, auto_link(link1_raw) - assert_equal "(link: #{link1_result})", auto_link("(link: #{link1_raw})") - - link2_raw = 'http://en.wikipedia.org/wiki/Sprite_[computer_graphics]' - link2_result = generate_result(link2_raw) - assert_equal link2_result, auto_link(link2_raw) - assert_equal "[link: #{link2_result}]", auto_link("[link: #{link2_raw}]") - - link3_raw = 'http://en.wikipedia.org/wiki/Sprite_{computer_graphics}' - link3_result = generate_result(link3_raw) - assert_equal link3_result, auto_link(link3_raw) - assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") - end - - def test_auto_link_at_eol - url1 = "http://api.rubyonrails.com/Foo.html" - url2 = "http://www.ruby-doc.org/core/Bar.html" - - assert_equal %(

    #{url1}
    #{url2}

    ), auto_link("

    #{url1}
    #{url2}

    ") - end - - def test_auto_link_with_block - url = "http://api.rubyonrails.com/Foo.html" - email = "fantabulous@shiznadel.ic" - - assert_equal %(

    #{url[0...7]}...
    #{email[0...7]}...

    ), auto_link("

    #{url}
    #{email}

    ") { |_url| truncate(_url, :length => 10) } - end - - def test_auto_link_with_block_with_html - pic = "http://example.com/pic.png" - url = "http://example.com/album?a&b=c" - - assert_equal %(My pic: -- full album here #{generate_result(url)}), auto_link("My pic: #{pic} -- full album here #{url}") { |link| - if link =~ /\.(jpg|gif|png|bmp|tif)$/i - raw %() - else - link - end - } - end - - def test_auto_link_with_options_hash - assert_dom_equal 'Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.', - auto_link("Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com.", - :link => :all, :html => { :class => "menu", :target => "_blank" }) - end - - def test_auto_link_with_multiple_trailing_punctuations - url = "http://youtube.com" - url_result = generate_result(url) - assert_equal url_result, auto_link(url) - assert_equal "(link: #{url_result}).", auto_link("(link: #{url}).") - end - def test_cycle_class value = Cycle.new("one", 2, "3") assert_equal("one", value.to_s) -- cgit v1.2.3 From 3cca86641e91400e3317ce2d03b483edf1db3ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 2 May 2011 23:53:53 +0200 Subject: Update CHANGELOG. --- actionpack/CHANGELOG | 20 ++++++++++++++++---- actionpack/lib/action_controller/metal/streaming.rb | 2 +- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 448ee878b1..ba01c4749f 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -6,15 +6,27 @@ [tenderlove] -* Add `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist] +* Added streaming support, you can enable it with: [José Valim] - This can be customizabled by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb` + class PostsController < ActionController::Base + stream :only => :index + end + + Please read the docs at `ActionController::Streaming` for more information. + +* Added `ActionDispatch::Request.ignore_accept_header` to ignore accept headers and only consider the format given as parameter [José Valim] + +* Created `ActionView::Renderer` and specified an API for `ActionView::Context`, check those objects for more information [José Valim] + +* Added `ActionController::ParamsWrapper` to wrap parameters into a nested hash, and will be turned on for JSON request in new applications by default [Prem Sichanugrist] + + This can be customized by setting `ActionController::Base.wrap_parameters` in `config/initializer/wrap_parameters.rb` * RJS has been extracted out to a gem. [fxn] -* Implicit actions named not_implemented can be rendered [Santiago Pastorino] +* Implicit actions named not_implemented can be rendered. [Santiago Pastorino] -* Wildcard route will always matching the optional format segment by default. [Prem Sichanugrist] +* Wildcard route will always match the optional format segment by default. [Prem Sichanugrist] For example if you have this route: diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 1d27c3aa51..3892a12407 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -160,7 +160,7 @@ module ActionController #:nodoc: # needs to inject contents in the HTML body. # # Also +Rack::Cache+ won't work with streaming as it does not support - # streaming bodies yet. So, whenever streaming, Cache-Control is automatically + # streaming bodies yet. Whenever streaming Cache-Control is automatically # set to "no-cache". # # == Errors -- cgit v1.2.3 From 4bddc06e83acecce662b4282159c5eb0096c4783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 00:37:40 +0200 Subject: Move most processing to load time for performance and improve test suite. --- .../lib/action_controller/metal/params_wrapper.rb | 74 ++++++++++++++-------- actionpack/test/controller/params_wrapper_test.rb | 39 ++++++++---- 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 7827ed598e..3262e24f67 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,4 +1,6 @@ require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/array/wrap' require 'action_dispatch/http/mime_types' module ActionController @@ -96,23 +98,25 @@ module ActionController # ==== Options # * :format - The list of formats in which the parameters wrapper # will be enabled. - # * :only - The list of attribute names which parmeters wrapper + # * :only - The list of attribute names which parameters wrapper # will wrap into a nested hash. - # * :only - The list of attribute names which parmeters wrapper + # * :except - The list of attribute names which parameters wrapper # will exclude from a nested hash. def wrap_parameters(name_or_model_or_options, options = {}) - if !name_or_model_or_options.is_a? Hash - if name_or_model_or_options != false - options = options.merge(:name_or_model => name_or_model_or_options) - else - options = opions.merge(:format => []) - end - else + model = nil + + case name_or_model_or_options + when Hash options = name_or_model_or_options + when false + options = options.merge(:format => []) + when Symbol, String + options = options.merge(:name => name_or_model_or_options) + else + model = name_or_model_or_options end - options[:name_or_model] ||= _default_wrap_model - self._wrapper_options = self._wrapper_options.merge(options) + _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model) end # Sets the default wrapper key or model which will be used to determine @@ -120,11 +124,13 @@ module ActionController # module is inherited. def inherited(klass) if klass._wrapper_options[:format].present? - klass._wrapper_options = klass._wrapper_options.merge(:name_or_model => klass._default_wrap_model) + klass._set_wrapper_defaults(klass._wrapper_options) end super end + protected + # Determine the wrapper model from the controller's name. By convention, # this could be done by trying to find the defined model that has the # same singularize name as the controller. For example, +UsersController+ @@ -142,6 +148,29 @@ module ActionController model_klass end + + def _set_wrapper_defaults(options, model=nil) + options = options.dup + + unless options[:only] || options[:except] + model ||= _default_wrap_model + if model.respond_to?(:column_names) + options[:only] = model.column_names + end + end + + unless options[:name] + model ||= _default_wrap_model + options[:name] = model ? model.to_s.demodulize.underscore : + controller_name.singularize + end + + options[:only] = Array.wrap(options[:only]).collect(&:to_s) if options[:only] + options[:except] = Array.wrap(options[:except]).collect(&:to_s) if options[:except] + options[:format] = Array.wrap(options[:format]) + + self._wrapper_options = options + end end # Performs parameters wrapping upon the request. Will be called automatically @@ -164,21 +193,15 @@ module ActionController private # Returns the wrapper key which will use to stored wrapped parameters. def _wrapper_key - @_wrapper_key ||= if _wrapper_options[:name_or_model] - _wrapper_options[:name_or_model].to_s.demodulize.underscore - else - self.class.controller_name.singularize - end + _wrapper_options[:name] end # Returns the list of parameters which will be selected for wrapped. def _wrapped_keys - @_wrapped_keys ||= if _wrapper_options[:only] - Array(_wrapper_options[:only]).collect(&:to_s) - elsif _wrapper_options[:except] - request.request_parameters.keys - Array(_wrapper_options[:except]).collect(&:to_s) - EXCLUDE_PARAMETERS - elsif _wrapper_options[:name_or_model].respond_to?(:column_names) - _wrapper_options[:name_or_model].column_names + @_wrapped_keys ||= if only = _wrapper_options[:only] + only + elsif except = _wrapper_options[:except] + request.request_parameters.keys - except - EXCLUDE_PARAMETERS else request.request_parameters.keys - EXCLUDE_PARAMETERS end @@ -186,12 +209,13 @@ module ActionController # Returns the list of enabled formats. def _wrapper_formats - Array(_wrapper_options[:format]) + _wrapper_options[:format] end # Checks if we should perform parameters wrapping. def _wrapper_enabled? - _wrapper_formats.any?{ |format| format == request.content_mime_type.try(:ref) } && request.request_parameters[_wrapper_key].nil? + ref = request.content_mime_type.try(:ref) + _wrapper_formats.any? { |format| format == ref } && !request.request_parameters[_wrapper_key] end end end diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 2e5d096fcd..314b27cf47 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -80,6 +80,15 @@ class ParamsWrapperTest < ActionController::TestCase end end + def test_wrap_parameters_false + with_default_wrapper_options do + UsersController.wrap_parameters false + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer"}', @response.body + end + end + def test_specify_format with_default_wrapper_options do UsersController.wrap_parameters :format => :xml @@ -115,10 +124,10 @@ class ParamsWrapperTest < ActionController::TestCase end def test_derived_wrapped_keys_from_matching_model - with_default_wrapper_options do - User.expects(:respond_to?).with(:column_names).returns(true) - User.expects(:column_names).returns(["username"]) + User.expects(:respond_to?).with(:column_names).returns(true) + User.expects(:column_names).returns(["username"]) + with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' post :test, { 'username' => 'sikachu', 'title' => 'Developer' } assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body @@ -153,11 +162,13 @@ class NamespacedParamsWrapperTest < ActionController::TestCase render :json => params.except(:controller, :action) end end + end - class User; end + class Sample + def self.column_names + ["username"] + end end - class User; end - class Person; end tests Admin::UsersController @@ -169,12 +180,16 @@ class NamespacedParamsWrapperTest < ActionController::TestCase end end - def test_namespace_lookup_when_namespaced_model_available - with_default_wrapper_options do - Admin::User.expects(:respond_to?).with(:column_names).returns(false) - - @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu' } + def test_namespace_lookup_from_model + Admin.const_set(:User, Class.new(Sample)) + begin + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + end + ensure + Admin.send :remove_const, :User end end -- cgit v1.2.3 From 1afb56f4818381098c6ed0babc4a5899e324e2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 00:47:11 +0200 Subject: Instrumentation should have callbacks. --- actionpack/lib/action_controller/base.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 373df7fb55..ccf1632a14 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -207,14 +207,14 @@ module ActionController HttpAuthentication::Digest::ControllerMethods, HttpAuthentication::Token::ControllerMethods, - # Add instrumentations hooks at the bottom, to ensure they instrument - # all the methods properly. - Instrumentation, - # Before callbacks should also be executed the earliest as possible, so # also include them at the bottom. AbstractController::Callbacks, + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. + Instrumentation, + # The same with rescue, append it at the end to wrap as much as possible. Rescue ] -- cgit v1.2.3 From a55f2de0c5baae589b1730df1e4068f0cd1474ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 01:03:21 +0200 Subject: Improve performance for filtered parameters and add tests. --- actionpack/lib/action_controller/base.rb | 5 ++++- actionpack/lib/action_controller/metal/instrumentation.rb | 2 +- actionpack/lib/action_controller/metal/params_wrapper.rb | 5 ++--- actionpack/lib/action_dispatch/http/filter_parameters.rb | 5 +++++ actionpack/test/controller/log_subscriber_test.rb | 11 +++++++++++ 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index ccf1632a14..c03c77cb4a 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -194,7 +194,6 @@ module ActionController Caching, MimeResponds, ImplicitRender, - ParamsWrapper, Cookies, Flash, @@ -215,6 +214,10 @@ module ActionController # all the methods properly. Instrumentation, + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper, + # The same with rescue, append it at the end to wrap as much as possible. Rescue ] diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index dc3ea939e6..4e54c2ad88 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -14,7 +14,7 @@ module ActionController attr_internal :view_runtime - def process_action(action, *args) + def process_action(*args) raw_payload = { :controller => self.class.name, :action => self.action_name, diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 3262e24f67..d128f6d03c 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -178,14 +178,13 @@ module ActionController def process_action(*args) if _wrapper_enabled? wrapped_hash = { _wrapper_key => request.request_parameters.slice(*_wrapped_keys) } - wrapped_filtered_hash = { _wrapper_key => request.filtered_parameters.slice(*_wrapped_keys) } # This will make the wrapped hash accessible from controller and view request.parameters.merge! wrapped_hash request.request_parameters.merge! wrapped_hash # This will make the wrapped hash displayed in the log file - request.filtered_parameters.merge! wrapped_filtered_hash + request.clear_filtered_parameters end super end @@ -215,7 +214,7 @@ module ActionController # Checks if we should perform parameters wrapping. def _wrapper_enabled? ref = request.content_mime_type.try(:ref) - _wrapper_formats.any? { |format| format == ref } && !request.request_parameters[_wrapper_key] + _wrapper_formats.include?(ref) && !request.request_parameters[_wrapper_key] end end end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 8dd1af7f3d..9c5b6a6b88 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -33,6 +33,11 @@ module ActionDispatch @filtered_parameters ||= parameter_filter.filter(parameters) end + # Clear any filtered parameters forcing them to be filtered again. + def clear_filtered_parameters + @filtered_parameters = nil + end + # Return a hash of request.env with all sensitive data replaced. def filtered_env @filtered_env ||= env_filter.filter(@env) diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index ddfa3df552..5d7a51e902 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -4,6 +4,8 @@ require "action_controller/log_subscriber" module Another class LogSubscribersController < ActionController::Base + wrap_parameters :person, :only => :name, :format => :json + def show render :nothing => true end @@ -95,6 +97,15 @@ class ACLogSubscriberTest < ActionController::TestCase assert_equal 'Parameters: {"id"=>"10"}', logs[1] end + def test_process_action_with_wrapped_parameters + @request.env['CONTENT_TYPE'] = 'application/json' + post :show, :id => '10', :name => 'jose' + wait + + assert_equal 3, logs.size + assert_match '"person"=>{"name"=>"jose"}', logs[1] + end + def test_process_action_with_view_runtime get :show wait -- cgit v1.2.3 From d53c2e4952f9dc938648b791f1f3c697f34ff922 Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Mon, 2 May 2011 20:30:31 -0300 Subject: fixed-fonts on SchemaDefinitions --- .../connection_adapters/abstract/schema_definitions.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7ac48c6646..2f747e77a1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -204,7 +204,7 @@ module ActiveRecord # end # # There's a short-hand method for each of the type values declared at the top. And then there's - # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes. + # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes. # # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type # column if the :polymorphic option is supplied. If :polymorphic is a hash of @@ -351,7 +351,7 @@ module ActiveRecord @base.index_exists?(@table_name, column_name, options) end - # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # ===== Example # t.timestamps def timestamps @@ -398,7 +398,7 @@ module ActiveRecord @base.remove_index(@table_name, options) end - # Removes the timestamp columns (created_at and updated_at) from the table. + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. # ===== Example # t.remove_timestamps def remove_timestamps -- cgit v1.2.3 From 35d0d82ae3edf8fe959624999c858a63b2b4ed52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 01:36:58 +0200 Subject: More performance optimizations. --- .../lib/action_controller/metal/params_wrapper.rb | 30 ++++++++++++---------- .../lib/action_dispatch/http/filter_parameters.rb | 5 ---- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index d128f6d03c..21bbe17dc3 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/hash/except' require 'active_support/core_ext/array/wrap' require 'action_dispatch/http/mime_types' @@ -177,40 +178,43 @@ module ActionController # by the metal call stack. def process_action(*args) if _wrapper_enabled? - wrapped_hash = { _wrapper_key => request.request_parameters.slice(*_wrapped_keys) } + wrapped_hash = _wrap_parameters request.request_parameters + wrapped_filtered_hash = _wrap_parameters request.filtered_parameters # This will make the wrapped hash accessible from controller and view request.parameters.merge! wrapped_hash request.request_parameters.merge! wrapped_hash # This will make the wrapped hash displayed in the log file - request.clear_filtered_parameters + request.filtered_parameters.merge! wrapped_filtered_hash end super end private + # Returns the wrapper key which will use to stored wrapped parameters. def _wrapper_key _wrapper_options[:name] end - # Returns the list of parameters which will be selected for wrapped. - def _wrapped_keys - @_wrapped_keys ||= if only = _wrapper_options[:only] - only - elsif except = _wrapper_options[:except] - request.request_parameters.keys - except - EXCLUDE_PARAMETERS - else - request.request_parameters.keys - EXCLUDE_PARAMETERS - end - end - # Returns the list of enabled formats. def _wrapper_formats _wrapper_options[:format] end + # Returns the list of parameters which will be selected for wrapped. + def _wrap_parameters(parameters) + value = if only = _wrapper_options[:only] + parameters.slice(*only) + else + except = _wrapper_options[:except] || [] + parameters.except(*(except + EXCLUDE_PARAMETERS)) + end + + { _wrapper_key => value } + end + # Checks if we should perform parameters wrapping. def _wrapper_enabled? ref = request.content_mime_type.try(:ref) diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 9c5b6a6b88..8dd1af7f3d 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -33,11 +33,6 @@ module ActionDispatch @filtered_parameters ||= parameter_filter.filter(parameters) end - # Clear any filtered parameters forcing them to be filtered again. - def clear_filtered_parameters - @filtered_parameters = nil - end - # Return a hash of request.env with all sensitive data replaced. def filtered_env @filtered_env ||= env_filter.filter(@env) -- cgit v1.2.3 From 2fbbd08616c25dc2931eca914349dd2161766f69 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 2 May 2011 19:24:49 -0500 Subject: Use require_self from latest Sprockets --- actionpack/actionpack.gemspec | 2 +- .../rails/app/templates/app/assets/stylesheets/application.css | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index a2570587ce..4af21366e3 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.2.1') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.2') - s.add_dependency('sprockets', '~> 2.0.0.beta.1') + s.add_dependency('sprockets', '~> 2.0.0.beta.2') s.add_dependency('tzinfo', '~> 0.3.23') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index ccfff11a5d..f4b082ccc0 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -1,4 +1,5 @@ /* * FIXME: Introduce SCSS & Sprockets + *= require_self *= require_tree . */ \ No newline at end of file -- cgit v1.2.3 From 8bf5d684fd0c83513d57f8e392c696dd3d12974d Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 3 May 2011 15:40:52 +0700 Subject: Update Action Pack to depend on Rack 1.3.0 Rack 1.3.0 was released a while ago, which break the hard dependency we set in Action Pack. This dependency bump won't be the issue, as we always depends on edge Rack. Also, we was waiting for Rack 1.3.x release before we can release Rails 3.1 beta anyway. --- actionpack/actionpack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 4af21366e3..d85f077e0f 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency('rack-cache', '~> 1.0.0') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6.0beta1') - s.add_dependency('rack', '~> 1.2.1') + s.add_dependency('rack', '~> 1.3.0') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.2') s.add_dependency('sprockets', '~> 2.0.0.beta.2') -- cgit v1.2.3 From 31f412fc975033a9293888e8618fc933097d1d35 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Tue, 3 May 2011 16:51:19 +0700 Subject: Fix broken params_wrapper_test on 1.8.7 :bomb: I have to keep in mind that 1.8.7 does *not* preserve the hash order. Guys, let's move to use 1.9.2 in production! --- actionpack/test/controller/params_wrapper_test.rb | 116 +++++++++++++++------- 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 314b27cf47..d6f8f28d0d 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -4,8 +4,13 @@ module Admin; class User; end; end class ParamsWrapperTest < ActionController::TestCase class UsersController < ActionController::Base - def test - render :json => params.except(:controller, :action) + class << self + attr_accessor :last_parameters + end + + def parse + self.class.last_parameters = request.params.except(:controller, :action) + head :ok end end @@ -14,11 +19,15 @@ class ParamsWrapperTest < ActionController::TestCase tests UsersController + def teardown + UsersController.last_parameters = nil + end + def test_derivered_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu' } - assert_equal '{"username":"sikachu","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end @@ -27,8 +36,8 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :person @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu' } - assert_equal '{"username":"sikachu","person":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end @@ -37,8 +46,8 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu' } - assert_equal '{"username":"sikachu","person":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu' } + assert_parameters({ 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }}) end end @@ -47,8 +56,8 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :only => :username @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -57,8 +66,8 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :except => :title @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -67,16 +76,16 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :person, :only => :username @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","person":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end def test_not_enabled_format with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/xml' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer"}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end @@ -84,8 +93,8 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do UsersController.wrap_parameters false @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer"}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer' }) end end @@ -94,32 +103,32 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters :format => :xml @request.env['CONTENT_TYPE'] = 'application/xml' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu","title":"Developer"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu', 'title' => 'Developer' }}) end end def test_not_wrap_reserved_parameters with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } - assert_equal '{"authenticity_token":"pwned","_method":"put","utf8":"☃","username":"sikachu","user":{"username":"sikachu"}}', @response.body + post :parse, { 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu' } + assert_parameters({ 'authenticity_token' => 'pwned', '_method' => 'put', 'utf8' => '☃', 'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end def test_no_double_wrap_if_key_exists with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'user' => { 'username' => 'sikachu' }} - assert_equal '{"user":{"username":"sikachu"}}', @response.body + post :parse, { 'user' => { 'username' => 'sikachu' }} + assert_parameters({ 'user' => { 'username' => 'sikachu' }}) end end def test_nested_params with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'person' => { 'username' => 'sikachu' }} - assert_equal '{"person":{"username":"sikachu"},"user":{"person":{"username":"sikachu"}}}', @response.body + post :parse, { 'person' => { 'username' => 'sikachu' }} + assert_parameters({ 'person' => { 'username' => 'sikachu' }, 'user' => {'person' => { 'username' => 'sikachu' }}}) end end @@ -129,8 +138,8 @@ class ParamsWrapperTest < ActionController::TestCase with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end end @@ -142,8 +151,8 @@ class ParamsWrapperTest < ActionController::TestCase UsersController.wrap_parameters Person @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","person":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }}) end end @@ -153,13 +162,22 @@ class ParamsWrapperTest < ActionController::TestCase @controller.class.inherited(@controller.class) yield end + + def assert_parameters(expected) + assert_equal expected, UsersController.last_parameters + end end class NamespacedParamsWrapperTest < ActionController::TestCase module Admin class UsersController < ActionController::Base - def test - render :json => params.except(:controller, :action) + class << self + attr_accessor :last_parameters + end + + def parse + self.class.last_parameters = request.params.except(:controller, :action) + head :ok end end end @@ -172,11 +190,15 @@ class NamespacedParamsWrapperTest < ActionController::TestCase tests Admin::UsersController + def teardown + Admin::UsersController.last_parameters = nil + end + def test_derivered_name_from_controller with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu' } - assert_equal '{"username":"sikachu","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu' } + assert_parameters({'username' => 'sikachu', 'user' => { 'username' => 'sikachu' }}) end end @@ -185,18 +207,40 @@ class NamespacedParamsWrapperTest < ActionController::TestCase begin with_default_wrapper_options do @request.env['CONTENT_TYPE'] = 'application/json' - post :test, { 'username' => 'sikachu', 'title' => 'Developer' } - assert_equal '{"username":"sikachu","title":"Developer","user":{"username":"sikachu"}}', @response.body + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) end ensure Admin.send :remove_const, :User end end + def test_heirarchy_namespace_lookup_from_model + # Make sure that we cleanup ::Admin::User + admin_user_constant = ::Admin::User + ::Admin.send :remove_const, :User + + Object.const_set(:User, Class.new(Sample)) + begin + with_default_wrapper_options do + @request.env['CONTENT_TYPE'] = 'application/json' + post :parse, { 'username' => 'sikachu', 'title' => 'Developer' } + assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }}) + end + ensure + Object.send :remove_const, :User + ::Admin.const_set(:User, admin_user_constant) + end + end + private def with_default_wrapper_options(&block) @controller.class._wrapper_options = {:format => [:json]} @controller.class.inherited(@controller.class) yield end + + def assert_parameters(expected) + assert_equal expected, Admin::UsersController.last_parameters + end end -- cgit v1.2.3 From ed3e667415e191d1677a3b7e55b077f55504214c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 12:10:11 +0200 Subject: Multipart is now fixed in Rack. --- .../request/multipart_params_parsing_test.rb | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 3ff558ec5a..560ea00923 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -82,21 +82,15 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest assert_equal 19512, file.size end - # Pending fix in Rack 1.2.2 - # http://rack.lighthouseapp.com/projects/22435-rack/tickets/79-multipart-handling-incorrectly-assuming-file-upload test "parses mixed files" do - if Rack.release <= '1.2.1' - $stderr.puts 'multipart/mixed parsing pending fix in Rack 1.2.2' - else - params = parse_multipart('mixed_files') - assert_equal %w(files foo), params.keys.sort - assert_equal 'bar', params['foo'] - - # Rack doesn't handle multipart/mixed for us. - files = params['files'] - files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) - assert_equal 19756, files.size - end + params = parse_multipart('mixed_files') + assert_equal %w(files foo), params.keys.sort + assert_equal 'bar', params['foo'] + + # Rack doesn't handle multipart/mixed for us. + files = params['files'] + files.force_encoding('ASCII-8BIT') if files.respond_to?(:force_encoding) + assert_equal 19756, files.size end test "does not create tempfile if no file has been selected" do -- cgit v1.2.3 From e1c16850168fbadc5ae8a0688e23170021a84955 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 12:32:14 +0200 Subject: Static middleware accepts cache control. --- actionpack/lib/action_dispatch/middleware/static.rb | 8 ++++---- actionpack/test/dispatch/static_test.rb | 8 +++++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 348f7b86b8..360c1209bb 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,10 +2,10 @@ require 'rack/utils' module ActionDispatch class FileHandler - def initialize(root) + def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ - @file_server = ::Rack::File.new(@root) + @file_server = ::Rack::File.new(@root, cache_control) end def match?(path) @@ -37,9 +37,9 @@ module ActionDispatch class Static FILE_METHODS = %w(GET HEAD).freeze - def initialize(app, path) + def initialize(app, path, cache_control=nil) @app = app - @file_handler = FileHandler.new(path) + @file_handler = FileHandler.new(path, cache_control) end def call(env) diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 2ebbed4414..9f3cbd19ef 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -5,6 +5,12 @@ module StaticTests assert_equal "Hello, World!", get("/nofile").body end + def test_sets_cache_control + response = get("/index.html") + assert_html "/index.html", response + assert_equal "public, max-age=60", response.headers["Cache-Control"] + end + def test_serves_static_index_at_root assert_html "/index.html", get("/index.html") assert_html "/index.html", get("/index") @@ -40,7 +46,7 @@ class StaticTest < ActiveSupport::TestCase DummyApp = lambda { |env| [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] } - App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public") + App = ActionDispatch::Static.new(DummyApp, "#{FIXTURE_LOAD_PATH}/public", "public, max-age=60") def setup @app = App -- cgit v1.2.3 From f7c711baee9ccd25fb976d45cb9fca033878fd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 12:42:42 +0200 Subject: No need for a regexp here. --- actionpack/lib/action_view/helpers/asset_paths.rb | 1 - actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb | 3 ++- actionpack/lib/action_view/helpers/sprockets_helper.rb | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index cb6737b94e..958f0e0a10 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -21,7 +21,6 @@ module ActionView return source if is_uri?(source) source = rewrite_extension(source, dir, ext) if ext - source = "/#{dir}/#{source}" unless source[0] == ?/ source = rewrite_asset_path(source, dir) if controller && include_host diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 38860431b4..cd0f8c8878 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -41,7 +41,8 @@ module ActionView # Break out the asset path rewrite in case plugins wish to put the asset id # someplace other than the query string. - def rewrite_asset_path(source, path = nil) + def rewrite_asset_path(source, dir) + source = "/#{dir}/#{source}" unless source[0] == ?/ path = config.asset_path if path && path.respond_to?(:call) diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index b43b91178c..ab98da9624 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -40,10 +40,10 @@ module ActionView class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: def rewrite_asset_path(source, dir) - if source =~ /^\/#{dir}\/(.+)/ - assets.path($1, performing_caching?, dir) - else + if source[0] == ?/ source + else + assets.path(source, performing_caching?, dir) end end -- cgit v1.2.3 From 06ddda0a5f659cab8c2c76cfcb3b27ac5bb47ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 12:56:57 +0200 Subject: We no longer depend on those git repos. --- Gemfile | 3 +-- actionpack/actionpack.gemspec | 2 +- railties/lib/rails/generators/app_base.rb | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 6cbc1bbebc..4d317ba1dc 100644 --- a/Gemfile +++ b/Gemfile @@ -8,10 +8,9 @@ else gem "arel", '~> 2.1.0' end -gem "rack", :git => "git://github.com/rack/rack.git" gem "rack-test", :git => "git://github.com/brynary/rack-test.git" - gem "sprockets", :git => "git://github.com/sstephenson/sprockets.git" + gem "coffee-script" gem "sass" gem "uglifier" diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index d85f077e0f..2385ac611f 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency('rack-cache', '~> 1.0.0') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('i18n', '~> 0.6.0beta1') - s.add_dependency('rack', '~> 1.3.0') + s.add_dependency('rack', '~> 1.3.0.beta') s.add_dependency('rack-test', '~> 0.5.7') s.add_dependency('rack-mount', '~> 0.7.2') s.add_dependency('sprockets', '~> 2.0.0.beta.2') diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 689ef921e1..b5110fa9b7 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -130,14 +130,10 @@ module Rails if options.dev? <<-GEMFILE.strip_heredoc gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' - gem 'arel', :git => 'git://github.com/rails/arel.git' - gem 'rack', :git => 'git://github.com/rack/rack.git' GEMFILE elsif options.edge? <<-GEMFILE.strip_heredoc gem 'rails', :git => 'git://github.com/rails/rails.git' - gem 'arel', :git => 'git://github.com/rails/arel.git' - gem 'rack', :git => 'git://github.com/rack/rack.git' GEMFILE else <<-GEMFILE.strip_heredoc @@ -145,8 +141,6 @@ module Rails # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' - # gem 'arel', :git => 'git://github.com/rails/arel.git' - # gem 'rack', :git => 'git://github.com/rack/rack.git' GEMFILE end end -- cgit v1.2.3 From 1ca81238b5abcbdad667d45a588fdb1f68a0516e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Tue, 3 May 2011 13:07:39 +0200 Subject: Add static_cache_control as configuration option and set a default for test. Should improve performance in acceptance tests. --- railties/lib/rails/application.rb | 2 +- railties/lib/rails/application/configuration.rb | 3 ++- .../generators/rails/app/templates/config/environments/test.rb.tt | 6 +++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 0c3c7737ea..4e82761e5d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -152,7 +152,7 @@ module Rails end if config.serve_static_assets - middleware.use ::ActionDispatch::Static, paths["public"].first + middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control end middleware.use ::Rack::Lock unless config.allow_concurrency diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index f818313955..29b9c27a13 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -9,7 +9,7 @@ module Rails :dependency_loading, :encoding, :filter_parameters, :force_ssl, :helpers_paths, :logger, :preload_frameworks, :reload_plugins, :secret_token, :serve_static_assets, - :session_options, :time_zone, :whiny_nils + :static_cache_control, :session_options, :time_zone, :whiny_nils attr_writer :log_level @@ -22,6 +22,7 @@ module Rails @helpers_paths = [] @dependency_loading = true @serve_static_assets = true + @static_cache_control = nil @force_ssl = false @session_store = :cookie_store @session_options = {} diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt index d8d1e55157..8d11377211 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt @@ -7,7 +7,11 @@ # and recreated between test runs. Don't rely on the data there! config.cache_classes = true - # Log error messages when you accidentally call methods on nil. + # Configure static asset server for tests with Cache-Control for performance + config.serve_static_assets = true + config.static_cache_control = "public, max-age=3600" + + # Log error messages when you accidentally call methods on nil config.whiny_nils = true # Show full error reports and disable caching -- cgit v1.2.3 From c7f7a45676f929195d6b12824acd7f200610e081 Mon Sep 17 00:00:00 2001 From: Matias Korhonen Date: Tue, 3 May 2011 13:44:31 +0300 Subject: Rescues template HTML5 doctype and the utf8 charset meta tag, and better font choices for Mac users. --- .../lib/action_dispatch/middleware/templates/rescues/layout.erb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 6c32fb17b8..6e71fd7ddc 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -1,11 +1,13 @@ - + + + Action Controller: Exception caught