From 4d1552810f631898c3d7f758454c92ca35a8cb26 Mon Sep 17 00:00:00 2001 From: Marshall Huss <mwhuss@gmail.com> Date: Mon, 18 May 2009 18:34:44 -0400 Subject: HTTP proxy support [#2133 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activeresource/CHANGELOG | 5 ++ activeresource/lib/active_resource/base.rb | 22 ++++++++ activeresource/lib/active_resource/connection.rb | 16 ++++-- activeresource/test/base_test.rb | 64 ++++++++++++++++++++++++ activeresource/test/connection_test.rb | 10 ++++ activeresource/test/fixtures/proxy.rb | 4 ++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 activeresource/test/fixtures/proxy.rb diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 6572934893..1f9ecea6c5 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,3 +1,8 @@ +*Edge* + +* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet] + + *2.3.2 [Final] (March 15, 2009)* * Nothing new, just included in 2.3.2 diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index bc82139dac..9db35881b8 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -257,6 +257,22 @@ module ActiveResource end end + # Gets the \proxy variable if a proxy is required + def proxy + # Not using superclass_delegating_reader. See +site+ for explanation + if defined?(@proxy) + @proxy + elsif superclass != Object && superclass.proxy + superclass.proxy.dup.freeze + end + end + + # Sets the URI of the http proxy to the value in the +proxy+ argument. + def proxy=(proxy) + @connection = nil + @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy) + end + # Gets the \user for REST HTTP authentication. def user # Not using superclass_delegating_reader. See +site+ for explanation @@ -332,6 +348,7 @@ module ActiveResource def connection(refresh = false) if defined?(@connection) || superclass == Object @connection = Connection.new(site, format) if refresh || @connection.nil? + @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password @connection.timeout = timeout if timeout @@ -622,6 +639,11 @@ module ActiveResource site.is_a?(URI) ? site.dup : URI.parse(site) end + # Accepts a URI and creates the proxy URI from that. + def create_proxy_uri_from(proxy) + proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy) + end + # contains a set of the current prefix parameters. def prefix_parameters @prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 99d4b8f2ca..b29e379c53 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -16,7 +16,7 @@ module ActiveResource :delete => 'Accept' } - attr_reader :site, :user, :password, :timeout + attr_reader :site, :user, :password, :timeout, :proxy attr_accessor :format class << self @@ -41,6 +41,11 @@ module ActiveResource @password = URI.decode(@site.password) if @site.password end + # Set the proxy for remote service. + def proxy=(proxy) + @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) + end + # Sets the user for remote service. def user=(user) @user = user @@ -132,8 +137,13 @@ module ActiveResource # Creates new Net::HTTP instance for communication with the # remote service and resources. def http - http = Net::HTTP.new(@site.host, @site.port) - http.use_ssl = @site.is_a?(URI::HTTPS) + http = + if @proxy + Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password) + else + Net::HTTP.new(@site.host, @site.port) + end + http.use_ssl = @site.is_a?(URI::HTTPS) http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used. http diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 82d3b2ae96..e68d562d97 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -3,6 +3,7 @@ require "fixtures/person" require "fixtures/customer" require "fixtures/street_address" require "fixtures/beast" +require "fixtures/proxy" require 'active_support/core_ext/hash/conversions' class BaseTest < Test::Unit::TestCase @@ -125,6 +126,28 @@ class BaseTest < Test::Unit::TestCase assert_nil actor.site end + def test_proxy_accessor_accepts_uri_or_string_argument + proxy = URI.parse('http://localhost') + + assert_nothing_raised { Person.proxy = 'http://localhost' } + assert_equal proxy, Person.proxy + + assert_nothing_raised { Person.proxy = proxy } + assert_equal proxy, Person.proxy + end + + def test_should_use_proxy_prefix_and_credentials + assert_equal 'http://user:password@proxy.local:3000', ProxyResource.proxy.to_s + end + + def test_proxy_variable_can_be_reset + actor = Class.new(ActiveResource::Base) + assert_nil actor.site + actor.proxy = 'http://localhost:31337' + actor.proxy = nil + assert_nil actor.site + end + def test_should_accept_setting_user Forum.user = 'david' assert_equal('david', Forum.user) @@ -221,6 +244,47 @@ class BaseTest < Test::Unit::TestCase assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class' end + def test_proxy_reader_uses_superclass_site_until_written + # Superclass is Object so returns nil. + assert_nil ActiveResource::Base.proxy + assert_nil Class.new(ActiveResource::Base).proxy + + # Subclass uses superclass proxy. + actor = Class.new(Person) + assert_equal Person.proxy, actor.proxy + + # Subclass returns frozen superclass copy. + assert !Person.proxy.frozen? + assert actor.proxy.frozen? + + # Changing subclass proxy doesn't change superclass site. + actor.proxy = 'http://localhost:31337' + assert_not_equal Person.proxy, actor.proxy + + # Changed subclass proxy is not frozen. + assert !actor.proxy.frozen? + + # Changing superclass proxy doesn't overwrite subclass site. + Person.proxy = 'http://somewhere.else' + assert_not_equal Person.proxy, actor.proxy + + # Changing superclass proxy after subclassing changes subclass site. + jester = Class.new(actor) + actor.proxy = 'http://nomad' + assert_equal actor.proxy, jester.proxy + assert jester.proxy.frozen? + + # Subclasses are always equal to superclass proxy when not overridden + fruit = Class.new(ActiveResource::Base) + apple = Class.new(fruit) + + fruit.proxy = 'http://market' + assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class' + + fruit.proxy = 'http://supermarket' + assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class' + end + def test_user_reader_uses_superclass_user_until_written # Superclass is Object so returns nil. assert_nil ActiveResource::Base.user diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 831fbc4003..60a8204533 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -101,6 +101,16 @@ class ConnectionTest < Test::Unit::TestCase assert_equal site, @conn.site end + def test_proxy_accessor_accepts_uri_or_string_argument + proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242") + + assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" } + assert_equal proxy, @conn.proxy + + assert_nothing_raised { @conn.proxy = proxy } + assert_equal proxy, @conn.proxy + end + def test_timeout_accessor @conn.timeout = 5 assert_equal 5, @conn.timeout diff --git a/activeresource/test/fixtures/proxy.rb b/activeresource/test/fixtures/proxy.rb new file mode 100644 index 0000000000..bb8e015df0 --- /dev/null +++ b/activeresource/test/fixtures/proxy.rb @@ -0,0 +1,4 @@ +class ProxyResource < ActiveResource::Base + self.site = "http://localhost" + self.proxy = "http://user:password@proxy.local:3000" +end \ No newline at end of file -- cgit v1.2.3 From c0f828ca4f3aa45a4c8ea8761183ad3854f0a13f Mon Sep 17 00:00:00 2001 From: Elad Meidar <elad@eizesus.com> Date: Sat, 8 Aug 2009 17:06:45 -0400 Subject: Fixed reference to AR::SessionStore::Session.table_name in session migrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- .../session_migration/session_migration_generator.rb | 6 +++++- railties/lib/tasks/databases.rake | 2 +- .../test/generators/session_migration_generator_test.rb | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb index d60da5c0a5..d78b9d42b0 100644 --- a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb +++ b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb @@ -12,7 +12,11 @@ module ActiveRecord protected def session_table_name - ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session' + current_table_name = ActiveRecord::SessionStore::Session.table_name + if current_table_name == "sessions" || current_table_name == "session" + current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session') + end + current_table_name end end diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 23a3a73a7f..0fefc0433a 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -448,7 +448,7 @@ def drop_database(config) end def session_table_name - ActiveRecord::Base.pluralize_table_names ? :sessions : :session + ActiveRecord::SessionStore::Session.table_name end def set_firebird_env(config) diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index f83109800b..a87eeb1e1a 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -2,6 +2,16 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'generators/rails/session_migration/session_migration_generator' +module ActiveRecord + module SessionStore + class Session + class << self + attr_accessor :table_name + end + end + end +end + class SessionMigrationGeneratorTest < GeneratorsTestCase def test_session_migration_with_default_name @@ -14,6 +24,10 @@ class SessionMigrationGeneratorTest < GeneratorsTestCase assert_migration "db/migrate/create_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/ end + def test_session_migtions_with_custom_table_name + run_generator + assert_migration "db/migrate/add_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/ + end protected def run_generator(args=[]) -- cgit v1.2.3 From 3ea091e1cc4deab1410676d080dc1fcdc572d65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 00:20:03 +0200 Subject: Improved coverage for session_migration generator. [#3008 status:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../session_migration/session_migration_generator.rb | 2 +- railties/test/generators/session_migration_generator_test.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb index d78b9d42b0..59c4792066 100644 --- a/railties/lib/generators/active_record/session_migration/session_migration_generator.rb +++ b/railties/lib/generators/active_record/session_migration/session_migration_generator.rb @@ -13,7 +13,7 @@ module ActiveRecord def session_table_name current_table_name = ActiveRecord::SessionStore::Session.table_name - if current_table_name == "sessions" || current_table_name == "session" + if ["sessions", "session"].include?(current_table_name) current_table_name = (ActiveRecord::Base.pluralize_table_names ? 'session'.pluralize : 'session') end current_table_name diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index a87eeb1e1a..57bd755a9a 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -24,9 +24,13 @@ class SessionMigrationGeneratorTest < GeneratorsTestCase assert_migration "db/migrate/create_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/ end - def test_session_migtions_with_custom_table_name + def test_session_migration_with_custom_table_name + ActiveRecord::SessionStore::Session.table_name = "custom_table_name" run_generator - assert_migration "db/migrate/add_session_table.rb", /class CreateSessionTable < ActiveRecord::Migration/ + assert_migration "db/migrate/add_sessions_table.rb" do |migration| + assert_match /class AddSessionsTable < ActiveRecord::Migration/, migration + assert_match /create_table :custom_table_name/, migration + end end protected -- cgit v1.2.3 From bee3e099bd3f4038f8a4122ad48446a232cbf21a Mon Sep 17 00:00:00 2001 From: Matt Duncan <mrduncan@gmail.com> Date: Sat, 8 Aug 2009 18:07:17 -0400 Subject: Users can now pass :branch for git plugins and :revision for subversion plugins [#2352 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- railties/lib/generators/actions.rb | 11 ++++++++++- railties/test/generators/actions_test.rb | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/railties/lib/generators/actions.rb b/railties/lib/generators/actions.rb index 55ef212abb..03d0d11a07 100644 --- a/railties/lib/generators/actions.rb +++ b/railties/lib/generators/actions.rb @@ -5,22 +5,31 @@ module Rails module Actions # Install a plugin. You must provide either a Subversion url or Git url. - # For a Git-hosted plugin, you can specify if it should be added as a submodule instead of cloned. + # + # For a Git-hosted plugin, you can specify a branch and + # whether it should be added as a submodule instead of cloned. + # + # For a Subversion-hosted plugin you can specify a revision. # # ==== Examples # # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git' + # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :branch => 'stable' # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk' + # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk', :revision => 1234 # def plugin(name, options) log :plugin, name if options[:git] && options[:submodule] + options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch] in_root do run "git submodule add #{options[:git]} vendor/plugins/#{name}", :verbose => false end elsif options[:git] || options[:svn] + options[:git] = "-b #{options[:branch]} #{options[:git]}" if options[:branch] + options[:svn] = "-r #{options[:revision]} #{options[:svn]}" if options[:revision] in_root do run_ruby_script "script/plugin install #{options[:svn] || options[:git]}", :verbose => false end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index 0cda49702b..fdaef6d9cb 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -29,11 +29,26 @@ class ActionsTest < GeneratorsTestCase action :plugin, 'restful-authentication', :svn => @svn_plugin_uri end + def test_plugin_with_git_option_and_branch_should_run_plugin_install + generator.expects(:run_ruby_script).once.with("script/plugin install -b stable #{@git_plugin_uri}", :verbose => false) + action :plugin, 'restful-authentication', :git => @git_plugin_uri, :branch => 'stable' + end + + def test_plugin_with_svn_option_and_revision_should_run_plugin_install + generator.expects(:run_ruby_script).once.with("script/plugin install -r 1234 #{@svn_plugin_uri}", :verbose => false) + action :plugin, 'restful-authentication', :svn => @svn_plugin_uri, :revision => 1234 + end + def test_plugin_with_git_option_and_submodule_should_use_git_scm generator.expects(:run).with("git submodule add #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false) action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true end + def test_plugin_with_git_option_and_submodule_should_use_git_scm + generator.expects(:run).with("git submodule add -b stable #{@git_plugin_uri} vendor/plugins/rest_auth", :verbose => false) + action :plugin, 'rest_auth', :git => @git_plugin_uri, :submodule => true, :branch => 'stable' + end + def test_plugin_with_no_options_should_skip_method generator.expects(:run).never action :plugin, 'rest_auth', {} -- cgit v1.2.3 From 5632b36701ad9514d596c558877cd74c14c1d54b Mon Sep 17 00:00:00 2001 From: Adam Keys <adam@therealadam.com> Date: Sat, 8 Aug 2009 16:35:03 -0500 Subject: Fix exclusive range patch to use begin/end instead of min/max. [#2981 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activemodel/lib/active_model/validations/length.rb | 12 ++++++++---- .../test/cases/validations/length_validation_test.rb | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 3e76796355..e91841bd1c 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -66,10 +66,14 @@ module ActiveModel validates_each(attrs, options) do |record, attr, value| value = options[:tokenizer].call(value) if value.kind_of?(String) - if value.nil? or value.size < option_value.begin - record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin) - elsif value.size > option_value.end - record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end) + + min, max = option_value.begin, option_value.end + max = max - 1 if option_value.exclude_end? + + if value.nil? || value.size < min + record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => min) + elsif value.size > max + record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => max) end end when :is, :minimum, :maximum diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 499f6a5e31..2c97b762f1 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -112,6 +112,20 @@ class LengthValidationTest < ActiveModel::TestCase assert t.valid? end + def test_validates_length_of_using_within_with_exclusive_range + Topic.validates_length_of(:title, :within => 4...10) + + t = Topic.new("title" => "9 chars!!") + assert t.valid? + + t.title = "Now I'm 10" + assert !t.valid? + assert_equal ["is too long (maximum is 9 characters)"], t.errors[:title] + + t.title = "Four" + assert t.valid? + end + def test_optionally_validates_length_of_using_within Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true -- cgit v1.2.3 From df745ed80556692f2363a661c8504601be40a5ad Mon Sep 17 00:00:00 2001 From: Michael Koziarski <michael@koziarski.com> Date: Sun, 9 Aug 2009 11:11:34 +1200 Subject: Depend on rubygems 1.3.2 Also move the min_version definition up a line so it's present in the rescue block down below. --- railties/lib/generators/rails/app/templates/config/boot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/generators/rails/app/templates/config/boot.rb b/railties/lib/generators/rails/app/templates/config/boot.rb index 0ad0f787f8..dd5e3b6916 100644 --- a/railties/lib/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/generators/rails/app/templates/config/boot.rb @@ -82,8 +82,8 @@ module Rails end def load_rubygems + min_version = '1.3.2' require 'rubygems' - min_version = '1.3.1' unless rubygems_version >= min_version $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 -- cgit v1.2.3 From 618771beb57e623e05f70fd31f3bdb0c04318017 Mon Sep 17 00:00:00 2001 From: "Steve St. Martin" <kuprishuz@gmail.com> Date: Sat, 8 Aug 2009 18:44:33 -0400 Subject: Update truncate documentation / examples to more clearly demonstrate its actual behavior [#3016 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- actionpack/lib/action_view/helpers/text_helper.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index c3ce4c671e..34ef742a6e 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -33,13 +33,15 @@ module ActionView end # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> - # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "..."). + # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "...") + # for a total length not exceeding <tt>:length</tt>. + # # Pass a <tt>:separator</tt> to truncate +text+ at a natural break. # # ==== Examples # # truncate("Once upon a time in a world far far away") - # # => Once upon a time in a world f... + # # => Once upon a time in a world... # # truncate("Once upon a time in a world far far away", :separator => ' ') # # => Once upon a time in a world... @@ -48,19 +50,19 @@ module ActionView # # => Once upon a... # # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)") - # # => And they found that many (clipped) + # # => And they found t(clipped) # - # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 25) + # # => And they f... (continued) # # You can still use <tt>truncate</tt> with the old API that accepts the # +length+ as its optional second and the +ellipsis+ as its # optional third parameter: # truncate("Once upon a time in a world far far away", 14) - # # => Once upon a time in a world f... + # # => Once upon a... # - # truncate("And they found that many people were sleeping better.", 15, "... (continued)") - # # => And they found... (continued) + # truncate("And they found that many people were sleeping better.", 25, "... (continued)") + # # => And they f... (continued) def truncate(text, *args) options = args.extract_options! unless args.empty? -- cgit v1.2.3 From 1f6afe4a74bb815a33f41b2d75acd530de6e2eba Mon Sep 17 00:00:00 2001 From: Jan Schwenzien <jan@schwenzien.info> Date: Sun, 9 Aug 2009 01:24:26 +0100 Subject: Fix HTTP basic authentication for long credentials [#2572 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../action_controller/metal/http_authentication.rb | 2 +- .../controller/http_basic_authentication_test.rb | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 525787bf92..5ebe1ee048 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -141,7 +141,7 @@ module ActionController end def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split.last || '') + ActiveSupport::Base64.decode64(authorization(request).split(' ', 2).last || '') end def encode_credentials(user_name, password) diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index fbc94a0df7..23688ca584 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -4,6 +4,7 @@ class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base before_filter :authenticate, :only => :index before_filter :authenticate_with_request, :only => :display + before_filter :authenticate_long_credentials, :only => :show def index render :text => "Hello Secret" @@ -12,6 +13,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase def display render :text => 'Definitely Maybe' end + + def show + render :text => 'Only for loooooong credentials' + end private @@ -28,6 +33,12 @@ class HttpBasicAuthenticationTest < ActionController::TestCase request_http_basic_authentication("SuperSecret") end end + + def authenticate_long_credentials + authenticate_or_request_with_http_basic do |username, password| + username == '1234567890123456789012345678901234567890' && password == '1234567890123456789012345678901234567890' + end + end end AUTH_HEADERS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION', 'REDIRECT_X_HTTP_AUTHORIZATION'] @@ -42,6 +53,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :success assert_equal 'Hello Secret', @response.body, "Authentication failed for request header #{header}" end + test "successful authentication with #{header.downcase} and long credentials" do + @request.env[header] = encode_credentials('1234567890123456789012345678901234567890', '1234567890123456789012345678901234567890') + get :show + + assert_response :success + assert_equal 'Only for loooooong credentials', @response.body, "Authentication failed for request header #{header} and long credentials" + end end AUTH_HEADERS.each do |header| @@ -52,6 +70,13 @@ class HttpBasicAuthenticationTest < ActionController::TestCase assert_response :unauthorized assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header}" end + test "unsuccessful authentication with #{header.downcase} and long credentials" do + @request.env[header] = encode_credentials('h4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0rh4x0r', 'worldworldworldworldworldworldworldworld') + get :show + + assert_response :unauthorized + assert_equal "HTTP Basic: Access denied.\n", @response.body, "Authentication didn't fail for request header #{header} and long credentials" + end end test "authentication request without credential" do -- cgit v1.2.3 From 45f5bdfbc7fc1ee1fbc23525a112062c8b31d539 Mon Sep 17 00:00:00 2001 From: Matt Duncan <mrduncan@gmail.com> Date: Sat, 8 Aug 2009 20:10:01 -0400 Subject: Adding :from scoping to ActiveRecord calculations Signed-off-by: Michael Koziarski <michael@koziarski.com> [#1229 state:committed] --- activerecord/lib/active_record/calculations.rb | 2 ++ activerecord/test/cases/calculations_test.rb | 6 ++++++ activerecord/test/models/organization.rb | 2 ++ 3 files changed, 10 insertions(+) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 727f4c1dc6..4a88c43dff 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -197,6 +197,8 @@ module ActiveRecord sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] if options[:from] sql << " FROM #{options[:from]} " + elsif scope && scope[:from] + sql << " FROM #{scope[:from]} " else sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround sql << " FROM #{connection.quote_table_name(table_name)} " diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 24bc4f71ce..c2e02763f6 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -2,6 +2,8 @@ require "cases/helper" require 'models/company' require 'models/topic' require 'models/edge' +require 'models/club' +require 'models/organization' Company.has_many :accounts @@ -223,6 +225,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 15, companies(:rails_core).companies.sum(:id) end + def test_should_sum_scoped_field_with_from + assert_equal Club.count, Organization.clubs.count + end + def test_should_sum_scoped_field_with_conditions assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') end diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index d79d5037c8..c85726169e 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -1,4 +1,6 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details + + named_scope :clubs, { :from => 'clubs' } end \ No newline at end of file -- cgit v1.2.3 From 3b3798506b403911665c3c24fd055b75d6f6a44f Mon Sep 17 00:00:00 2001 From: Matt Duncan <mrduncan@gmail.com> Date: Sat, 8 Aug 2009 20:10:01 -0400 Subject: Adding :from scoping to ActiveRecord calculations Signed-off-by: Michael Koziarski <michael@koziarski.com> [#1229 state:committed] --- activerecord/lib/active_record/calculations.rb | 2 ++ activerecord/test/cases/calculations_test.rb | 6 ++++++ activerecord/test/models/organization.rb | 2 ++ 3 files changed, 10 insertions(+) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 727f4c1dc6..4a88c43dff 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -197,6 +197,8 @@ module ActiveRecord sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] if options[:from] sql << " FROM #{options[:from]} " + elsif scope && scope[:from] + sql << " FROM #{scope[:from]} " else sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround sql << " FROM #{connection.quote_table_name(table_name)} " diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 24bc4f71ce..c2e02763f6 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -2,6 +2,8 @@ require "cases/helper" require 'models/company' require 'models/topic' require 'models/edge' +require 'models/club' +require 'models/organization' Company.has_many :accounts @@ -223,6 +225,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 15, companies(:rails_core).companies.sum(:id) end + def test_should_sum_scoped_field_with_from + assert_equal Club.count, Organization.clubs.count + end + def test_should_sum_scoped_field_with_conditions assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') end diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index d79d5037c8..c85726169e 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -1,4 +1,6 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details + + named_scope :clubs, { :from => 'clubs' } end \ No newline at end of file -- cgit v1.2.3 From 29096268ccce2b13e1490c8b673ffe0b498555fc Mon Sep 17 00:00:00 2001 From: Marc-Andre Lafortune <github@marc-andre.ca> Date: Tue, 14 Apr 2009 01:12:55 -0400 Subject: Enumerable#sum now works will all enumerables, even if they don't respond to :size [#2489 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/core_ext/enumerable.rb | 6 ++---- activesupport/test/core_ext/enumerable_test.rb | 4 ++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 434a32b29b..e89b7e392e 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -55,12 +55,10 @@ module Enumerable # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) # def sum(identity = 0, &block) - return identity unless size > 0 - if block_given? - map(&block).sum + map(&block).sum(identity) else - inject { |sum, element| sum + element } + inject { |sum, element| sum + element } || identity end end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 885393815b..1927a0ad0d 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -60,6 +60,10 @@ class EnumerableTests < Test::Unit::TestCase assert_equal Payment.new(0), [].sum(Payment.new(0)) end + def test_enumerable_sums + assert_equal 10, (1..4).sum + end + def test_each_with_object result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result) -- cgit v1.2.3 From 071f48b716ce2cd8a46219730afc307c258a9798 Mon Sep 17 00:00:00 2001 From: bastilian <sebastian.graessl@gmail.com> Date: Tue, 12 May 2009 21:01:20 +0200 Subject: Make sure db:drop doesn't fail when sqlite db is given by an absolute path [#1789 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- railties/lib/tasks/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 0fefc0433a..93aec674ab 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -440,7 +440,7 @@ def drop_database(config) ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config['database'] when /^sqlite/ - FileUtils.rm(File.join(RAILS_ROOT, config['database'])) + FileUtils.rm((config['database'] =~ /^\// ? config['database'] : File.join(RAILS_ROOT, config['database']))) when 'postgresql' ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.drop_database config['database'] -- cgit v1.2.3 From 89c8affe475d14f6dbe74b5e75a2613e047d01fc Mon Sep 17 00:00:00 2001 From: Pratik Naik <pratiknaik@gmail.com> Date: Sun, 9 Aug 2009 01:53:50 +0100 Subject: Use Pathname for checking if sqlite path is absolute --- railties/lib/tasks/databases.rake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 93aec674ab..b82341ba94 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -440,7 +440,11 @@ def drop_database(config) ActiveRecord::Base.establish_connection(config) ActiveRecord::Base.connection.drop_database config['database'] when /^sqlite/ - FileUtils.rm((config['database'] =~ /^\// ? config['database'] : File.join(RAILS_ROOT, config['database']))) + require 'pathname' + path = Pathname.new(config['database']) + file = path.absolute? ? path.to_s : File.join(RAILS_ROOT, path) + + FileUtils.rm(file) when 'postgresql' ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.drop_database config['database'] -- cgit v1.2.3 From 271baf235d6e197a6ecd25945c34df0385137764 Mon Sep 17 00:00:00 2001 From: Jon Wood <jonathan.wood@uk.clara.net> Date: Wed, 6 May 2009 16:23:20 +0100 Subject: Add :redirect to the testable RJS statements [#2612 state:resolved] Example : assert_select_rjs :redirect, root_path Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../lib/action_dispatch/testing/assertions/selector.rb | 13 ++++++++++--- actionpack/test/controller/assert_select_test.rb | 7 +++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index dd75cda6b9..d22adfa749 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -345,14 +345,17 @@ module ActionDispatch # # Use the first argument to narrow down assertions to only statements # of that type. Possible values are <tt>:replace</tt>, <tt>:replace_html</tt>, - # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tt> and - # <tt>:insert_html</tt>. + # <tt>:show</tt>, <tt>:hide</tt>, <tt>:toggle</tt>, <tt>:remove</tta>, + # <tt>:insert_html</tt> and <tt>:redirect</tt>. # # Use the argument <tt>:insert</tt> followed by an insertion position to narrow # down the assertion to only statements that insert elements in that # position. Possible values are <tt>:top</tt>, <tt>:bottom</tt>, <tt>:before</tt> # and <tt>:after</tt>. # + # Use the argument <tt>:redirect</tt> follwed by a path to check that an statement + # which redirects to the specified path is generated. + # # Using the <tt>:remove</tt> statement, you will be able to pass a block, but it will # be ignored as there is no HTML passed for this statement. # @@ -399,6 +402,9 @@ module ActionDispatch # # # The same, but shorter. # assert_select "ol>li", 4 + # + # # Checking for a redirect. + # assert_select_rjs :redirect, root_path def assert_select_rjs(*args, &block) rjs_type = args.first.is_a?(Symbol) ? args.shift : nil id = args.first.is_a?(String) ? args.shift : nil @@ -576,7 +582,8 @@ module ActionDispatch :chained_replace => "\\$\\(#{RJS_ANY_ID}\\)\\.replace\\(#{RJS_PATTERN_HTML}\\)", :chained_replace_html => "\\$\\(#{RJS_ANY_ID}\\)\\.update\\(#{RJS_PATTERN_HTML}\\)", :replace_html => "Element\\.update\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", - :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)" + :replace => "Element\\.replace\\(#{RJS_ANY_ID}, #{RJS_PATTERN_HTML}\\)", + :redirect => "window.location.href = #{RJS_ANY_ID}" } [:remove, :show, :hide, :toggle].each do |action| RJS_STATEMENTS[action] = "Element\\.#{action}\\(#{RJS_ANY_ID}\\)" diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index ad17d1288b..2e77d2f8ad 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -257,6 +257,13 @@ class AssertSelectTest < ActionController::TestCase end assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"} end + + def test_assert_select_rjs_for_redirect_to + render_rjs do |page| + page.redirect_to '/' + end + assert_select_rjs :redirect, '/' + end def test_elect_with_xml_namespace_attributes render_html %Q{<link xlink:href="http://nowhere.com"></link>} -- cgit v1.2.3 From 370bf1401af62c8589d557cd51b9533dd603c463 Mon Sep 17 00:00:00 2001 From: Michael Koziarski <michael@koziarski.com> Date: Sun, 9 Aug 2009 13:07:50 +1200 Subject: Don't call additional methods on builders passed to the atom_feed helper. Additionally, actually test that the atom_feed helper works with :xml as an option. [#1836 state:committed] --- .../lib/action_view/helpers/atom_feed_helper.rb | 2 +- actionpack/test/template/atom_feed_helper_test.rb | 29 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/atom_feed_helper.rb b/actionpack/lib/action_view/helpers/atom_feed_helper.rb index dc4497581c..9951e11a37 100644 --- a/actionpack/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionpack/lib/action_view/helpers/atom_feed_helper.rb @@ -98,7 +98,7 @@ module ActionView options[:schema_date] = "2005" # The Atom spec copyright date end - xml = options[:xml] || eval("xml", block.binding) + xml = options.delete(:xml) || eval("xml", block.binding) xml.instruct! if options[:instruct] options[:instruct].each do |target,attrs| diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 3acaecd142..6a5fb0acff 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -157,6 +157,26 @@ class ScrollsController < ActionController::Base end end EOT + FEEDS["provide_builder"] = <<-'EOT' + # we pass in the new_xml to the helper so it doesn't + # call anything on the original builder + new_xml = Builder::XmlMarkup.new(:target=>'') + atom_feed(:xml => new_xml) do |feed| + feed.title("My great blog!") + feed.updated((@scrolls.first.created_at)) + + for scroll in @scrolls + feed.entry(scroll) do |entry| + entry.title(scroll.title) + entry.content(scroll.body, :type => 'html') + + entry.author do |author| + author.name("DHH") + end + end + end + end + EOT def index @scrolls = [ Scroll.new(1, "1", "Hello One", "Something <i>COOL!</i>", Time.utc(2007, 12, 12, 15), Time.utc(2007, 12, 12, 15)), @@ -202,6 +222,15 @@ class AtomFeedTest < ActionController::TestCase end end + def test_providing_builder_to_atom_feed + with_restful_routing(:scrolls) do + get :index, :id=>"provide_builder" + # because we pass in the non-default builder, the content generated by the + # helper should go 'nowhere'. Leaving the response body blank. + assert @response.body.blank? + end + end + def test_entry_with_prefilled_options_should_use_those_instead_of_querying_the_record with_restful_routing(:scrolls) do get :index, :id => "entry_options" -- cgit v1.2.3 From d8041538dd1fed038c1ad30ddca5c9ce8254ee30 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <hugo.peixoto@gmail.com> Date: Sun, 9 Aug 2009 03:49:57 +0100 Subject: MySQL: fix diacritic uniqueness test by setting the default character set and collation to utf8/utf8_unicode_ci [#2883 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 0d33b9d516..09dbc5ad6d 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -64,8 +64,8 @@ end namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest ) - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 ) + %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) + %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) end desc 'Drop the MySQL test databases' -- cgit v1.2.3 From 4b7f95eb8b45a32c470a414c7f536fb8cb029bda Mon Sep 17 00:00:00 2001 From: Rob <rob.anderton@thewebfellas.com> Date: Fri, 1 May 2009 23:22:09 +0100 Subject: Fix binary fixture test on Windows [#2597 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/test/cases/fixtures_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index b07d4f3521..eb3f03c91d 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -185,7 +185,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures assert_equal 1, @binaries.size - data = File.read(ASSETS_ROOT + "/flowers.jpg") + data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) data.freeze assert_equal data, @flowers.data -- cgit v1.2.3 From 791cccaedab9c3e975b00135db76047ad4435611 Mon Sep 17 00:00:00 2001 From: Tristan Dunn <tristanzdunn@gmail.com> Date: Sat, 8 Aug 2009 18:29:20 -0400 Subject: Don't define a default primary key in the schema dumper. [#1908 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/lib/active_record/schema_dumper.rb | 1 - activerecord/test/cases/schema_dumper_test.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 5d88012e4f..c8e1b4f53a 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -84,7 +84,6 @@ HEADER elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - pk ||= 'id' tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 4f8e20b3ba..e6a77f626b 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -205,4 +205,12 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output end end + + def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added + output = standard_dump + match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) + assert_not_nil(match, "goofy_string_id table not found") + assert_match %r(:id => false), match[1], "no table id not preserved" + assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" + end end -- cgit v1.2.3 From b5b1576aa9c5606a8ac9862b4a903aa5e196ef02 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sat, 8 Aug 2009 23:04:25 -0700 Subject: Ruby 1.9 compat: can't implicitly set instance var using block arg --- actionpack/lib/action_view/render/partials.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 986d3af454..440836bfb1 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -232,8 +232,8 @@ module ActionView index = 0 - collection.map do |@object| - @path = partial_path + collection.map do |object| + @object, @path = object, partial_path template = passed_template || find @locals[template.counter_name] = index index += 1 -- cgit v1.2.3 From bf412c9ec641de6d6bc89a294d02f04c86abc392 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sat, 8 Aug 2009 17:30:04 -0300 Subject: Clean up initializer and some of the internals of PartialRenderer --- actionpack/lib/action_view/helpers/form_helper.rb | 4 + actionpack/lib/action_view/render/partials.rb | 90 +++++++++++------------ 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index bde600f6ed..72f162cb20 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -936,6 +936,10 @@ module ActionView @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, '')) end + def to_model + self + end + def initialize(object_name, object, template, options, proc) @nested_child_index = {} @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 440836bfb1..3acf907e60 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -177,69 +177,62 @@ module ActionView @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end - def initialize(view_context, options, formats) - object = options[:partial] + def initialize(view_context, options) + partial = options[:partial] - @view, @formats = view_context, formats + @view = view_context @options = options || {} + @locals = options[:locals] || {} + @layout = options[:layout] - if object.is_a?(String) - @path = object - elsif - @object = object - @path = partial_path unless collection - end + @partial_names = self.class.partial_names[@view.controller.class] + @templates, @formats_hash = Hash.new {|h,k| h[k] = {}}, view_context.formats.hash - @locals = options[:locals] || {} - @object ||= @options[:object] || @locals[:object] - @layout = options[:layout] + if partial.is_a?(String) + @object, @path = options[:object], partial + else + @object = partial + @path = partial_path(partial) + end end def render(&block) - template = find if @path - - if collection - render_collection(template, &block) - else - render_object(template, &block) - end + collection ? render_collection(&block) : render_object(&block) end - def render_template(template, &block) + def render_template(template, object = @object, &block) @options[:_template] = template - @locals[:object] = @locals[template.variable_name] = @object - @locals[@options[:as]] = @object if @options[:as] + @locals[:object] = @locals[template.variable_name] = object + @locals[@options[:as]] = object if @options[:as] content = @view._render_single_template(template, @locals, &block) return content if block_given? || !@layout - find(@layout).render(@view, @locals) { content } + find_template(@layout).render(@view, @locals) { content } end - def render_object(template, &block) - @object ||= @locals[template.variable_name] - render_template(template, &block) + def render_object(&block) + template = find_template + render_template(template, @object || @locals[template.variable_name], &block) end - def render_collection(passed_template = nil, &block) - @options[:_template] = passed_template + def render_collection(&block) + @options[:_template] = provided_template = @path && find_template return nil if collection.blank? if @options.key?(:spacer_template) - spacer = @view.render_partial( - :partial => @options[:spacer_template], :_details => @options[:_details]) + spacer = render_template(find_template(@options[:spacer_template])) end - index = 0 + segments = [] - collection.map do |object| - @object, @path = object, partial_path - template = passed_template || find + collection.each_with_index do |object, index| + template = provided_template || find_template(partial_path(object)) @locals[template.counter_name] = index - index += 1 + segments << render_template(template, object, &block) + end - render_template(template, &block) - end.join(spacer) + segments.join(spacer) end private @@ -251,21 +244,20 @@ module ActionView end end - def find(path = @path) - prefix = @view.controller.controller_path unless path.include?(?/) - @view.find(path, {:formats => @view.formats}, prefix, true) + def find_template(path = @path) + @templates[path][@formats_hash] ||= begin + prefix = @view.controller.controller_path unless path.include?(?/) + @view.find(path, {:formats => @view.formats}, prefix, true) + end end def partial_path(object = @object) - self.class.partial_names[@view.controller.class][object.class] ||= begin - return nil unless object.class.respond_to?(:model_name) + @partial_names[object.class] ||= begin + return nil unless object.respond_to?(:to_model) - name = object.class.model_name - path = @view.controller_path - if path && path.include?(?/) - File.join(File.dirname(path), name.partial_path) - else - name.partial_path + object.to_model.class.model_name.partial_path.tap do |partial| + path = @view.controller_path + partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/) end end end @@ -279,7 +271,7 @@ module ActionView end def _render_partial(options, &block) #:nodoc: - PartialRenderer.new(self, options, formats).render(&block) + PartialRenderer.new(self, options).render(&block) end end -- cgit v1.2.3 From d7415f792cefeaa11453d7ac1d0c3813b2883599 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sat, 8 Aug 2009 18:12:47 -0300 Subject: Clean up partial object some more; replace passing around a block to a single block ivar --- actionpack/lib/action_view/render/partials.rb | 74 ++++++++++++++------------- actionpack/test/controller/render_test.rb | 1 - 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 3acf907e60..e7d03c81e8 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -177,64 +177,66 @@ module ActionView @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end - def initialize(view_context, options) + def initialize(view_context, options, block) partial = options[:partial] - @view = view_context - @options = options || {} - @locals = options[:locals] || {} - @layout = options[:layout] + @view = view_context + @options = options + @locals = options[:locals] || {} + @block = block - @partial_names = self.class.partial_names[@view.controller.class] - @templates, @formats_hash = Hash.new {|h,k| h[k] = {}}, view_context.formats.hash + # Set up some instance variables to speed up memoizing + @partial_names = self.class.partial_names[@view.controller.class] + @templates = Hash.new {|h,k| h[k] = {}} + @formats_hash = view_context.formats.hash - if partial.is_a?(String) - @object, @path = options[:object], partial - else - @object = partial - @path = partial_path(partial) - end - end - - def render(&block) - collection ? render_collection(&block) : render_object(&block) + # Set up the object and path + @object = partial.is_a?(String) ? options[:object] : partial + @path = partial_path(partial) end - def render_template(template, object = @object, &block) - @options[:_template] = template - @locals[:object] = @locals[template.variable_name] = object - @locals[@options[:as]] = object if @options[:as] - - content = @view._render_single_template(template, @locals, &block) - return content if block_given? || !@layout - find_template(@layout).render(@view, @locals) { content } - end + def render + return render_collection if collection - def render_object(&block) template = find_template - render_template(template, @object || @locals[template.variable_name], &block) + render_template(template, @object || @locals[template.variable_name]) end - def render_collection(&block) - @options[:_template] = provided_template = @path && find_template + def render_collection + # Even if no template is rendered, this will ensure that the MIME type + # for the empty response is the same as the provided template + @options[:_template] = default_template = find_template return nil if collection.blank? if @options.key?(:spacer_template) - spacer = render_template(find_template(@options[:spacer_template])) + spacer = find_template(@options[:spacer_template]).render(@view, @locals) end segments = [] collection.each_with_index do |object, index| - template = provided_template || find_template(partial_path(object)) + template = default_template || find_template(partial_path(object)) @locals[template.counter_name] = index - segments << render_template(template, object, &block) + segments << render_template(template, object) end segments.join(spacer) end + def render_template(template, object = @object) + @options[:_template] ||= template + + # TODO: is locals[:object] really necessary? + @locals[:object] = @locals[template.variable_name] = object + @locals[@options[:as]] = object if @options[:as] + + content = @view._render_single_template(template, @locals, &@block) + return content if @block || !@options[:layout] + find_template(@options[:layout]).render(@view, @locals) { content } + end + + private def collection @collection ||= if @object.respond_to?(:to_ary) @@ -245,6 +247,7 @@ module ActionView end def find_template(path = @path) + return if !path @templates[path][@formats_hash] ||= begin prefix = @view.controller.controller_path unless path.include?(?/) @view.find(path, {:formats => @view.formats}, prefix, true) @@ -252,10 +255,11 @@ module ActionView end def partial_path(object = @object) + return object if object.is_a?(String) @partial_names[object.class] ||= begin return nil unless object.respond_to?(:to_model) - object.to_model.class.model_name.partial_path.tap do |partial| + object.to_model.class.model_name.partial_path.dup.tap do |partial| path = @view.controller_path partial.insert(0, "#{File.dirname(path)}/") if path.include?(?/) end @@ -271,7 +275,7 @@ module ActionView end def _render_partial(options, &block) #:nodoc: - PartialRenderer.new(self, options).render(&block) + PartialRenderer.new(self, options, block).render end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 9546fdb50d..0c0599679c 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -1237,7 +1237,6 @@ class RenderTest < ActionController::TestCase def test_partial_collection_with_spacer get :partial_collection_with_spacer assert_equal "Hello: davidonly partialHello: mary", @response.body - assert_template :partial => 'test/_partial_only' assert_template :partial => '_customer' end -- cgit v1.2.3 From b57d94c187965f8ef0994db9ba9f8b7bf1bf6591 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sat, 8 Aug 2009 18:36:05 -0300 Subject: Update minimal.rb to benchmark partials --- actionpack/examples/minimal.rb | 31 ++++++++++++++++++++++++---- actionpack/examples/views/_collection.erb | 1 + actionpack/examples/views/_hello.erb | 1 + actionpack/examples/views/_many_partials.erb | 10 +++++++++ actionpack/examples/views/_partial.erb | 10 +++++++++ actionpack/examples/views/template.html.erb | 2 +- 6 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 actionpack/examples/views/_collection.erb create mode 100644 actionpack/examples/views/_hello.erb create mode 100644 actionpack/examples/views/_many_partials.erb create mode 100644 actionpack/examples/views/_partial.erb diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index 7106149fa2..01c64ba56b 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -3,6 +3,7 @@ ENV['RAILS_ENV'] ||= 'production' ENV['NO_RELOAD'] ||= '1' $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" require 'action_controller' require 'action_controller/new_base' if ENV['NEW'] require 'benchmark' @@ -26,7 +27,7 @@ class Runner def self.run(app, n, label = nil) puts '=' * label.size, label, '=' * label.size if label - env = { 'n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout } + env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout) t = Benchmark.realtime { new(app).call(env) } puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n] puts @@ -37,9 +38,27 @@ end N = (ENV['N'] || 1000).to_i class BasePostController < ActionController::Base + append_view_path "#{File.dirname(__FILE__)}/views" + def index render :text => '' end + + def partial + render :partial => "/partial" + end + + def many_partials + render :partial => "/many_partials" + end + + def partial_collection + render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10] + end + + def show_template + render :template => "template" + end end OK = [200, {}, []] @@ -51,6 +70,10 @@ class HttpPostController < ActionController::Metal end end -Runner.run(MetalPostController, N, 'metal') -Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController -Runner.run(BasePostController.action(:index), N, 'base') +# Runner.run(MetalPostController, N, 'metal') +# Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController +# Runner.run(BasePostController.action(:index), N, 'base') +Runner.run(BasePostController.action(:partial), N, 'partial') +Runner.run(BasePostController.action(:many_partials), N, 'many_partials') +Runner.run(BasePostController.action(:partial_collection), N, 'collection') +Runner.run(BasePostController.action(:show_template), N, 'template') diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb new file mode 100644 index 0000000000..bcfe958e2c --- /dev/null +++ b/actionpack/examples/views/_collection.erb @@ -0,0 +1 @@ +<%= collection %> \ No newline at end of file diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb new file mode 100644 index 0000000000..5ab2f8a432 --- /dev/null +++ b/actionpack/examples/views/_hello.erb @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/actionpack/examples/views/_many_partials.erb b/actionpack/examples/views/_many_partials.erb new file mode 100644 index 0000000000..7e379d46f5 --- /dev/null +++ b/actionpack/examples/views/_many_partials.erb @@ -0,0 +1,10 @@ +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> +<%= render :partial => '/hello' %> \ No newline at end of file diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb new file mode 100644 index 0000000000..3ca8e80b52 --- /dev/null +++ b/actionpack/examples/views/_partial.erb @@ -0,0 +1,10 @@ +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> +<%= "Hello" %> diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb index 3108e9ad70..5ab2f8a432 100644 --- a/actionpack/examples/views/template.html.erb +++ b/actionpack/examples/views/template.html.erb @@ -1 +1 @@ -Hello <%= @name %> \ No newline at end of file +Hello \ No newline at end of file -- cgit v1.2.3 From 930d235981c429bde8a604622393f70ec69a4985 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sat, 8 Aug 2009 22:35:00 -0300 Subject: Support a warmup for JRuby --- actionpack/examples/minimal.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index 01c64ba56b..cbfe6a427a 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -70,10 +70,9 @@ class HttpPostController < ActionController::Metal end end -# Runner.run(MetalPostController, N, 'metal') -# Runner.run(HttpPostController.action(:index), N, 'http') if defined? HttpPostController -# Runner.run(BasePostController.action(:index), N, 'base') -Runner.run(BasePostController.action(:partial), N, 'partial') -Runner.run(BasePostController.action(:many_partials), N, 'many_partials') -Runner.run(BasePostController.action(:partial_collection), N, 'collection') -Runner.run(BasePostController.action(:show_template), N, 'template') +(ENV["M"] || 1).to_i.times do + Runner.run(BasePostController.action(:partial), N, 'partial') + Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + Runner.run(BasePostController.action(:partial_collection), N, 'collection') + Runner.run(BasePostController.action(:show_template), N, 'template') +end \ No newline at end of file -- cgit v1.2.3 From 0ab40b039bf7b7882a31ab187916bc2dc5a8ae7c Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sat, 8 Aug 2009 23:43:45 -0300 Subject: Went from 25% slower partials (vs. 2.3) to 10% faster. More to come. --- actionpack/examples/minimal.rb | 19 ++++++++++++++----- actionpack/lib/action_view/render/partials.rb | 10 +++++++--- actionpack/lib/action_view/template/template.rb | 4 ++-- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index cbfe6a427a..bf36f518bd 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -70,9 +70,18 @@ class HttpPostController < ActionController::Metal end end -(ENV["M"] || 1).to_i.times do - Runner.run(BasePostController.action(:partial), N, 'partial') - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - Runner.run(BasePostController.action(:partial_collection), N, 'collection') - Runner.run(BasePostController.action(:show_template), N, 'template') +unless ENV["PROFILE"] + (ENV["M"] || 1).to_i.times do + Runner.run(BasePostController.action(:partial), N, 'partial') + Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + Runner.run(BasePostController.action(:partial_collection), N, 'collection') + Runner.run(BasePostController.action(:show_template), N, 'template') + end +else + require "ruby-prof" + RubyProf.start + Runner.run(BasePostController.action(:many_partials), N, 'partial') + result = RubyProf.stop + printer = RubyProf::CallStackPrinter.new(result) + printer.print(File.open("output.html", "w"), :min_percent => 2) end \ No newline at end of file diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index e7d03c81e8..7a8c943b05 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -177,6 +177,10 @@ module ActionView @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end + def self.formats + @formats ||= Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {}}} + end + def initialize(view_context, options, block) partial = options[:partial] @@ -187,8 +191,8 @@ module ActionView # Set up some instance variables to speed up memoizing @partial_names = self.class.partial_names[@view.controller.class] - @templates = Hash.new {|h,k| h[k] = {}} - @formats_hash = view_context.formats.hash + @templates = self.class.formats + @details_hash = [view_context.formats, I18n.locale].hash # Set up the object and path @object = partial.is_a?(String) ? options[:object] : partial @@ -248,7 +252,7 @@ module ActionView def find_template(path = @path) return if !path - @templates[path][@formats_hash] ||= begin + @templates[@details_hash][path][@view.controller_path] ||= begin prefix = @view.controller.controller_path unless path.include?(?/) @view.find(path, {:formats => @view.formats}, prefix, true) end diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index 4145045e2d..abe310b758 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -30,12 +30,12 @@ module ActionView # TODO: Figure out how to abstract this def variable_name - identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym + @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym end # TODO: Figure out how to abstract this def counter_name - "#{variable_name}_counter".to_sym + @counter_name ||= "#{variable_name}_counter".to_sym end # TODO: kill hax -- cgit v1.2.3 From 33f01fb1f6e0bf850e9366ef8203c4c944c27540 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 01:37:03 -0300 Subject: Cache some more things to improve partial perf --- actionpack/lib/action_view/render/partials.rb | 6 +++--- actionpack/lib/action_view/template/template.rb | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 7a8c943b05..64f08c447d 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -178,7 +178,7 @@ module ActionView end def self.formats - @formats ||= Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {}}} + @formats ||= Hash.new {|h,k| h[k] = Hash.new{|h,k| h[k] = Hash.new {|h,k| h[k] = {}}}} end def initialize(view_context, options, block) @@ -192,7 +192,7 @@ module ActionView # Set up some instance variables to speed up memoizing @partial_names = self.class.partial_names[@view.controller.class] @templates = self.class.formats - @details_hash = [view_context.formats, I18n.locale].hash + @format = view_context.formats # Set up the object and path @object = partial.is_a?(String) ? options[:object] : partial @@ -252,7 +252,7 @@ module ActionView def find_template(path = @path) return if !path - @templates[@details_hash][path][@view.controller_path] ||= begin + @templates[path][@view.controller_path][@format][I18n.locale] ||= begin prefix = @view.controller.controller_path unless path.include?(?/) @view.find(path, {:formats => @view.formats}, prefix, true) end diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index abe310b758..33d3f79ad3 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -7,19 +7,22 @@ require "action_view/template/resolver" module ActionView class Template extend TemplateHandlers - attr_reader :source, :identifier, :handler, :mime_type, :details + attr_reader :source, :identifier, :handler, :mime_type, :formats, :details def initialize(source, identifier, handler, details) @source = source @identifier = identifier @handler = handler @details = details + @method_names = {} format = details.delete(:format) || begin # TODO: Clean this up handler.respond_to?(:default_format) ? handler.default_format.to_sym.to_s : "html" end @mime_type = Mime::Type.lookup_by_extension(format.to_s) + @formats = [format.to_sym] + @formats << :html if format == :js @details[:formats] = Array.wrap(format.to_sym) end @@ -90,7 +93,8 @@ module ActionView def build_method_name(locals) # TODO: is locals.keys.hash reliably the same? - "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + @method_names[locals.keys.hash] ||= + "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") end end end -- cgit v1.2.3 From 964bc4e85517dbd45d86e91445b3b9aecde960d8 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 01:45:52 -0300 Subject: Rendering a template from ActionView will default to looking for partials only in the current mime type. * The old behavior was tested only as a side-effect of a different test--the original tests remain; a new template in the XML mime was added. * If you are relying on the current behavior and object to this change, please participate in http://groups.google.com/group/rubyonrails-core/browse_thread/thread/6ef25f3c108389bd --- actionpack/lib/action_view/base.rb | 2 +- actionpack/lib/action_view/template/text.rb | 4 +++- actionpack/test/fixtures/test/greeting.xml.erb | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 actionpack/test/fixtures/test/greeting.xml.erb diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7932f01ebb..d1cd506dde 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -248,7 +248,7 @@ module ActionView #:nodoc: def with_template(current_template) _evaluate_assigns_and_ivars last_template, self.template = template, current_template - last_formats, self.formats = formats, [current_template.mime_type.to_sym] + Mime::SET.symbols + last_formats, self.formats = formats, current_template.formats yield ensure self.template, self.formats = last_template, last_formats diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 81944ff546..9f12e5e0a8 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -15,7 +15,9 @@ module ActionView #:nodoc: def render(*) self end def mime_type() @content_type end - + + def formats() [mime_type] end + def partial?() false end end end diff --git a/actionpack/test/fixtures/test/greeting.xml.erb b/actionpack/test/fixtures/test/greeting.xml.erb new file mode 100644 index 0000000000..62fb0293f0 --- /dev/null +++ b/actionpack/test/fixtures/test/greeting.xml.erb @@ -0,0 +1 @@ +<p>This is grand!</p> -- cgit v1.2.3 From e28e0611652e4d2c4fc2e8afdf866264c1583154 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:05:04 -0300 Subject: Use response_body rather than performed? --- actionpack/lib/action_controller/base.rb | 2 +- actionpack/test/template/body_parts_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 61f1c715c8..698189bd46 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -41,7 +41,7 @@ module ActionController module ImplicitRender def send_action(*) ret = super - default_render unless performed? + default_render unless response_body ret end diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb index bac67c1a7d..defe85107e 100644 --- a/actionpack/test/template/body_parts_test.rb +++ b/actionpack/test/template/body_parts_test.rb @@ -4,7 +4,7 @@ class BodyPartsTest < ActionController::TestCase RENDERINGS = [Object.new, Object.new, Object.new] class TestController < ActionController::Base - def performed?() true end + def response_body() "" end def index RENDERINGS.each do |rendering| -- cgit v1.2.3 From 10eaba8f412d3acbdb6a57e1cd457e074c127953 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:06:08 -0300 Subject: Cache controller_path on the AV instance to avoid needing to make additional calls back into the controller for each attempt (this was done because these calls were adding up significantly in partial rendering and showing up on profiles) --- actionpack/lib/action_view/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index d1cd506dde..c3361c9367 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -195,7 +195,9 @@ module ActionView #:nodoc: attr_internal :request, :layout - delegate :controller_path, :to => :controller, :allow_nil => true + def controller_path + @controller_path ||= controller && controller.controller_path + end delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, :flash, :action_name, :controller_name, :to => :controller -- cgit v1.2.3 From e58b2769cf2c4df348d4ac31a8c5497cbd545564 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:28:46 -0300 Subject: Experimental: Improve performance of ActionView by preventing method cache flushing due to runtime Kernel#extend: * The helper module adds a new _helper_serial property onto AbstractController subclasses * When #helper is used to add helpers to a class, the serial number is updated * An ActionView subclass is created and cached based on this serial number. * That subclass includes the helper module from the controller * Subsequent requests using the same controller with the same serial will result in reusing that subclass, rather than being forced to take an action (like include or extend) that will result in a global method cache flush on MRI and a flush of the entire AV class' cache on JRuby. * For now, this optimization is not applied to the RJS helpers, which results in a global method cache flush in MRI and a flush of the JavaScriptGenerator class in JRuby only when using RJS. * Since the effects are limited to using RJS, and would only affect JavaScriptGenerator in JRuby (as opposed to the entire view object), it seems worthwhile to apply this now. * This resulted in a significant performance improvement. I will have benchmarks in the next day or two that show the performance impact of the last several commits. * There is a small chance this could break existing code (although I'm not sure how). If that happens, please report it immediately. --- actionpack/lib/abstract_controller/helpers.rb | 10 ++++++++ actionpack/lib/action_view/base.rb | 34 +++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 04eaa02441..f3072fad74 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -4,8 +4,16 @@ module AbstractController include RenderingController + def self.next_serial + @helper_serial ||= 0 + @helper_serial += 1 + end + included do extlib_inheritable_accessor(:_helpers) { Module.new } + extlib_inheritable_accessor(:_helper_serial) do + AbstractController::Helpers.next_serial + end end module ClassMethods @@ -58,6 +66,8 @@ module AbstractController # of the helper module. Any methods defined in the block # will be helpers. def helper(*args, &block) + self._helper_serial = AbstractController::Helpers.next_serial + 1 + args.flatten.each do |arg| case arg when Module diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index c3361c9367..c171a5a8f5 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -164,6 +164,9 @@ module ActionView #:nodoc: # # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. class Base + module Subclasses + end + include Helpers, Rendering, Partials, ::ERB::Util extend ActiveSupport::Memoizable @@ -212,30 +215,35 @@ module ActionView #:nodoc: ActionView::PathSet.new(Array(value)) end + extlib_inheritable_accessor :helpers attr_reader :helpers - class ProxyModule < Module - def initialize(receiver) - @receiver = receiver - end + def self.for_controller(controller) + @views ||= {} - def include(*args) - super(*args) - @receiver.extend(*args) - end - end + # TODO: Decouple this so helpers are a separate concern in AV just like + # they are in AC. + if controller.class.respond_to?(:_helper_serial) + klass = @views[controller.class._helper_serial] ||= Class.new(self) do + Subclasses.const_set(controller.class.name.gsub(/::/, '__'), self) - def self.for_controller(controller) - new(controller.class.view_paths, {}, controller).tap do |view| - view.helpers.include(controller._helpers) if controller.respond_to?(:_helpers) + if controller.respond_to?(:_helpers) + include controller._helpers + self.helpers = controller._helpers + end + end + else + klass = self end + + klass.new(controller.class.view_paths, {}, controller) end def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil, formats = nil)#:nodoc: @formats = formats || [:html] @assigns = assigns_for_first_render.each { |key, value| instance_variable_set("@#{key}", value) } @controller = controller - @helpers = ProxyModule.new(self) + @helpers = self.class.helpers || Module.new @_content_for = Hash.new {|h,k| h[k] = "" } self.view_paths = view_paths end -- cgit v1.2.3 From b05c95190737eb39047a756e37d811b340a47ba2 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:29:56 -0300 Subject: Temporary fix to get our LoadError monkey-patch working with newer JRuby. We should probably remove MissingSourceFile and just monkey-patch LoadError instead of overriding LoadError.new. --- activesupport/lib/active_support/core_ext/load_error.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index f36a21818f..cc6287b100 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -20,7 +20,8 @@ class MissingSourceFile < LoadError #:nodoc: REGEXPS = [ [/^no such file to load -- (.+)$/i, 1], [/^Missing \w+ (file\s*)?([^\s]+.rb)$/i, 2], - [/^Missing API definition file in (.+)$/i, 1] + [/^Missing API definition file in (.+)$/i, 1], + [/win32/, 0] ] unless defined?(REGEXPS) end -- cgit v1.2.3 From e8849203dc68d1a2fabf5e6b51b379ad1c51f8c8 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:30:10 -0300 Subject: Updates to benchmark harness. --- actionpack/examples/minimal.rb | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index bf36f518bd..2697d1f391 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -6,6 +6,7 @@ $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" require 'action_controller' require 'action_controller/new_base' if ENV['NEW'] +require 'action_view' require 'benchmark' class Runner @@ -37,9 +38,19 @@ end N = (ENV['N'] || 1000).to_i +module ActionController::Rails2Compatibility + instance_methods.each do |name| + remove_method name + end +end + class BasePostController < ActionController::Base append_view_path "#{File.dirname(__FILE__)}/views" + def overhead + self.response_body = '' + end + def index render :text => '' end @@ -71,17 +82,27 @@ class HttpPostController < ActionController::Metal end unless ENV["PROFILE"] + Runner.run(BasePostController.action(:overhead), N, 'overhead') + Runner.run(BasePostController.action(:index), N, 'index') + Runner.run(BasePostController.action(:partial), N, 'partial') + Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + Runner.run(BasePostController.action(:partial_collection), N, 'collection') + Runner.run(BasePostController.action(:show_template), N, 'template') + (ENV["M"] || 1).to_i.times do + Runner.run(BasePostController.action(:overhead), N, 'overhead') + Runner.run(BasePostController.action(:index), N, 'index') Runner.run(BasePostController.action(:partial), N, 'partial') Runner.run(BasePostController.action(:many_partials), N, 'many_partials') Runner.run(BasePostController.action(:partial_collection), N, 'collection') Runner.run(BasePostController.action(:show_template), N, 'template') end else + Runner.run(BasePostController.action(:many_partials), N, 'many_partials') require "ruby-prof" RubyProf.start - Runner.run(BasePostController.action(:many_partials), N, 'partial') + Runner.run(BasePostController.action(:many_partials), N, 'many_partials') result = RubyProf.stop printer = RubyProf::CallStackPrinter.new(result) - printer.print(File.open("output.html", "w"), :min_percent => 2) + printer.print(File.open("output.html", "w")) end \ No newline at end of file -- cgit v1.2.3 From 0d539947017e0ba04601889e2a3e01a64bcadf69 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 03:50:34 -0300 Subject: Make bench harness produce output that is easier to compare --- actionpack/examples/minimal.rb | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index 2697d1f391..a9015da053 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -10,8 +10,12 @@ require 'action_view' require 'benchmark' class Runner - def initialize(app) - @app = app + def initialize(app, output) + @app, @output = app, output + end + + def puts(*) + super if @output end def call(env) @@ -20,16 +24,22 @@ class Runner end def report(env, response) + return unless ENV["DEBUG"] out = env['rack.errors'] out.puts response[0], response[1].to_yaml, '---' response[2].each { |part| out.puts part } out.puts '---' end - def self.run(app, n, label = nil) - puts '=' * label.size, label, '=' * label.size if label + def self.puts(*) + super if @output + end + + def self.run(app, n, label, output = true) + @output = output + puts label, '=' * label.size if label env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout) - t = Benchmark.realtime { new(app).call(env) } + t = Benchmark.realtime { new(app, output).call(env) } puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n] puts end @@ -82,12 +92,12 @@ class HttpPostController < ActionController::Metal end unless ENV["PROFILE"] - Runner.run(BasePostController.action(:overhead), N, 'overhead') - Runner.run(BasePostController.action(:index), N, 'index') - Runner.run(BasePostController.action(:partial), N, 'partial') - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - Runner.run(BasePostController.action(:partial_collection), N, 'collection') - Runner.run(BasePostController.action(:show_template), N, 'template') + Runner.run(BasePostController.action(:overhead), N, 'overhead', false) + Runner.run(BasePostController.action(:index), N, 'index', false) + Runner.run(BasePostController.action(:partial), N, 'partial', false) + Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false) + Runner.run(BasePostController.action(:partial_collection), N, 'collection', false) + Runner.run(BasePostController.action(:show_template), N, 'template', false) (ENV["M"] || 1).to_i.times do Runner.run(BasePostController.action(:overhead), N, 'overhead') -- cgit v1.2.3 From 9bb8ef9edebf5c27b8a1c67ca3776d52afbc1dc4 Mon Sep 17 00:00:00 2001 From: Rich Bradley <richbradley@gmail.com> Date: Sat, 8 Aug 2009 23:29:38 -0700 Subject: Fix for nested :include with namespaced models. [#260 state:committed] --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/test/cases/modules_test.rb | 13 +++++++++++++ activerecord/test/models/company_in_module.rb | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 66aa9332c8..cce7777039 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1912,7 +1912,7 @@ module ActiveRecord descendant end.flatten.compact - remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty? + remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 283333fc04..8360416aa2 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -36,4 +36,17 @@ class ModulesTest < ActiveRecord::TestCase assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass' assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end + + def test_eager_loading_in_modules + # need to add an eager loading condition to force the eager loading model into + # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 + client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + + [client_join_loaded, client_sequential_loaded].each do |client| + assert_no_queries do + assert_not_nil(client.firm.account) + end + end + end end diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 8b84c2fb5e..cdda7a44d4 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -13,7 +13,7 @@ module MyApplication has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}' - has_one :account, :dependent => :destroy + has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy end class Client < Company -- cgit v1.2.3 From 60219a13da9899b97f9b2ff4ecb7a9508aeea662 Mon Sep 17 00:00:00 2001 From: Yehuda Katz <wycats@gmail.com> Date: Sun, 9 Aug 2009 05:52:49 -0300 Subject: Add a .tmp path --- railties/lib/rails/configuration.rb | 3 ++- railties/test/initializer/path_test.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index fe3cb67d3a..5cc4f80684 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -71,7 +71,8 @@ module Rails @paths.lib "lib", :load_path => true @paths.vendor "vendor", :load_path => true @paths.vendor.plugins "vendor/plugins" - @paths.cache "tmp/cache" + @paths.tmp "tmp" + @paths.tmp.cache "tmp/cache" @paths.config "config" @paths.config.locales "config/locales" @paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb" diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb index 1b73cdc73e..8de3161546 100644 --- a/railties/test/initializer/path_test.rb +++ b/railties/test/initializer/path_test.rb @@ -30,7 +30,8 @@ class PathsTest < Test::Unit::TestCase assert_path @paths.lib, "lib" assert_path @paths.vendor, "vendor" assert_path @paths.vendor.plugins, "vendor", "plugins" - assert_path @paths.cache, "tmp", "cache" + assert_path @paths.tmp, "tmp" + assert_path @paths.tmp.cache, "tmp", "cache" assert_path @paths.config, "config" assert_path @paths.config.locales, "config", "locales" assert_path @paths.config.environments, "config", "environments" -- cgit v1.2.3 From a0caad5255ed120192755fce10960a38b53c056d Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 02:24:35 -0700 Subject: Setting connection timeout also affects Net::HTTP open_timeout. [#2947 state:resolved] --- activeresource/lib/active_resource/connection.rb | 26 +++++++++++++++++------- activeresource/test/connection_test.rb | 11 ++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index b29e379c53..ef57c1f8b2 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -137,15 +137,27 @@ module ActiveResource # Creates new Net::HTTP instance for communication with the # remote service and resources. def http - http = - if @proxy - Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password) - else - Net::HTTP.new(@site.host, @site.port) - end + configure_http(new_http) + end + + def new_http + if @proxy + Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password) + else + Net::HTTP.new(@site.host, @site.port) + end + end + + def configure_http(http) http.use_ssl = @site.is_a?(URI::HTTPS) http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? - http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used. + + # Net::HTTP timeouts default to 60 seconds. + if @timeout + http.open_timeout = @timeout + http.read_timeout = @timeout + end + http end diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 60a8204533..12e8058b0c 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -185,6 +185,17 @@ class ConnectionTest < Test::Unit::TestCase assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') } end + def test_setting_timeout + http = Net::HTTP.new('') + + [10, 20].each do |timeout| + @conn.timeout = timeout + @conn.send(:configure_http, http) + assert_equal timeout, http.open_timeout + assert_equal timeout, http.read_timeout + end + end + def test_accept_http_header @http = mock('new Net::HTTP') @conn.expects(:http).returns(@http) -- cgit v1.2.3 From 22b38c18c61a18babd671b973e804e054ba795d4 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <hugo.peixoto@gmail.com> Date: Sun, 9 Aug 2009 11:24:40 +0100 Subject: Fixed generating a namespaced model with table pluralization turned off. Add tests for namespaced model generation. [#767 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../active_record/model/model_generator.rb | 4 +- railties/lib/generators/named_base.rb | 3 +- railties/test/generators/model_generator_test.rb | 44 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/railties/lib/generators/active_record/model/model_generator.rb b/railties/lib/generators/active_record/model/model_generator.rb index 54187aede0..5c6033629f 100644 --- a/railties/lib/generators/active_record/model/model_generator.rb +++ b/railties/lib/generators/active_record/model/model_generator.rb @@ -13,7 +13,9 @@ module ActiveRecord def create_migration_file if options[:migration] && options[:parent].nil? - file_name = "create_#{file_path.gsub(/\//, '_').pluralize}" + klass_name = file_path.gsub(/\//, '_') + klass_name = klass_name.pluralize if ActiveRecord::Base.pluralize_table_names + file_name = "create_#{klass_name}" migration_template "migration.rb", "db/migrate/#{file_name}.rb" end end diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb index 9632e6806c..c2b958e311 100644 --- a/railties/lib/generators/named_base.rb +++ b/railties/lib/generators/named_base.rb @@ -28,7 +28,6 @@ module Rails else singular_name end - @table_name.gsub! '/', '_' if class_nesting.empty? @class_name = class_name_without_nesting @@ -36,6 +35,8 @@ module Rails @table_name = class_nesting.underscore << "_" << @table_name @class_name = "#{class_nesting}::#{class_name_without_nesting}" end + + @table_name.gsub! '/', '_' end # Convert attributes hash into an array with GeneratedAttribute objects. diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index a9b772d67b..501c7d10c6 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -31,6 +31,50 @@ class ModelGeneratorTest < GeneratorsTestCase assert_migration "db/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration/ end + def test_migration_with_namespace + run_generator ["Gallery::Image"] + assert_migration "db/migrate/create_gallery_images", /class CreateGalleryImages < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_images" + end + + def test_migration_with_nested_namespace + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_migration "db/migrate/create_admin_gallery_images", /class CreateAdminGalleryImages < ActiveRecord::Migration/ + assert_migration "db/migrate/create_admin_gallery_images", /create_table :admin_gallery_images/ + end + + def test_migration_with_nested_namespace_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator ["Admin::Gallery::Image"] + assert_no_migration "db/migrate/create_images" + assert_no_migration "db/migrate/create_gallery_images" + assert_no_migration "db/migrate/create_admin_gallery_images" + assert_migration "db/migrate/create_admin_gallery_image", /class CreateAdminGalleryImage < ActiveRecord::Migration/ + assert_migration "db/migrate/create_admin_gallery_image", /create_table :admin_gallery_image/ + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_with_namespaces_in_model_name_without_plurization + ActiveRecord::Base.pluralize_table_names = false + run_generator ["Gallery::Image"] + assert_migration "db/migrate/create_gallery_image", /class CreateGalleryImage < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_gallery_images" + ensure + ActiveRecord::Base.pluralize_table_names = true + end + + def test_migration_without_pluralization + ActiveRecord::Base.pluralize_table_names = false + run_generator + assert_migration "db/migrate/create_account", /class CreateAccount < ActiveRecord::Migration/ + assert_no_migration "db/migrate/create_accounts" + ensure + ActiveRecord::Base.pluralize_table_names = true + end + def test_migration_is_skipped run_generator ["account", "--no-migration"] assert_no_migration "db/migrate/create_accounts.rb" -- cgit v1.2.3 From f16008afddaf7d80d6bac0ace380e864b78106fe Mon Sep 17 00:00:00 2001 From: Dmitry Ratnikov <ratnikov@gmail.com> Date: Sun, 9 Aug 2009 03:35:45 -0500 Subject: Modified Rich Bradley's test-case to fail as part of suite and with a reasonable message Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/test/cases/modules_test.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 8360416aa2..9c44bea74f 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -4,6 +4,16 @@ require 'models/company_in_module' class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers + def setup + # need to make sure Object::Firm is not defined, so that constantize will not be able to cheat when having to load namespaced classes + @firm_const = Object.send(:remove_const, :Firm) if Object.const_defined?(:Firm) + end + + def teardown + # reinstate the Object::Firm constant for further tests + Object.send :const_set, :Firm, @firm_const unless @firm_const.nil? + end + def test_module_spanning_associations firm = MyApplication::Business::Firm.find(:first) assert !firm.clients.empty?, "Firm should have clients" @@ -40,8 +50,12 @@ class ModulesTest < ActiveRecord::TestCase def test_eager_loading_in_modules # need to add an eager loading condition to force the eager loading model into # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 - client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + begin + client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + rescue NameError => nE + flunk "Should be able to resolve all classes via reflections" + end [client_join_loaded, client_sequential_loaded].each do |client| assert_no_queries do -- cgit v1.2.3 From 314ba0433f03b66022ad41d55cc75d2bd9809fe3 Mon Sep 17 00:00:00 2001 From: Dmitry Ratnikov <ratnikov@gmail.com> Date: Sun, 9 Aug 2009 03:48:49 -0500 Subject: Changed to use klass instead of constantizing in assign_ids generated method [#260 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/test/cases/modules_test.rb | 39 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index cce7777039..7f299b2aa5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1367,7 +1367,7 @@ module ActiveRecord define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) + send("#{reflection.name}=", reflection.klass.find(ids)) end end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 9c44bea74f..4f559bcaa5 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -5,13 +5,20 @@ class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers def setup - # need to make sure Object::Firm is not defined, so that constantize will not be able to cheat when having to load namespaced classes - @firm_const = Object.send(:remove_const, :Firm) if Object.const_defined?(:Firm) + # need to make sure Object::Firm and Object::Client are not defined, + # so that constantize will not be able to cheat when having to load namespaced classes + @undefined_consts = {} + + [:Firm, :Client].each do |const| + @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const) + end end def teardown - # reinstate the Object::Firm constant for further tests - Object.send :const_set, :Firm, @firm_const unless @firm_const.nil? + # reinstate the constants that we undefined in the setup + @undefined_consts.each do |constant, value| + Object.send :const_set, constant, value unless value.nil? + end end def test_module_spanning_associations @@ -47,17 +54,25 @@ class ModulesTest < ActiveRecord::TestCase assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end + def test_assign_ids + firm = MyApplication::Business::Firm.first + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + firm.client_ids = [MyApplication::Business::Client.first.id] + end + end + + # need to add an eager loading condition to force the eager loading model into + # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 def test_eager_loading_in_modules - # need to add an eager loading condition to force the eager loading model into - # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 - begin - client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) - rescue NameError => nE - flunk "Should be able to resolve all classes via reflections" + clients = [] + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) end - [client_join_loaded, client_sequential_loaded].each do |client| + clients.each do |client| assert_no_queries do assert_not_nil(client.firm.account) end -- cgit v1.2.3 From 32bde66aa67a95a532ed68bbc71a0e9cd5dd4ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 11:40:43 +0200 Subject: Make http digest work with different server/browser combinations Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../action_controller/metal/http_authentication.rb | 3 +- .../controller/http_digest_authentication_test.rb | 35 ++++++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 5ebe1ee048..2b62a1be85 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -197,9 +197,10 @@ module ActionController return false unless password method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + uri = credentials[:uri][0,1] == '/' ? request.request_uri : request.url [true, false].any? do |password_is_ha1| - expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected = expected_response(method, uri, credentials, password, password_is_ha1) expected == credentials[:response] end end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 58f3b88075..7e9a2625f1 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -136,7 +136,7 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_equal 'Definitely Maybe', @response.body end - test "authentication request with request-uri that doesn't match credentials digest-uri" do + test "authentication request with request-uri that doesn't match credentials digest-uri" do @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') @request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri" get :display @@ -145,10 +145,33 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_equal "Authentication Failed", @response.body end - test "authentication request with absolute uri" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display", + test "authentication request with absolute request uri (as in webrick)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with absolute uri in credentials (as in IE)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", :username => 'pretty', :password => 'please') - @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display" + + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with absolute uri in both request and credentials (as in Webrick with IE)" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:url => "http://test.host/http_digest_authentication_test/dummy_digest", + :username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest" + get :display assert_response :success @@ -202,11 +225,11 @@ class HttpDigestAuthenticationTest < ActionController::TestCase credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}") + credentials.merge!(:uri => @request.env['REQUEST_URI'].to_s) ActionController::HttpAuthentication::Digest.encode_credentials(method, credentials, password, options[:password_is_ha1]) end def decode_credentials(header) ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate']) end -end \ No newline at end of file +end -- cgit v1.2.3 From d811864e880580549bc1ab73a1ab0be886598e6e Mon Sep 17 00:00:00 2001 From: Michael Siebert <siebertm85@googlemail.com> Date: Thu, 25 Jun 2009 00:59:58 +0200 Subject: Fix deprecating =-methods by using send [#2431 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- .../lib/active_support/deprecation/method_wrappers.rb | 18 +++++++++--------- activesupport/test/deprecation_test.rb | 4 ++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index b9eb539aa7..deb29a82b8 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -11,15 +11,15 @@ module ActiveSupport method_names.each do |method_name| target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| target_module.module_eval(<<-end_eval, __FILE__, __LINE__ + 1) - def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block) - ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn( - ::ActiveSupport::Deprecation.deprecated_method_warning( # ::ActiveSupport::Deprecation.deprecated_method_warning( - :#{method_name}, # :generate_secret, - #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"), - caller # caller - ) # ) - #{target}_without_deprecation#{punctuation}(*args, &block) # generate_secret_without_deprecation(*args, &block) - end # end + def #{target}_with_deprecation#{punctuation}(*args, &block) # def generate_secret_with_deprecation(*args, &block) + ::ActiveSupport::Deprecation.warn( # ::ActiveSupport::Deprecation.warn( + ::ActiveSupport::Deprecation.deprecated_method_warning( # ::ActiveSupport::Deprecation.deprecated_method_warning( + :#{method_name}, # :generate_secret, + #{options[method_name].inspect}), # "You should use ActiveSupport::SecureRandom.hex(64)"), + caller # caller + ) # ) + send(:#{target}_without_deprecation#{punctuation}, *args, &block) # send(:generate_secret_without_deprecation, *args, &block) + end # end end_eval end end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 73a1f9959c..a3ae39d071 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -25,6 +25,9 @@ class Deprecatee def e; end deprecate :a, :b, :c => :e, :d => "you now need to do something extra for this one" + def f=(v); end + deprecate :f= + module B C = 1 end @@ -133,6 +136,7 @@ class DeprecationTest < ActiveSupport::TestCase def test_deprecation_without_explanation assert_deprecated { @dtc.a } assert_deprecated { @dtc.b } + assert_deprecated { @dtc.f = :foo } end def test_deprecation_with_alternate_method -- cgit v1.2.3 From 1bd4d1c67459a91415ee73a8f55d2309c0d62a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 13:22:06 +0200 Subject: Optimize Range#sum to use arithmetic progression when a block is not given [#2489]. Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activesupport/lib/active_support/core_ext/enumerable.rb | 9 +++++++++ activesupport/test/core_ext/enumerable_test.rb | 2 ++ 2 files changed, 11 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index e89b7e392e..d68eef8c23 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -111,3 +111,12 @@ module Enumerable !any?(&block) end unless [].respond_to?(:none?) end + +class Range #:nodoc: + # Optimize range sum to use arithmetic progression if a block is not given. + def sum(identity=0, &block) + return super if block_given? + actual_last = exclude_end? ? (last - 1) : last + (actual_last - first + 1) * (actual_last + first) / 2 + end +end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 1927a0ad0d..6512754e53 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -61,7 +61,9 @@ class EnumerableTests < Test::Unit::TestCase end def test_enumerable_sums + assert_equal 20, (1..4).sum { |i| i * 2 } assert_equal 10, (1..4).sum + assert_equal 6, (1...4).sum end def test_each_with_object -- cgit v1.2.3 From 3545d6b0b227cc9ee4a645097bdbd43b33b42a87 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <hugo.peixoto@gmail.com> Date: Sun, 9 Aug 2009 15:19:56 +0200 Subject: Setting usec (and nsec for Ruby 1.9) on Time#end_of_* methods [#1255 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- .../active_support/core_ext/time/calculations.rb | 16 ++++---- activesupport/test/core_ext/date_ext_test.rb | 2 +- activesupport/test/core_ext/time_ext_test.rb | 46 +++++++++++----------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index b73c3b2c9b..4f3b869f50 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -158,7 +158,7 @@ class Time alias :monday :beginning_of_week alias :at_beginning_of_week :beginning_of_week - # Returns a new Time representing the end of this week (Sunday, 23:59:59) + # Returns a new Time representing the end of this week, (end of Sunday) def end_of_week days_to_sunday = wday!=0 ? 7-wday : 0 (self + days_to_sunday.days).end_of_day @@ -178,9 +178,9 @@ class Time alias :at_midnight :beginning_of_day alias :at_beginning_of_day :beginning_of_day - # Returns a new Time representing the end of the day (23:59:59) + # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) def end_of_day - change(:hour => 23, :min => 59, :sec => 59) + change(:hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end # Returns a new Time representing the start of the month (1st of the month, 0:00) @@ -190,11 +190,11 @@ class Time end alias :at_beginning_of_month :beginning_of_month - # Returns a new Time representing the end of the month (last day of the month, 0:00) + # Returns a new Time representing the end of the month (end of the last day of the month) def end_of_month #self - ((self.mday-1).days + self.seconds_since_midnight) last_day = ::Time.days_in_month(month, year) - change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 0) + change(:day => last_day, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end alias :at_end_of_month :end_of_month @@ -204,7 +204,7 @@ class Time end alias :at_beginning_of_quarter :beginning_of_quarter - # Returns a new Time representing the end of the quarter (last day of march, june, september, december, 23:59:59) + # Returns a new Time representing the end of the quarter (end of the last day of march, june, september, december) def end_of_quarter beginning_of_month.change(:month => [3, 6, 9, 12].detect { |m| m >= month }).end_of_month end @@ -216,9 +216,9 @@ class Time end alias :at_beginning_of_year :beginning_of_year - # Returns a new Time representing the end of the year (31st of december, 23:59:59) + # Returns a new Time representing the end of the year (end of the 31st of december) def end_of_year - change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59) + change(:month => 12, :day => 31, :hour => 23, :min => 59, :sec => 59, :usec => 999999.999) end alias :at_end_of_year :end_of_year diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 7fd551eaf3..8a7bae5fc6 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -196,7 +196,7 @@ class DateExtCalculationsTest < Test::Unit::TestCase end def test_end_of_day - assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day + assert_equal Time.local(2005,2,21,23,59,59,999999.999), Date.new(2005,2,21).end_of_day end def test_xmlschema diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 1c2d0fbce4..f6003bc083 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -85,45 +85,45 @@ class TimeExtCalculationsTest < Test::Unit::TestCase end def test_end_of_day - assert_equal Time.local(2007,8,12,23,59,59), Time.local(2007,8,12,10,10,10).end_of_day + assert_equal Time.local(2007,8,12,23,59,59,999999.999), Time.local(2007,8,12,10,10,10).end_of_day with_env_tz 'US/Eastern' do - assert_equal Time.local(2007,4,2,23,59,59), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' - assert_equal Time.local(2007,10,29,23,59,59), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2007,4,2,23,59,59,999999.999), Time.local(2007,4,2,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2007,10,29,23,59,59,999999.999), Time.local(2007,10,29,10,10,10).end_of_day, 'ends DST' end with_env_tz 'NZ' do - assert_equal Time.local(2006,3,19,23,59,59), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' - assert_equal Time.local(2006,10,1,23,59,59), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' + assert_equal Time.local(2006,3,19,23,59,59,999999.999), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' + assert_equal Time.local(2006,10,1,23,59,59,999999.999), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' end end def test_end_of_week - assert_equal Time.local(2008,1,6,23,59,59), Time.local(2007,12,31,10,10,10).end_of_week - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,27,0,0,0).end_of_week #monday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,28,0,0,0).end_of_week #tuesday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,29,0,0,0).end_of_week #wednesday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,30,0,0,0).end_of_week #thursday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,8,31,0,0,0).end_of_week #friday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,01,0,0,0).end_of_week #saturday - assert_equal Time.local(2007,9,2,23,59,59), Time.local(2007,9,02,0,0,0).end_of_week #sunday + assert_equal Time.local(2008,1,6,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_week + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,27,0,0,0).end_of_week #monday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,28,0,0,0).end_of_week #tuesday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,29,0,0,0).end_of_week #wednesday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,30,0,0,0).end_of_week #thursday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,8,31,0,0,0).end_of_week #friday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,01,0,0,0).end_of_week #saturday + assert_equal Time.local(2007,9,2,23,59,59,999999.999), Time.local(2007,9,02,0,0,0).end_of_week #sunday end def test_end_of_month - assert_equal Time.local(2005,3,31,23,59,59), Time.local(2005,3,20,10,10,10).end_of_month - assert_equal Time.local(2005,2,28,23,59,59), Time.local(2005,2,20,10,10,10).end_of_month - assert_equal Time.local(2005,4,30,23,59,59), Time.local(2005,4,20,10,10,10).end_of_month + assert_equal Time.local(2005,3,31,23,59,59,999999.999), Time.local(2005,3,20,10,10,10).end_of_month + assert_equal Time.local(2005,2,28,23,59,59,999999.999), Time.local(2005,2,20,10,10,10).end_of_month + assert_equal Time.local(2005,4,30,23,59,59,999999.999), Time.local(2005,4,20,10,10,10).end_of_month end def test_end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,2,15,10,10,10).end_of_quarter - assert_equal Time.local(2007,3,31,23,59,59), Time.local(2007,3,31,0,0,0).end_of_quarter - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,21,10,10,10).end_of_quarter - assert_equal Time.local(2007,6,30,23,59,59), Time.local(2007,4,1,0,0,0).end_of_quarter - assert_equal Time.local(2008,6,30,23,59,59), Time.local(2008,5,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,2,15,10,10,10).end_of_quarter + assert_equal Time.local(2007,3,31,23,59,59,999999.999), Time.local(2007,3,31,0,0,0).end_of_quarter + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,21,10,10,10).end_of_quarter + assert_equal Time.local(2007,6,30,23,59,59,999999.999), Time.local(2007,4,1,0,0,0).end_of_quarter + assert_equal Time.local(2008,6,30,23,59,59,999999.999), Time.local(2008,5,31,0,0,0).end_of_quarter end def test_end_of_year - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,2,22,10,10,10).end_of_year - assert_equal Time.local(2007,12,31,23,59,59), Time.local(2007,12,31,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,2,22,10,10,10).end_of_year + assert_equal Time.local(2007,12,31,23,59,59,999999.999), Time.local(2007,12,31,10,10,10).end_of_year end def test_beginning_of_year -- cgit v1.2.3 From 1ea7a00bc4c2d28abb21a36ee09f44f5210b06f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 12:01:40 +0200 Subject: Refactored create_migration on model generator. Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- railties/lib/generators/active_record/model/model_generator.rb | 8 ++------ railties/lib/generators/named_base.rb | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/railties/lib/generators/active_record/model/model_generator.rb b/railties/lib/generators/active_record/model/model_generator.rb index 5c6033629f..2641083e0d 100644 --- a/railties/lib/generators/active_record/model/model_generator.rb +++ b/railties/lib/generators/active_record/model/model_generator.rb @@ -12,12 +12,8 @@ module ActiveRecord class_option :parent, :type => :string, :desc => "The parent class for the generated model" def create_migration_file - if options[:migration] && options[:parent].nil? - klass_name = file_path.gsub(/\//, '_') - klass_name = klass_name.pluralize if ActiveRecord::Base.pluralize_table_names - file_name = "create_#{klass_name}" - migration_template "migration.rb", "db/migrate/#{file_name}.rb" - end + return unless options[:migration] && options[:parent].nil? + migration_template "migration.rb", "db/migrate/create_#{table_name}.rb" end def create_model_file diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb index c2b958e311..cd7aa61b50 100644 --- a/railties/lib/generators/named_base.rb +++ b/railties/lib/generators/named_base.rb @@ -36,7 +36,7 @@ module Rails @class_name = "#{class_nesting}::#{class_name_without_nesting}" end - @table_name.gsub! '/', '_' + @table_name.gsub!('/', '_') end # Convert attributes hash into an array with GeneratedAttribute objects. -- cgit v1.2.3 From 96b575d6dcea6c9c4a716b4929c319695afa59f3 Mon Sep 17 00:00:00 2001 From: Pratik Naik <pratiknaik@gmail.com> Date: Sun, 9 Aug 2009 16:00:53 +0100 Subject: Get rid of parenthesize argument warnings --- actionmailer/test/mail_service_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 008ca498b1..5584afa8be 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -894,11 +894,11 @@ EOF tmp_location = ActionMailer::Base.file_settings[:location] TestMailer.deliver_cc_bcc(@recipient) - assert File.exists? tmp_location - assert File.directory? tmp_location - assert File.exists? File.join(tmp_location, @recipient) - assert File.exists? File.join(tmp_location, 'nobody@loudthinking.com') - assert File.exists? File.join(tmp_location, 'root@loudthinking.com') + assert File.exists?(tmp_location) + assert File.directory?(tmp_location) + assert File.exists?(File.join(tmp_location, @recipient)) + assert File.exists?(File.join(tmp_location, 'nobody@loudthinking.com')) + assert File.exists?(File.join(tmp_location, 'root@loudthinking.com')) end def test_recursive_multipart_processing -- cgit v1.2.3 From 7d254b5d74144a1e217125e7be21882ce380a3f8 Mon Sep 17 00:00:00 2001 From: Mike Breen <hardbap@gmail.com> Date: Sun, 9 Aug 2009 16:34:05 +0100 Subject: Serialized attributes should only be saved with partial_updates when the serialized attribute is present [#2397 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activerecord/lib/active_record/attribute_methods/dirty.rb | 2 +- activerecord/test/cases/dirty_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 9ec1fbeee1..911c908c8b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -161,7 +161,7 @@ module ActiveRecord if partial_updates? # Serialized attributes should always be written in case they've been # changed in place. - update_without_dirty(changed | self.class.serialized_attributes.keys) + update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys)) else update_without_dirty end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 1441421a80..74571d923a 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -298,6 +298,16 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present + with_partial_updates(Topic) do + Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) + topic = Topic.first(:select => 'id, author_name') + topic.update_attribute :author_name, 'John' + topic = Topic.first + assert_not_nil topic.content + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? -- cgit v1.2.3 From 7dbb2b6f83c5a1a5f4ef0a97fee5322957977306 Mon Sep 17 00:00:00 2001 From: rizwanreza <rizwanreza@gmail.com> Date: Sun, 9 Aug 2009 05:55:25 +0300 Subject: Support passing Redcloth options via textilize helper [#2973 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- actionpack/lib/action_view/helpers/text_helper.rb | 14 +++++++++++--- actionpack/test/template/text_helper_test.rb | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 34ef742a6e..1d92bcb763 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -241,12 +241,20 @@ module ActionView # # textilize("Visit the Rails website "here":http://www.rubyonrails.org/.) # # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>" - def textilize(text) + # + # textilize("This is worded <strong>strongly</strong>") + # # => "<p>This is worded <strong>strongly</strong></p>" + # + # textilize("This is worded <strong>strongly</strong>", :filter_html) + # # => "<p>This is worded <strong>strongly</strong></p>" + # + def textilize(text, *options) + options ||= [:hard_breaks] + if text.blank? "" else - textilized = RedCloth.new(text, [ :hard_breaks ]) - textilized.hard_breaks = true if textilized.respond_to?(:hard_breaks=) + textilized = RedCloth.new(text, options) textilized.to_html end end diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 706b5085f4..b7823b9394 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'testing_sandbox' +require 'redcloth' class TextHelperTest < ActionView::TestCase tests ActionView::Helpers::TextHelper @@ -528,4 +529,20 @@ class TextHelperTest < ActionView::TestCase assert_equal("red", cycle("red", "blue")) assert_equal(%w{Specialized Fuji Giant}, @cycles) end + + def test_textilize + assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!")) + end + + def test_textilize_with_blank + assert_equal("", textilize("")) + end + + def test_textilize_with_options + assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong>", :filter_html)) + end + + def test_textilize_with_hard_breaks + assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True.")) + end end -- cgit v1.2.3 From 654568e71b1ee36a04acef74b1a8ce4737050882 Mon Sep 17 00:00:00 2001 From: Felipe Talavera <felipe.talavera@gmail.com> Date: Sun, 9 Aug 2009 16:56:18 +0100 Subject: Allow to configure trusted proxies via ActionController::Base.trusted_proxies [#2126 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../lib/action_controller/metal/compatibility.rb | 2 ++ actionpack/lib/action_dispatch/http/request.rb | 4 ++-- actionpack/test/dispatch/request_test.rb | 28 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 23e7b1b3af..f94d1c669c 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -64,6 +64,8 @@ module ActionController cattr_accessor :ip_spoofing_check self.ip_spoofing_check = true + + cattr_accessor :trusted_proxies end # For old tests diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 5f9463eb91..4190fa21cd 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -246,7 +246,7 @@ module ActionDispatch remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/) unless remote_addr_list.blank? - not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES} + not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES || addr =~ ActionController::Base.trusted_proxies} return not_trusted_addrs.first unless not_trusted_addrs.empty? end remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',') @@ -265,7 +265,7 @@ EOM end if remote_ips - while remote_ips.size > 1 && TRUSTED_PROXIES =~ remote_ips.last.strip + while remote_ips.size > 1 && (TRUSTED_PROXIES =~ remote_ips.last.strip || ActionController::Base.trusted_proxies =~ remote_ips.last.strip) remote_ips.pop end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 8ebf9aa186..f3500fca34 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -72,6 +72,34 @@ class RequestTest < ActiveSupport::TestCase assert_equal '9.9.9.9', request.remote_ip end + test "remote ip with user specified trusted proxies" do + ActionController::Base.trusted_proxies = /^67\.205\.106\.73$/i + + request = stub_request 'REMOTE_ADDR' => '67.205.106.73', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '67.205.106.73,172.16.0.1', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '3.4.5.6', request.remote_ip + + request = stub_request 'REMOTE_ADDR' => '67.205.106.74,172.16.0.1', + 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' + assert_equal '67.205.106.74', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' + assert_equal 'unknown', request.remote_ip + + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' + assert_equal '3.4.5.6', request.remote_ip + + ActionController::Base.trusted_proxies = nil + end + test "domains" do request = stub_request 'HTTP_HOST' => 'www.rubyonrails.org' assert_equal "rubyonrails.org", request.domain -- cgit v1.2.3 From 076ca48bd649ddea4dd1a320879c03a9fe7a0a6d Mon Sep 17 00:00:00 2001 From: railsbob <anup.narkhede@gmail.com> Date: Sun, 9 Aug 2009 13:01:42 +0100 Subject: Ensure hm:t#find does not assign nil to :include [#1845 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../lib/active_record/associations/has_many_through_association.rb | 2 +- .../test/cases/associations/has_many_through_associations_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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 ed7c3a6e08..f4507c979c 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -54,7 +54,7 @@ module ActiveRecord options[:select] = construct_select(options[:select]) options[:from] ||= construct_from options[:joins] = construct_joins(options[:joins]) - options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? + options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] end def insert_record(record, force = true, validate = true) diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index f6b4a42377..799ab52025 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -299,4 +299,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys assert_equal 1, owners(:blackbeard).toys.count end + + def test_find_on_has_many_association_collection_with_include_and_conditions + post_with_no_comments = people(:michael).posts_with_no_comments.first + assert_equal post_with_no_comments, posts(:authorless) + end end -- cgit v1.2.3 From 1c9b3aabdd1ff1e5b0e2b9bf6d5149baf6f23d1b Mon Sep 17 00:00:00 2001 From: Matt Conway <wr0ngway@yahoo.com> Date: Wed, 22 Apr 2009 22:25:46 -0400 Subject: Allow connect_timeout, read_timeout and write_timeout settings for MySQL [#2544 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 83cb9cff15..2b882a1f25 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -587,6 +587,10 @@ module ActiveRecord @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) end + @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] + @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] + @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] + @connection.real_connect(*@connection_options) # reconnect must be set after real_connect is called, because real_connect sets it to false internally -- cgit v1.2.3 From a1c289dbe44f32d30d3addcb6b9c912d153f93b0 Mon Sep 17 00:00:00 2001 From: jastix <nik@jastix.me> Date: Sun, 9 Aug 2009 20:50:56 +0400 Subject: Require test/unit in the generated test_helper for plugin [#1878 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- railties/lib/generators/test_unit/plugin/templates/test_helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/railties/lib/generators/test_unit/plugin/templates/test_helper.rb b/railties/lib/generators/test_unit/plugin/templates/test_helper.rb index cf148b8b47..348ec33582 100644 --- a/railties/lib/generators/test_unit/plugin/templates/test_helper.rb +++ b/railties/lib/generators/test_unit/plugin/templates/test_helper.rb @@ -1,3 +1,5 @@ require 'rubygems' +require 'test/unit' require 'active_support' -require 'active_support/test_case' \ No newline at end of file +require 'active_support/test_case' + -- cgit v1.2.3 From 1185500ff0465aff8686315f1b785884f133adcf Mon Sep 17 00:00:00 2001 From: Gabe da Silveira <gabe@websaviour.com> Date: Sun, 9 Aug 2009 02:27:53 -0700 Subject: Remove unused routeset method routes_for_controller_and_action in favour for routes_for [#3023 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- actionpack/lib/action_controller/routing/route_set.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 040a7e2cb6..0c499f3b46 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -463,13 +463,6 @@ module ActionController routes_by_controller[controller][action][merged.keys] end - def routes_for_controller_and_action(controller, action) - selected = routes.select do |route| - route.matches_controller_and_action? controller, action - end - (selected.length == routes.length) ? routes : selected - end - def routes_for_controller_and_action_and_keys(controller, action, keys) selected = routes.select do |route| route.matches_controller_and_action? controller, action -- cgit v1.2.3 From e0adfa82c05f9c975005f102b4bcaebfcd17d241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 19:03:09 +0200 Subject: Optimize Range#sum only for integers [#2489] --- activesupport/lib/active_support/core_ext/enumerable.rb | 5 +++-- activesupport/test/core_ext/enumerable_test.rb | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index d68eef8c23..42686cfbd5 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -113,9 +113,10 @@ module Enumerable end class Range #:nodoc: - # Optimize range sum to use arithmetic progression if a block is not given. + # Optimize range sum to use arithmetic progression if a block is not given and + # we have a range of numeric values. def sum(identity=0, &block) - return super if block_given? + return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) actual_last = exclude_end? ? (last - 1) : last (actual_last - first + 1) * (actual_last + first) / 2 end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6512754e53..6a31ad32f5 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -64,6 +64,7 @@ class EnumerableTests < Test::Unit::TestCase assert_equal 20, (1..4).sum { |i| i * 2 } assert_equal 10, (1..4).sum assert_equal 6, (1...4).sum + assert_equal 'abc', ('a'..'c').sum end def test_each_with_object -- cgit v1.2.3 From 97a5c7a5163b4bf7398939a4ccaf1d04c530ec52 Mon Sep 17 00:00:00 2001 From: Pratik Naik <pratiknaik@gmail.com> Date: Sun, 9 Aug 2009 18:13:03 +0100 Subject: Make enumerable test run stand alone --- activesupport/test/core_ext/enumerable_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6a31ad32f5..318ba9c0e2 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/core_ext/array' +require 'active_support/core_ext/symbol' require 'active_support/core_ext/enumerable' Payment = Struct.new(:price) -- cgit v1.2.3 From 60bed59fbf21fcfe5b9a753181f6d34488055acd Mon Sep 17 00:00:00 2001 From: Pratik Naik <pratiknaik@gmail.com> Date: Sun, 9 Aug 2009 18:15:13 +0100 Subject: Remove unnecessary &block from Range#sum and add tests for (num..float).sum --- activesupport/lib/active_support/core_ext/enumerable.rb | 2 +- activesupport/test/core_ext/enumerable_test.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 42686cfbd5..15a303cf04 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -115,7 +115,7 @@ end class Range #:nodoc: # Optimize range sum to use arithmetic progression if a block is not given and # we have a range of numeric values. - def sum(identity=0, &block) + def sum(identity = 0) return super if block_given? || !(first.instance_of?(Integer) && last.instance_of?(Integer)) actual_last = exclude_end? ? (last - 1) : last (actual_last - first + 1) * (actual_last + first) / 2 diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 318ba9c0e2..66507d4652 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -66,6 +66,7 @@ class EnumerableTests < Test::Unit::TestCase assert_equal 10, (1..4).sum assert_equal 6, (1...4).sum assert_equal 'abc', ('a'..'c').sum + assert_raises(NoMethodError) { 1..2.5.sum } end def test_each_with_object -- cgit v1.2.3 From bb1e1776914edf3be7e46b55036c18a64595f919 Mon Sep 17 00:00:00 2001 From: Pratik Naik <pratiknaik@gmail.com> Date: Sun, 9 Aug 2009 18:18:47 +0100 Subject: .gitignore activesupport/test/fixtures/isolation_test --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2d879499fa..43c4d7b124 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ actionpack/pkg activemodel/test/fixtures/fixture_database.sqlite3 actionmailer/pkg activesupport/pkg +activesupport/test/fixtures/isolation_test railties/pkg railties/test/500.html railties/test/fixtures/tmp -- cgit v1.2.3 From c2f9c42719b248bec93801d69791351bdf97ecd0 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 11:02:34 -0700 Subject: Fix that RedCloth shouldn't be required to run tests --- actionpack/test/template/text_helper_test.rb | 30 +++++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index b7823b9394..08143ba680 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -1,6 +1,10 @@ require 'abstract_unit' require 'testing_sandbox' -require 'redcloth' +begin + require 'redcloth' +rescue LoadError + $stderr.puts "Skipping textilize tests. `gem install RedCloth` to enable." +end class TextHelperTest < ActionView::TestCase tests ActionView::Helpers::TextHelper @@ -530,19 +534,21 @@ class TextHelperTest < ActionView::TestCase assert_equal(%w{Specialized Fuji Giant}, @cycles) end - def test_textilize - assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!")) - end + if defined? RedCloth + def test_textilize + assert_equal("<p><strong>This is Textile!</strong> Rejoice!</p>", textilize("*This is Textile!* Rejoice!")) + end - def test_textilize_with_blank - assert_equal("", textilize("")) - end + def test_textilize_with_blank + assert_equal("", textilize("")) + end - def test_textilize_with_options - assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong>", :filter_html)) - end + def test_textilize_with_options + assert_equal("<p>This is worded <strong>strongly</strong></p>", textilize("This is worded <strong>strongly</strong>", :filter_html)) + end - def test_textilize_with_hard_breaks - assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True.")) + def test_textilize_with_hard_breaks + assert_equal("<p>This is one scary world.<br />\n True.</p>", textilize("This is one scary world.\n True.")) + end end end -- cgit v1.2.3 From ff60ec469fd85db450c9c2c7eb8f9d9254e45988 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <hugo.peixoto@gmail.com> Date: Sat, 8 Aug 2009 23:22:22 +0100 Subject: Added a uniqueness validation test that uses diacritics. [#2883 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/test/cases/validations/uniqueness_validation_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 961db51d1d..8a3950b5aa 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -43,13 +43,13 @@ class UniquenessValidationTest < ActiveRecord::TestCase def test_validate_uniqueness Topic.validates_uniqueness_of(:title) - t = Topic.new("title" => "I'm unique!") + t = Topic.new("title" => "I'm uniqué!") assert t.save, "Should save t as unique" t.content = "Remaining unique" assert t.save, "Should still save t as unique" - t2 = Topic.new("title" => "I'm unique!") + t2 = Topic.new("title" => "I'm uniqué!") assert !t2.valid?, "Shouldn't be valid" assert !t2.save, "Shouldn't save t2 as unique" assert_equal ["has already been taken"], t2.errors[:title] -- cgit v1.2.3 From db42f1bf67e0c5d584b77b11d440e9d09065c94b Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 11:58:07 -0700 Subject: Fix standalone test missing developer fixture --- activerecord/test/cases/validations/uniqueness_validation_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 8a3950b5aa..e72013b59c 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -5,6 +5,7 @@ require 'models/reply' require 'models/warehouse_thing' require 'models/guid' require 'models/event' +require 'models/developer' # The following methods in Topic are used in test_conditional_validation_* class Topic @@ -36,7 +37,7 @@ class Thaumaturgist < IneptWizard end class UniquenessValidationTest < ActiveRecord::TestCase - fixtures :topics, 'warehouse-things' + fixtures :topics, 'warehouse-things', :developers repair_validations(Topic) -- cgit v1.2.3 From 635f68dca935edf69d97a086e036e0d0b4523d57 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 12:11:50 -0700 Subject: Uses &:foo --- activesupport/test/dependencies_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 99c53924c2..9026126044 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -3,6 +3,7 @@ require 'pp' require 'active_support/dependencies' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/symbol/to_proc' module ModuleWithMissing mattr_accessor :missing_count -- cgit v1.2.3 From d0bcf51191d71edfddf38ade6ff7a3099ba23a54 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 12:14:25 -0700 Subject: Extract repetitive method --- activesupport/test/dependencies_test.rb | 70 +++++++++++++++++---------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 9026126044..fe9f86fd8c 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -34,6 +34,10 @@ class DependenciesTest < Test::Unit::TestCase ActiveSupport::Dependencies.explicitly_unloadable_constants = [] end + def with_autoloading_fixtures(&block) + with_loading 'autoloading_fixtures', &block + end + def test_tracking_loaded_files require_dependency 'dependencies/service_one' require_dependency 'dependencies/service_two' @@ -130,7 +134,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_module_loading - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Module, A assert_kind_of Class, A::B assert_kind_of Class, A::C::D @@ -139,7 +143,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_non_existing_const_raises_name_error - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_raise(NameError) { DoesNotExist } assert_raise(NameError) { NoModule::DoesNotExist } assert_raise(NameError) { A::DoesNotExist } @@ -148,49 +152,49 @@ class DependenciesTest < Test::Unit::TestCase end def test_directories_manifest_as_modules_unless_const_defined - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Module, ModuleFolder Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_class - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ModuleFolder::NestedClass Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_inline_class - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ModuleFolder::InlineClass Object.__send__ :remove_const, :ModuleFolder end end def test_directories_may_manifest_as_nested_classes - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ClassFolder Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_class - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ClassFolder::NestedClass Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_inline_class - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ClassFolder::InlineClass Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_inline_subclass_of_parent - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Class, ClassFolder::ClassFolderSubclass assert_kind_of Class, ClassFolder assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder @@ -199,7 +203,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_nested_class_can_access_sibling - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" assert defined?(ModuleFolder::NestedSibling) assert_equal ModuleFolder::NestedSibling, sibling @@ -208,7 +212,7 @@ class DependenciesTest < Test::Unit::TestCase end def failing_test_access_thru_and_upwards_fails - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! defined?(ModuleFolder) assert_raise(NameError) { ModuleFolder::Object } assert_raise(NameError) { ModuleFolder::NestedClass::Object } @@ -217,7 +221,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_non_existing_const_raises_name_error_with_fully_qualified_name - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do begin A::DoesNotExist.nil? flunk "No raise!!" @@ -295,7 +299,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_autoloaded? - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") @@ -374,7 +378,7 @@ class DependenciesTest < Test::Unit::TestCase end end_eval - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert_kind_of Integer, ::ModuleWithCustomConstMissing::B assert_kind_of Module, ::ModuleWithCustomConstMissing::A assert_kind_of String, ::ModuleWithCustomConstMissing::A::B @@ -383,7 +387,7 @@ class DependenciesTest < Test::Unit::TestCase def test_const_missing_should_not_double_load $counting_loaded_times = 0 - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do require_dependency '././counting_loader' assert_equal 1, $counting_loaded_times assert_raise(ArgumentError) { ActiveSupport::Dependencies.load_missing_constant Object, :CountingLoader } @@ -397,7 +401,7 @@ class DependenciesTest < Test::Unit::TestCase m.module_eval "def a() CountingLoader; end" extend m kls = nil - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do kls = nil assert_nothing_raised { kls = a } assert_equal "CountingLoader", kls.name @@ -432,7 +436,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_load_once_paths_do_not_add_to_autoloaded_constants - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths.dup assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") @@ -448,7 +452,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_application_should_special_case_application_controller - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do require_dependency 'application' assert_equal 10, ApplicationController assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) @@ -456,7 +460,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_const_missing_on_kernel_should_fallback_to_object - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do kls = Kernel::E assert_equal "E", kls.name assert_equal kls.object_id, Kernel::E.object_id @@ -464,14 +468,14 @@ class DependenciesTest < Test::Unit::TestCase end def test_preexisting_constants_are_not_marked_as_autoloaded - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do require_dependency 'e' assert ActiveSupport::Dependencies.autoloaded?(:E) ActiveSupport::Dependencies.clear end Object.const_set :E, Class.new - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do require_dependency 'e' assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!" ActiveSupport::Dependencies.clear @@ -482,7 +486,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_unloadable - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do Object.const_set :M, Module.new M.unloadable @@ -496,14 +500,14 @@ class DependenciesTest < Test::Unit::TestCase end def test_unloadable_should_fail_with_anonymous_modules - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do m = Module.new assert_raise(ArgumentError) { m.unloadable } end end def test_unloadable_should_return_change_flag - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do Object.const_set :M, Module.new assert_equal true, M.unloadable assert_equal false, M.unloadable @@ -594,7 +598,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_file_with_multiple_constants_and_require_dependency - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! defined?(MultipleConstantFile) assert ! defined?(SiblingConstant) @@ -612,7 +616,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_file_with_multiple_constants_and_auto_loading - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! defined?(MultipleConstantFile) assert ! defined?(SiblingConstant) @@ -631,7 +635,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_nested_file_with_multiple_constants_and_require_dependency - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! defined?(ClassFolder::NestedClass) assert ! defined?(ClassFolder::SiblingClass) @@ -650,7 +654,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_nested_file_with_multiple_constants_and_auto_loading - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert ! defined?(ClassFolder::NestedClass) assert ! defined?(ClassFolder::SiblingClass) @@ -669,7 +673,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_autoload_doesnt_shadow_no_method_error_with_relative_constant - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" 2.times do assert_raise(NoMethodError) { RaisesNoMethodError } @@ -682,7 +686,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it hasn't been referenced yet!" 2.times do assert_raise(NoMethodError) { ::RaisesNoMethodError } @@ -695,7 +699,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do ActiveSupport::Dependencies.mechanism = :require 2.times do assert_raise(NameError) { assert_equal 123, ::RaisesNameError::FooBarBaz } @@ -704,7 +708,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_autoload_doesnt_shadow_name_error - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError) 2.times do begin @@ -738,7 +742,7 @@ class DependenciesTest < Test::Unit::TestCase end def test_load_once_constants_should_not_be_unloaded - with_loading 'autoloading_fixtures' do + with_autoloading_fixtures do ActiveSupport::Dependencies.load_once_paths = ActiveSupport::Dependencies.load_paths ::A.to_s assert defined?(A) -- cgit v1.2.3 From 87e2c1895f486937cfb5ea2a3a4168b3a57d447b Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 12:40:16 -0700 Subject: Fix failing dependencies test relying on . being in LOAD_PATH --- activesupport/test/dependencies_test.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index fe9f86fd8c..97d70cf8c4 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -24,9 +24,11 @@ class DependenciesTest < Test::Unit::TestCase def with_loading(*from) old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load - dir = File.dirname(__FILE__) + this_dir = File.dirname(__FILE__) + parent_dir = File.dirname(this_dir) + $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir) prior_load_paths = ActiveSupport::Dependencies.load_paths - ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{dir}/#{f}" } + ActiveSupport::Dependencies.load_paths = from.collect { |f| "#{this_dir}/#{f}" } yield ensure ActiveSupport::Dependencies.load_paths = prior_load_paths -- cgit v1.2.3 From c5896bfd8432f6b7a1c6cb06486c5c85eafe9450 Mon Sep 17 00:00:00 2001 From: Elise Huard <git@elisehuard.be> Date: Sun, 9 Aug 2009 10:24:28 +0200 Subject: validate uniqueness with limit in utf8 [#2653 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/lib/active_record/validations/uniqueness.rb | 2 +- .../test/cases/validations/uniqueness_validation_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index edec4e9e43..711086dc2c 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -119,7 +119,7 @@ module ActiveRecord comparison_operator = "IS ?" elsif column.text? comparison_operator = "#{connection.case_sensitive_equality_operator} ?" - value = column.limit ? value.to_s[0, column.limit] : value.to_s + value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s else comparison_operator = "= ?" end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index e72013b59c..cb123d3498 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -238,6 +238,16 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" end + def test_validate_uniqueness_with_limit_and_utf8 + with_kcode('UTF8') do + # Event.title is limited to 5 characters + e1 = Event.create(:title => "一二三四五") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "一二三四五六七八") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + end + end + def test_validate_straight_inheritance_uniqueness w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") assert w1.valid?, "Saving w1" -- cgit v1.2.3 From 3e0951632c52018eefb86d9e0bfe77383f9622fb Mon Sep 17 00:00:00 2001 From: Roy Nicholson <nicholson.roy@gmail.com> Date: Sun, 9 Aug 2009 13:57:20 -0400 Subject: Add ability to set SSL options on ARes connections. [#2370 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activeresource/CHANGELOG | 2 + activeresource/lib/active_resource/base.rb | 41 +++++++++++++++++++ activeresource/lib/active_resource/connection.rb | 35 ++++++++++++++-- activeresource/lib/active_resource/exceptions.rb | 8 ++++ activeresource/test/base_test.rb | 51 ++++++++++++++++++++++++ activeresource/test/connection_test.rb | 18 +++++++++ 6 files changed, 152 insertions(+), 3 deletions(-) diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 1f9ecea6c5..2b10fad4a5 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* More thorough SSL support. #2370 [Roy Nicholson] + * HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet] diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 9db35881b8..a66ce4c23f 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -103,6 +103,8 @@ module ActiveResource # # Many REST APIs will require authentication, usually in the form of basic # HTTP authentication. Authentication can be specified by: + # + # === HTTP Basic Authentication # * putting the credentials in the URL for the +site+ variable. # # class Person < ActiveResource::Base @@ -123,6 +125,19 @@ module ActiveResource # Note: Some values cannot be provided in the URL passed to site. e.g. email addresses # as usernames. In those situations you should use the separate user and password option. # + # === Certificate Authentication + # + # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options. + # + # class Person < ActiveResource::Base + # self.site = "https://secure.api.people.com/" + # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file)) + # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)), + # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs", + # :verify_mode => OpenSSL::SSL::VERIFY_PEER} + # end + # + # # == Errors & Validation # # Error handling and validation is handled in much the same manner as you're used to seeing in @@ -342,6 +357,31 @@ module ActiveResource end end + # Options that will get applied to an SSL connection. + # + # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. + # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate + # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates. + # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format. + # * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable) + # * <tt>:verify_callback</tt> - The verify callback for the server certification verification. + # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification. + # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate. + # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds. + def ssl_options=(opts={}) + @connection = nil + @ssl_options = opts + end + + # Returns the SSL options hash. + def ssl_options + if defined?(@ssl_options) + @ssl_options + elsif superclass != Object && superclass.ssl_options + superclass.ssl_options + end + end + # An instance of ActiveResource::Connection that is the base \connection to the remote service. # The +refresh+ parameter toggles whether or not the \connection is refreshed at every request # or not (defaults to <tt>false</tt>). @@ -352,6 +392,7 @@ module ActiveResource @connection.user = user if user @connection.password = password if password @connection.timeout = timeout if timeout + @connection.ssl_options = ssl_options if ssl_options @connection else superclass.connection diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index ef57c1f8b2..c08b7272ae 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -16,7 +16,7 @@ module ActiveResource :delete => 'Accept' } - attr_reader :site, :user, :password, :timeout, :proxy + attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -61,6 +61,11 @@ module ActiveResource @timeout = timeout end + # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'. + def ssl_options=(opts={}) + @ssl_options = opts + end + # Executes a GET request. # Used to get (find) resources. def get(path, headers = {}) @@ -102,6 +107,8 @@ module ActiveResource handle_response(result) rescue Timeout::Error => e raise TimeoutError.new(e.message) + rescue OpenSSL::SSL::SSLError => e + raise SSLError.new(e.message) end # Handles response and error codes from the remote service. @@ -149,8 +156,7 @@ module ActiveResource end def configure_http(http) - http.use_ssl = @site.is_a?(URI::HTTPS) - http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? + http = apply_ssl_options(http) # Net::HTTP timeouts default to 60 seconds. if @timeout @@ -161,6 +167,29 @@ module ActiveResource http end + def apply_ssl_options(http) + return http unless @site.is_a?(URI::HTTPS) + + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + return http unless defined?(@ssl_options) + + http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path] + http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file] + + http.cert = @ssl_options[:cert] if @ssl_options[:cert] + http.key = @ssl_options[:key] if @ssl_options[:key] + + http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store] + http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout] + + http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode] + http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback] + http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth] + + http + end + def default_header @default_header ||= {} end diff --git a/activeresource/lib/active_resource/exceptions.rb b/activeresource/lib/active_resource/exceptions.rb index 5e4b1d4487..dd59146b1a 100644 --- a/activeresource/lib/active_resource/exceptions.rb +++ b/activeresource/lib/active_resource/exceptions.rb @@ -20,6 +20,14 @@ module ActiveResource def to_s; @message ;end end + # Raised when a OpenSSL::SSL::SSLError occurs. + class SSLError < ConnectionError + def initialize(message) + @message = message + end + def to_s; @message ;end + end + # 3xx Redirection class Redirection < ConnectionError # :nodoc: def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index e68d562d97..2e1236cab4 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -166,6 +166,13 @@ class BaseTest < Test::Unit::TestCase assert_equal(5, Forum.connection.timeout) end + def test_should_accept_setting_ssl_options + expected = {:verify => 1} + Forum.ssl_options= expected + assert_equal(expected, Forum.ssl_options) + assert_equal(expected, Forum.connection.ssl_options) + end + def test_user_variable_can_be_reset actor = Class.new(ActiveResource::Base) actor.site = 'http://cinema' @@ -196,6 +203,16 @@ class BaseTest < Test::Unit::TestCase assert_nil actor.connection.timeout end + def test_ssl_options_hash_can_be_reset + actor = Class.new(ActiveResource::Base) + actor.site = 'https://cinema' + assert_nil actor.ssl_options + actor.ssl_options = {:foo => 5} + actor.ssl_options = nil + assert_nil actor.ssl_options + assert_nil actor.connection.ssl_options + end + def test_credentials_from_site_are_decoded actor = Class.new(ActiveResource::Base) actor.site = 'http://my%40email.com:%31%32%33@cinema' @@ -395,6 +412,40 @@ class BaseTest < Test::Unit::TestCase assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class' end + def test_ssl_options_reader_uses_superclass_ssl_options_until_written + # Superclass is Object so returns nil. + assert_nil ActiveResource::Base.ssl_options + assert_nil Class.new(ActiveResource::Base).ssl_options + Person.ssl_options = {:foo => 'bar'} + + # Subclass uses superclass ssl_options. + actor = Class.new(Person) + assert_equal Person.ssl_options, actor.ssl_options + + # Changing subclass ssl_options doesn't change superclass ssl_options. + actor.ssl_options = {:baz => ''} + assert_not_equal Person.ssl_options, actor.ssl_options + + # Changing superclass ssl_options doesn't overwrite subclass ssl_options. + Person.ssl_options = {:color => 'blue'} + assert_not_equal Person.ssl_options, actor.ssl_options + + # Changing superclass ssl_options after subclassing changes subclass ssl_options. + jester = Class.new(actor) + actor.ssl_options = {:color => 'red'} + assert_equal actor.ssl_options, jester.ssl_options + + # Subclasses are always equal to superclass ssl_options when not overridden. + fruit = Class.new(ActiveResource::Base) + apple = Class.new(fruit) + + fruit.ssl_options = {:alpha => 'betas'} + assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class' + + fruit.ssl_options = {:omega => 'moos'} + assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class' + end + def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects # Subclasses are always equal to superclass site when not overridden fruit = Class.new(ActiveResource::Base) diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 12e8058b0c..b482f2dd0e 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -204,6 +204,24 @@ class ConnectionTest < Test::Unit::TestCase assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, {'Accept' => 'application/xhtml+xml'}) } end + def test_ssl_options_get_applied_to_http + http = Net::HTTP.new('') + @conn.site="https://secure" + @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER} + @conn.timeout = 10 # prevent warning about uninitialized. + @conn.send(:configure_http, http) + + assert http.use_ssl? + assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER + end + + def test_ssl_error + http = Net::HTTP.new('') + @conn.expects(:http).returns(http) + http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate') + assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') } + end + protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do -- cgit v1.2.3 From 870750ed4b1e0b0e574aaec86db3e2cdf94b1190 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <theorem@Nayru.(none)> Date: Sun, 9 Aug 2009 09:20:18 +0100 Subject: With multiparameter date attributes, the behaviour when empty fields are present is now coherent with the one described in the date_select documentation. [#1715 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/lib/active_record/base.rb | 14 ++++++---- activerecord/test/cases/base_test.rb | 47 +++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 531a698f77..3b6e158ab9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -3013,16 +3013,22 @@ module ActiveRecord #:nodoc: def execute_callstack_for_multiparameter_attributes(callstack) errors = [] - callstack.each do |name, values| + callstack.each do |name, values_with_empty_parameters| begin klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass + # in order to allow a date to be set without a year, we must keep the empty values. + # Otherwise, we wouldn't be able to distinguish it from a date with an empty day. + values = values_with_empty_parameters.reject(&:nil?) + if values.empty? send(name + "=", nil) else + value = if Time == klass instantiate_time_object(name, values) elsif Date == klass begin + values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end Date.new(*values) rescue ArgumentError => ex # if Date.new raises an exception on an invalid date instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates @@ -3050,10 +3056,8 @@ module ActiveRecord #:nodoc: attribute_name = multiparameter_name.split("(").first attributes[attribute_name] = [] unless attributes.include?(attribute_name) - unless value.empty? - attributes[attribute_name] << - [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ] - end + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ] end attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } } diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 16364141df..88738d90b1 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1070,7 +1070,25 @@ class BasicsTest < ActiveRecord::TestCase assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date end - def test_multiparameter_attributes_on_date_with_empty_date + def test_multiparameter_attributes_on_date_with_empty_year + attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 6, 24), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_month + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 1, 24), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_day attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" } topic = Topic.find(1) topic.attributes = attributes @@ -1079,6 +1097,33 @@ class BasicsTest < ActiveRecord::TestCase assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date end + def test_multiparameter_attributes_on_date_with_empty_day_and_year + attributes = { "last_read(1i)" => "", "last_read(2i)" => "6", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 6, 1), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_day_and_month + attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "", "last_read(3i)" => "" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(2004, 1, 1), topic.last_read.to_date + end + + def test_multiparameter_attributes_on_date_with_empty_year_and_month + attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "24" } + topic = Topic.find(1) + topic.attributes = attributes + # note that extra #to_date call allows test to pass for Oracle, which + # treats dates/times the same + assert_date_from_db Date.new(1, 1, 24), topic.last_read.to_date + end + def test_multiparameter_attributes_on_date_with_all_empty attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" } topic = Topic.find(1) -- cgit v1.2.3 From 1382f4de1f9b0e443e7884bd4da53c20f0754568 Mon Sep 17 00:00:00 2001 From: David Burger <davidjburger@yahoo.com> Date: Sun, 17 May 2009 21:36:44 -0700 Subject: Fix that Hash#to_xml and Array#to_xml shouldn't modify their options hashes [#672 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activesupport/lib/active_support/core_ext/array/conversions.rb | 1 + activesupport/lib/active_support/core_ext/hash/conversions.rb | 1 + activesupport/test/core_ext/array_ext_test.rb | 7 +++++++ activesupport/test/core_ext/hash_ext_test.rb | 7 +++++++ 4 files changed, 16 insertions(+) diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 11846f265c..c53cf3f530 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -159,6 +159,7 @@ class Array raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml } require 'builder' unless defined?(Builder) + options = options.dup options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? ActiveSupport::Inflector.pluralize(ActiveSupport::Inflector.underscore(first.class.name)) : "records" options[:children] ||= options[:root].singularize options[:indent] ||= 2 diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 2a34874d08..bd9419e1a2 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -86,6 +86,7 @@ class Hash def to_xml(options = {}) require 'builder' unless defined?(Builder) + options = options.dup options[:indent] ||= 2 options.reverse_merge!({ :builder => Builder::XmlMarkup.new(:indent => options[:indent]), :root => "hash" }) diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 24d33896ce..8198b9bd2c 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -302,6 +302,13 @@ class ArrayToXmlTests < Test::Unit::TestCase xml = [].to_xml assert_match(/type="array"\/>/, xml) end + + def test_to_xml_dups_options + options = {:skip_instruct => true} + [].to_xml(options) + # :builder, etc, shouldn't be added to options + assert_equal({:skip_instruct => true}, options) + end end class ArrayExtractOptionsTests < Test::Unit::TestCase diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index ece5466abb..8b0f3fc33c 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -880,6 +880,13 @@ class HashToXmlTest < Test::Unit::TestCase assert_equal 30, alert_at.min assert_equal 45, alert_at.sec end + + def test_to_xml_dups_options + options = {:skip_instruct => true} + {}.to_xml(options) + # :builder, etc, shouldn't be added to options + assert_equal({:skip_instruct => true}, options) + end end class QueryTest < Test::Unit::TestCase -- cgit v1.2.3 From ae47f7575da3dc3c74fa63136d00492ba4beb278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= <jose.valim@gmail.com> Date: Sun, 9 Aug 2009 20:11:45 +0200 Subject: Improving test coverage for Range#sum [#2489] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activesupport/test/core_ext/enumerable_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 66507d4652..4170de3dce 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -64,9 +64,9 @@ class EnumerableTests < Test::Unit::TestCase def test_enumerable_sums assert_equal 20, (1..4).sum { |i| i * 2 } assert_equal 10, (1..4).sum + assert_equal 10, (1..4.5).sum assert_equal 6, (1...4).sum assert_equal 'abc', ('a'..'c').sum - assert_raises(NoMethodError) { 1..2.5.sum } end def test_each_with_object -- cgit v1.2.3 From 0ec64bea9293dd21588359da80bb43b82886cf2c Mon Sep 17 00:00:00 2001 From: Joshua Nichols <josh@technicalpickles.com> Date: Sun, 9 Aug 2009 13:13:34 -0400 Subject: Added back support for destroying an association's object by id. [#2306 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- .../associations/association_collection.rb | 1 + .../associations/has_many_associations_test.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index e67ccfb228..1b7bf42b91 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -208,6 +208,7 @@ module ActiveRecord # Note that this method will _always_ remove records from the database # ignoring the +:dependent+ option. def destroy(*records) + records = find(records) if records.any? {|record| record.kind_of?(Fixnum) || record.kind_of?(String)} remove_records(records) do |records, old_records| old_records.each { |record| record.destroy } end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index a3d92c3bdb..9345b469ab 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -699,6 +699,28 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, companies(:first_firm).clients_of_firm(true).size end + def test_destroying_by_fixnum_id + force_signal37_to_load_all_clients_of_firm + + assert_difference "Client.count", -1 do + companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id) + end + + assert_equal 0, companies(:first_firm).reload.clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + + def test_destroying_by_string_id + force_signal37_to_load_all_clients_of_firm + + assert_difference "Client.count", -1 do + companies(:first_firm).clients_of_firm.destroy(companies(:first_firm).clients_of_firm.first.id.to_s) + end + + assert_equal 0, companies(:first_firm).reload.clients_of_firm.size + assert_equal 0, companies(:first_firm).clients_of_firm(true).size + end + def test_destroying_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") -- cgit v1.2.3 From 1fc1986d6deacd71ec4bea2287d9cfed6123b898 Mon Sep 17 00:00:00 2001 From: Jatinder Singh <jatinder.saundh@gmail.com> Date: Sun, 9 Aug 2009 22:24:50 +0100 Subject: Make ActiveResource#exists? work [#3020 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activeresource/lib/active_resource/connection.rb | 5 +++-- activeresource/test/base_test.rb | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index c08b7272ae..884c425c1d 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -13,7 +13,8 @@ module ActiveResource HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept', :put => 'Content-Type', :post => 'Content-Type', - :delete => 'Accept' + :delete => 'Accept', + :head => 'Accept' } attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options @@ -93,7 +94,7 @@ module ActiveResource # Executes a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers)) + request(:head, path, build_request_headers(headers, :head)) end diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index 2e1236cab4..cba675177a 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -966,6 +966,14 @@ class BaseTest < Test::Unit::TestCase end end + def test_exists_without_http_mock + http = Net::HTTP.new(Person.site.host, Person.site.port) + ActiveResource::Connection.any_instance.expects(:http).returns(http) + http.expects(:request).returns(ActiveResource::Response.new("")) + + assert Person.exists?('not-mocked') + end + def test_to_xml matz = Person.find(1) xml = matz.encode -- cgit v1.2.3 From e1b73b975264c622f692a46eec060ff35a588d96 Mon Sep 17 00:00:00 2001 From: Bence Nagy <nagybence@tipogral.hu> Date: Sat, 27 Dec 2008 23:28:37 +0100 Subject: path_names could be used to customize collection actions too Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- actionpack/lib/action_controller/routing/resources.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb index 2dee0a3d87..c1ebd46b48 100644 --- a/actionpack/lib/action_controller/routing/resources.rb +++ b/actionpack/lib/action_controller/routing/resources.rb @@ -589,7 +589,10 @@ module ActionController resource.collection_methods.each do |method, actions| actions.each do |action| [method].flatten.each do |m| - map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= action + + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) end end end -- cgit v1.2.3 From 202b091373ed4d6c78adc7af5e89a359ff0fff2d Mon Sep 17 00:00:00 2001 From: Hugo Peixoto <theorem@Nayru.(none)> Date: Sun, 9 Aug 2009 07:28:29 +0100 Subject: Added both the documentation and a test case for the collection path name customization feature. [#1218 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../lib/action_controller/routing/resources.rb | 5 ++- actionpack/test/controller/resources_test.rb | 44 ++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb index c1ebd46b48..7fd3ffd3f1 100644 --- a/actionpack/lib/action_controller/routing/resources.rb +++ b/actionpack/lib/action_controller/routing/resources.rb @@ -320,9 +320,10 @@ module ActionController # notes.resources :attachments # end # - # * <tt>:path_names</tt> - Specify different names for the 'new' and 'edit' actions. For example: + # * <tt>:path_names</tt> - Specify different path names for the actions. For example: # # new_products_path == '/productos/nuevo' - # map.resources :products, :as => 'productos', :path_names => { :new => 'nuevo', :edit => 'editar' } + # # bids_product_path(1) == '/productos/1/licitacoes' + # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } # # You can also set default action names from an environment, like this: # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 30ab110ef7..0b639e363d 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -76,6 +76,50 @@ class ResourcesTest < ActionController::TestCase end end + def test_override_paths_for_member_and_collection_methods + collection_methods = { 'rss' => :get, 'reorder' => :post, 'csv' => :post } + member_methods = { 'rss' => :get, :atom => :get, :upload => :post, :fix => :post } + path_names = {:new => 'nuevo', 'rss' => 'canal', :fix => 'corrigir' } + + with_restful_routing :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do + + assert_restful_routes_for :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do |options| + member_methods.each do |action, method| + assert_recognizes(options.merge(:action => action.to_s, :id => '1'), + :path => "/messages/1/#{path_names[action] || action}", + :method => method) + end + + collection_methods.each do |action, method| + assert_recognizes(options.merge(:action => action), + :path => "/messages/#{path_names[action] || action}", + :method => method) + end + end + + assert_restful_named_routes_for :messages, + :collection => collection_methods, + :member => member_methods, + :path_names => path_names do |options| + + collection_methods.keys.each do |action| + assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", :action => action + end + + member_methods.keys.each do |action| + assert_named_route "/messages/1/#{path_names[action] || action}", "#{action}_message_path", :action => action, :id => "1" + end + + end + end + end + def test_override_paths_for_default_restful_actions resource = ActionController::Resources::Resource.new(:messages, :path_names => {:new => 'nuevo', :edit => 'editar'}) -- cgit v1.2.3 From 1af9bd58a04d14dc727713d2e581fafebc0e1d96 Mon Sep 17 00:00:00 2001 From: Tristan Dunn <tristanzdunn@gmail.com> Date: Sun, 9 Aug 2009 16:22:53 -0400 Subject: No longer require database name for MySQL to allow cross database selects. [#1122 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../lib/active_record/connection_adapters/mysql_adapter.rb | 7 +------ activerecord/test/cases/adapter_test.rb | 12 ++++++++++++ 2 files changed, 13 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 2b882a1f25..2812dbb522 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -53,12 +53,7 @@ module ActiveRecord socket = config[:socket] username = config[:username] ? config[:username].to_s : 'root' password = config[:password].to_s - - if config.has_key?(:database) - database = config[:database] - else - raise ArgumentError, "No database specified. Missing argument: database." - end + database = config[:database] # Require the MySQL driver and define Mysql::Result.all_hashes unless defined? Mysql diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 88136597e3..9463b7b14a 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -63,6 +63,18 @@ class AdapterTest < ActiveRecord::TestCase def test_show_nonexistent_variable_returns_nil assert_nil @connection.show_variable('foo_bar_baz') end + + def test_not_specifying_database_name_for_cross_database_selects + assert_nothing_raised do + ActiveRecord::Base.establish_connection({ + :adapter => 'mysql', + :username => 'rails' + }) + ActiveRecord::Base.connection.execute "SELECT activerecord_unittest.pirates.*, activerecord_unittest2.courses.* FROM activerecord_unittest.pirates, activerecord_unittest2.courses" + end + + ActiveRecord::Base.establish_connection 'arunit' + end end if current_adapter?(:PostgreSQLAdapter) -- cgit v1.2.3 From 916b18adeb0a1ae09fdaff21dda8d0bc8ed4ffa9 Mon Sep 17 00:00:00 2001 From: Jordan Brough <jordan@animoto.com> Date: Sun, 9 Aug 2009 22:53:04 +0100 Subject: Active Resource recognizes 410 as Resource Gone now [#2316 state:resolved] [Jordan Brough, Jatinder Singh] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activeresource/CHANGELOG | 2 ++ activeresource/lib/active_resource/base.rb | 3 ++- activeresource/lib/active_resource/connection.rb | 2 ++ activeresource/lib/active_resource/exceptions.rb | 3 +++ activeresource/test/base_test.rb | 24 ++++++++++++++++++++++++ activeresource/test/connection_test.rb | 3 +++ 6 files changed, 36 insertions(+), 1 deletion(-) diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 2b10fad4a5..aaa0eefe3c 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh] + * More thorough SSL support. #2370 [Roy Nicholson] * HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet] diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index a66ce4c23f..0311419fd6 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -164,6 +164,7 @@ module ActiveResource # * 404 - ActiveResource::ResourceNotFound # * 405 - ActiveResource::MethodNotAllowed # * 409 - ActiveResource::ResourceConflict + # * 410 - ActiveResource::ResourceGone # * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors) # * 401..499 - ActiveResource::ClientError # * 500..599 - ActiveResource::ServerError @@ -626,7 +627,7 @@ module ActiveResource response.code.to_i == 200 end # id && !find_single(id, options).nil? - rescue ActiveResource::ResourceNotFound + rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone false end diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 884c425c1d..9d551f04e7 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -131,6 +131,8 @@ module ActiveResource raise(MethodNotAllowed.new(response)) when 409 raise(ResourceConflict.new(response)) + when 410 + raise(ResourceGone.new(response)) when 422 raise(ResourceInvalid.new(response)) when 401...500 diff --git a/activeresource/lib/active_resource/exceptions.rb b/activeresource/lib/active_resource/exceptions.rb index dd59146b1a..0631cdcf9f 100644 --- a/activeresource/lib/active_resource/exceptions.rb +++ b/activeresource/lib/active_resource/exceptions.rb @@ -51,6 +51,9 @@ module ActiveResource # 409 Conflict class ResourceConflict < ClientError; end # :nodoc: + # 410 Gone + class ResourceGone < ClientError; end # :nodoc: + # 5xx Server Error class ServerError < ConnectionError; end # :nodoc: diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb index cba675177a..9c236bc893 100644 --- a/activeresource/test/base_test.rb +++ b/activeresource/test/base_test.rb @@ -899,6 +899,14 @@ class BaseTest < Test::Unit::TestCase assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end + def test_destroy_with_410_gone + assert Person.find(1).destroy + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/1.xml", {}, nil, 410 + end + assert_raise(ActiveResource::ResourceGone) { Person.find(1).destroy } + end + def test_delete assert Person.delete(1) ActiveResource::HttpMock.respond_to do |mock| @@ -914,6 +922,14 @@ class BaseTest < Test::Unit::TestCase end assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) } end + + def test_delete_with_410_gone + assert Person.delete(1) + ActiveResource::HttpMock.respond_to do |mock| + mock.get "/people/1.xml", {}, nil, 410 + end + assert_raise(ActiveResource::ResourceGone) { Person.find(1) } + end def test_exists # Class method. @@ -974,6 +990,14 @@ class BaseTest < Test::Unit::TestCase assert Person.exists?('not-mocked') end + def test_exists_with_410_gone + ActiveResource::HttpMock.respond_to do |mock| + mock.head "/people/1.xml", {}, nil, 410 + end + + assert !Person.exists?(1) + end + def test_to_xml matz = Person.find(1) xml = matz.encode diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index b482f2dd0e..d7466c65b4 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -56,6 +56,9 @@ class ConnectionTest < Test::Unit::TestCase # 409 is an optimistic locking error assert_response_raises ActiveResource::ResourceConflict, 409 + # 410 is a removed resource + assert_response_raises ActiveResource::ResourceGone, 410 + # 422 is a validation error assert_response_raises ActiveResource::ResourceInvalid, 422 -- cgit v1.2.3 From 08ec22054f56442b10f67e41c3b7593da6adcabd Mon Sep 17 00:00:00 2001 From: Vladimir Meremyanin <vladimir@meremyanin.com> Date: Sun, 9 Aug 2009 23:09:08 +0100 Subject: Make sure association conditions work with :include and :joins [#358 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activerecord/lib/active_record/base.rb | 8 +++++--- .../test/cases/associations/has_many_associations_test.rb | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3b6e158ab9..d4103b46c0 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2303,11 +2303,13 @@ module ActiveRecord #:nodoc: # Extract table name from qualified attribute names. if attr.include?('.') - table_name, attr = attr.split('.', 2) - table_name = connection.quote_table_name(table_name) + attr_table_name, attr = attr.split('.', 2) + attr_table_name = connection.quote_table_name(attr_table_name) + else + attr_table_name = table_name end - attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", value) + attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value) else sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s)) end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9345b469ab..b16395776e 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -287,6 +287,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") end + def test_find_all_with_include_and_conditions + assert_nothing_raised do + Developer.find(:all, :joins => :audit_logs, :conditions => {'audit_logs.message' => nil, :name => 'Smith'}) + end + end + def test_find_in_collection assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name assert_raise(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) } -- cgit v1.2.3 From e391c7a97cdefa172fcba214fb0a6cd3bd5b0bf4 Mon Sep 17 00:00:00 2001 From: Grzegorz Forysinski <grzegorz.forysinski@u2i.com> Date: Sun, 9 Aug 2009 18:24:49 -0400 Subject: Ensure ActiveResource#load works with numeric arrays [Grzegorz Forysinski, Elad Meidar] [#2305 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activeresource/lib/active_resource/base.rb | 8 +++++++- activeresource/test/base/load_test.rb | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 0311419fd6..41ffb5413b 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -1020,7 +1020,13 @@ module ActiveResource case value when Array resource = find_or_create_resource_for_collection(key) - value.map { |attrs| attrs.is_a?(String) ? attrs.dup : resource.new(attrs) } + value.map do |attrs| + if attrs.is_a?(String) || attrs.is_a?(Numeric) + attrs.duplicable? ? attrs.dup : attrs + else + resource.new(attrs) + end + end when Hash resource = find_or_create_resource_for(key) resource.new(value) diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/base/load_test.rb index 035bd965c2..5f5a580445 100644 --- a/activeresource/test/base/load_test.rb +++ b/activeresource/test/base/load_test.rb @@ -51,7 +51,9 @@ class BaseLoadTest < Test::Unit::TestCase :id => 1, :state => { :id => 1, :name => 'Oregon', :notable_rivers => [ { :id => 1, :name => 'Willamette' }, - { :id => 2, :name => 'Columbia', :rafted_by => @matz }] }}} + { :id => 2, :name => 'Columbia', :rafted_by => @matz }], + :postal_codes => [97018,1234567890], + :places => ["Columbia City", "Unknown"]}}} @person = Person.new end @@ -127,6 +129,19 @@ class BaseLoadTest < Test::Unit::TestCase assert_kind_of Person::Street::State::NotableRiver, rivers.first assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id assert_equal @matz[:id], rivers.last.rafted_by.id + + postal_codes = state.postal_codes + assert_kind_of Array, postal_codes + assert_equal 2, postal_codes.size + assert_kind_of Fixnum, postal_codes.first + assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first + assert_kind_of Bignum, postal_codes.last + assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last + + places = state.places + assert_kind_of Array, places + assert_kind_of String, places.first + assert_equal @deep[:street][:state][:places].first, places.first end def test_nested_collections_within_the_same_namespace -- cgit v1.2.3 From 920ef4e6f3832ea1d3297e2e7e5f0f775dab69a8 Mon Sep 17 00:00:00 2001 From: Matt Duncan <mrduncan@gmail.com> Date: Sat, 8 Aug 2009 11:50:17 -0400 Subject: Fixed to_label_tag to accept id attribute without changing for attribute [#2660 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- actionpack/lib/action_view/helpers/form_helper.rb | 1 + actionpack/test/template/form_helper_test.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 72f162cb20..81029102b1 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -738,6 +738,7 @@ module ActionView options = options.stringify_keys tag_value = options.delete("value") name_and_id = options.dup + name_and_id["id"] = name_and_id["for"] add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options["for"] ||= name_and_id["id"] diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 2b1d80b1bf..8fd018f86d 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -157,6 +157,22 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, "for" => "my_for")) end + def test_label_with_id_attribute_as_symbol + assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, :id => "my_id")) + end + + def test_label_with_id_attribute_as_string + assert_dom_equal('<label for="post_title" id="my_id">Title</label>', label(:post, :title, nil, "id" => "my_id")) + end + + def test_label_with_for_and_id_attributes_as_symbol + assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, :for => "my_for", :id => "my_id")) + end + + def test_label_with_for_and_id_attributes_as_string + assert_dom_equal('<label for="my_for" id="my_id">Title</label>', label(:post, :title, nil, "for" => "my_for", "id" => "my_id")) + end + def test_label_for_radio_buttons_with_value assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great_title")) assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title")) -- cgit v1.2.3 From ce2422ceaf8ee128e6ea9d8582611ba2ac1e0406 Mon Sep 17 00:00:00 2001 From: Arthur Zapparoli <arthurzap@gmail.com> Date: Sun, 9 Aug 2009 14:42:38 -0300 Subject: Model#human_attribute_name now accept symbols [#3025 status:resolved] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Valim <jose.valim@gmail.com> --- activerecord/lib/active_record/base.rb | 2 +- activerecord/test/cases/i18n_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d4103b46c0..2219edaa3a 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1394,7 +1394,7 @@ module ActiveRecord #:nodoc: end defaults << options[:default] if options[:default] defaults.flatten! - defaults << attribute_key_name.humanize + defaults << attribute_key_name.to_s.humanize options[:count] ||= 1 I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes])) end diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index b1db662eca..d59c53cec8 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -12,6 +12,11 @@ class ActiveRecordI18nTests < Test::Unit::TestCase I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } assert_equal 'topic title attribute', Topic.human_attribute_name('title') end + + def test_translated_model_attributes_with_symbols + I18n.backend.store_translations 'en', :activerecord => {:attributes => {:topic => {:title => 'topic title attribute'} } } + assert_equal 'topic title attribute', Topic.human_attribute_name(:title) + end def test_translated_model_attributes_with_sti I18n.backend.store_translations 'en', :activerecord => {:attributes => {:reply => {:title => 'reply title attribute'} } } -- cgit v1.2.3 From 810b59a2a9da3e85bc85d2617ae781c1b3645af4 Mon Sep 17 00:00:00 2001 From: Arthur Zapparoli <arthurzap@gmail.com> Date: Sun, 9 Aug 2009 16:19:23 -0300 Subject: Removed duplicated tests [#3026 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../cases/associations/belongs_to_associations_test.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 784c484178..2a77eed1b5 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -249,24 +249,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, Topic.find(topic.id)[:replies_count] end - def test_belongs_to_counter_after_save - topic = Topic.create("title" => "monday night") - topic.replies.create("title" => "re: monday night", "content" => "football") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.save - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - - def test_belongs_to_counter_after_update_attributes - topic = Topic.create("title" => "37s") - topic.replies.create("title" => "re: 37s", "content" => "rails") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - - topic.update_attributes("title" => "37signals") - assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count") - end - def test_assignment_before_child_saved final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) -- cgit v1.2.3 From 00d6c766608f90ce64f11feb98786cbc7f71b89d Mon Sep 17 00:00:00 2001 From: Gabe da Silveira <gabe@websaviour.com> Date: Sun, 9 Aug 2009 12:53:22 -0700 Subject: Enable has_many :through for going through a has_one association on the join model [#2719 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- activerecord/lib/active_record/associations.rb | 31 ++++++++++++++++++++-- .../associations/through_association_scope.rb | 2 +- activerecord/lib/active_record/reflection.rb | 2 +- .../associations/has_many_associations_test.rb | 2 +- .../has_many_through_associations_test.rb | 12 +++++++++ .../test/cases/associations/join_model_test.rb | 2 +- activerecord/test/models/author.rb | 1 + 7 files changed, 46 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7f299b2aa5..f494e38e2f 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -42,11 +42,12 @@ module ActiveRecord end end - class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc: + class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") end end + class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: def initialize(owner, reflection) super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") @@ -416,6 +417,32 @@ module ActiveRecord # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm # @firm.invoices # selects all invoices by going through the Client join model. # + # Similarly you can go through a +has_one+ association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, :through => :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are + # *read-only*. For example, the following would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around. + # @group.avatars.delete(@group.avatars.last) # so would this + # # === Polymorphic Associations # # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they @@ -819,7 +846,7 @@ module ActiveRecord # [:through] # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt> - # or <tt>has_many</tt> association on the join model. + # <tt>has_one</tt> or <tt>has_many</tt> association on the join model. # [:source] # Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 8e7ce33814..16b6123439 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -93,7 +93,7 @@ module ActiveRecord # Construct attributes for :through pointing to owner and associate. def construct_join_attributes(associate) # TODO: revist this to allow it for deletion, supposing dependent option is supported - raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many + raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro) join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id) diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0baa9654b7..db5d2b25ed 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -314,7 +314,7 @@ module ActiveRecord raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection) end - unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil? + unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil? raise HasManyThroughSourceAssociationMacroError.new(self) end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b16395776e..6edb52092f 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -898,7 +898,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] }, lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) }, lambda { authors(:mary).comments.delete(authors(:mary).comments.first) }, - ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) } + ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end def test_dynamic_find_should_respect_association_order_for_through 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 799ab52025..58ce2ca3a5 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -304,4 +304,16 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase post_with_no_comments = people(:michael).posts_with_no_comments.first assert_equal post_with_no_comments, posts(:authorless) end + + def test_has_many_through_has_one_reflection + assert_equal [comments(:eager_sti_on_associations_vs_comment)], authors(:david).very_special_comments + end + + def test_modifying_has_many_through_has_one_reflection_should_raise + [ + lambda { authors(:david).very_special_comments = [VerySpecialComment.create!(:body => "Gorp!", :post_id => 1011), VerySpecialComment.create!(:body => "Eep!", :post_id => 1012)] }, + lambda { authors(:david).very_special_comments << VerySpecialComment.create!(:body => "Hoohah!", :post_id => 1013) }, + lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) }, + ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } + end end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 9da7fc2639..4907cfe2b0 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -381,7 +381,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_through_polymorphic_has_one - assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging } + assert_equal Tagging.find(1,2), authors(:david).tagging end def test_has_many_through_polymorphic_has_many diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index b844c7cce0..f264f980d6 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -1,5 +1,6 @@ class Author < ActiveRecord::Base has_many :posts + has_many :very_special_comments, :through => :posts has_many :posts_with_comments, :include => :comments, :class_name => "Post" has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type" has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id' -- cgit v1.2.3 From 0472839d684d836e1e7ecd39722c313410d76d5b Mon Sep 17 00:00:00 2001 From: Tristan Dunn <tristanzdunn@gmail.com> Date: Mon, 10 Aug 2009 00:41:36 +0100 Subject: Prevent overwriting of table name in merging SQL conditions [#2949 state:resolved] --- activerecord/lib/active_record/base.rb | 4 +++- activerecord/test/cases/named_scope_test.rb | 6 ++++++ activerecord/test/models/comment.rb | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2219edaa3a..c5be561ea3 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2294,10 +2294,12 @@ module ActiveRecord #:nodoc: # And for value objects on a composed_of relationship: # { :address => Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, table_name = quoted_table_name) + def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name) attrs = expand_hash_conditions_for_aggregates(attrs) conditions = attrs.map do |attr, value| + table_name = default_table_name + unless value.is_a?(Hash) attr = attr.to_s diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 2a729f0678..bb2aba9d92 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -368,6 +368,12 @@ class NamedScopeTest < ActiveRecord::TestCase end end end + + def test_table_names_for_chaining_scopes_with_and_without_table_name_included + assert_nothing_raised do + Comment.for_first_post.for_first_author.all + end + end end class DynamicScopeMatchTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index f7f07c103f..399dea9f12 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -1,6 +1,10 @@ class Comment < ActiveRecord::Base named_scope :containing_the_letter_e, :conditions => "comments.body LIKE '%e%'" - + named_scope :for_first_post, :conditions => { :post_id => 1 } + named_scope :for_first_author, + :joins => :post, + :conditions => { "posts.author_id" => 1 } + belongs_to :post, :counter_cache => true def self.what_are_you -- cgit v1.2.3 From a0f69722be00cd546558b067054e9e7ae2564274 Mon Sep 17 00:00:00 2001 From: Tristan Dunn <tristanzdunn@gmail.com> Date: Sun, 9 Aug 2009 19:37:12 -0400 Subject: Allow ho:through#build when the owner is a new record [#1749 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- .../associations/has_one_through_association.rb | 12 +++++++++--- .../cases/associations/has_one_through_associations_test.rb | 10 ++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 830aa1808a..a79bf943d1 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -18,9 +18,15 @@ module ActiveRecord current_object = @owner.send(@reflection.through_reflection.name) if current_object - new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy - else - @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value))) if new_value + new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy + elsif new_value + if @owner.new_record? + self.target = new_value + through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name) + through_association.build(construct_join_attributes(new_value)) + else + @owner.send(@reflection.through_reflection.name, klass.create(construct_join_attributes(new_value))) + end end end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index ab6e6d20fc..9aef3eb374 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -28,6 +28,16 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase assert_not_nil new_member.current_membership assert_not_nil new_member.club end + + def test_creating_association_builds_through_record_for_new + new_member = Member.new(:name => "Jane") + new_member.club = clubs(:moustache_club) + assert new_member.current_membership + assert_equal clubs(:moustache_club), new_member.current_membership.club + assert_equal clubs(:moustache_club), new_member.club + assert new_member.save + assert_equal clubs(:moustache_club), new_member.club + end def test_replace_target_record new_club = Club.create(:name => "Marx Bros") -- cgit v1.2.3 From 0af4b0755f507d41590b5315966c9a20949f79f9 Mon Sep 17 00:00:00 2001 From: Max Lapshin <max@maxidoors.ru> Date: Mon, 16 Mar 2009 18:20:15 +0300 Subject: Make sure link_to generates the form with the specified :href if any [#2254 state:resolved] Signed-off-by: Pratik Naik <pratiknaik@gmail.com> --- actionpack/lib/action_view/helpers/url_helper.rb | 2 +- actionpack/test/template/url_helper_test.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index c5a6d1f084..b07304e361 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -568,7 +568,7 @@ module ActionView when confirm && popup "if (#{confirm_javascript_function(confirm)}) { #{popup_javascript_function(popup)} };return false;" when confirm && method - "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method)} };return false;" + "if (#{confirm_javascript_function(confirm)}) { #{method_javascript_function(method, url, href)} };return false;" when confirm "return #{confirm_javascript_function(confirm)};" when method diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 9eeb26831c..0e24fbd24d 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -220,6 +220,14 @@ class UrlHelperTest < ActionView::TestCase ) end + def test_link_tag_using_delete_javascript_and_href_and_confirm + assert_dom_equal( + "<a href='\#' onclick=\"if (confirm('Are you serious?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = 'http://www.example.com';var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;\">Destroy</a>", + link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#', :confirm => "Are you serious?"), + "When specifying url, form should be generated with it, but not this.href" + ) + end + def test_link_tag_using_post_javascript_and_popup assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } end -- cgit v1.2.3 From ca92d44e7637ae6d28d6b88b67873d2795290cb5 Mon Sep 17 00:00:00 2001 From: Andrew Moreland <andy@andymo.org> Date: Sun, 9 Aug 2009 15:38:30 -0700 Subject: Support deep-merging HashWithIndifferentAccess. [#2732 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/core_ext/hash/deep_merge.rb | 9 +++++---- activesupport/test/core_ext/hash_ext_test.rb | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index ffde34a741..24d0a2a481 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -1,11 +1,12 @@ class Hash # Returns a new hash with +self+ and +other_hash+ merged recursively. def deep_merge(other_hash) - merge(other_hash) do |key, oldval, newval| - oldval = oldval.to_hash if oldval.respond_to?(:to_hash) - newval = newval.to_hash if newval.respond_to?(:to_hash) - oldval.is_a?( Hash ) && newval.is_a?( Hash ) ? oldval.deep_merge(newval) : newval + target = dup + other_hash.each_pair do |k,v| + tv = target[k] + target[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v end + target end # Returns a new hash with +self+ and +other_hash+ merged recursively. diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8b0f3fc33c..eb4c37aaf0 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -265,6 +265,18 @@ class HashExtTest < Test::Unit::TestCase assert_equal expected, hash_1 end + def test_deep_merge_on_indifferent_access + hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }) + hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }) + hash_3 = { :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } } + expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } } + assert_equal expected, hash_1.deep_merge(hash_2) + assert_equal expected, hash_1.deep_merge(hash_3) + + hash_1.deep_merge!(hash_2) + assert_equal expected, hash_1 + end + def test_reverse_merge defaults = { :a => "x", :b => "y", :c => 10 }.freeze options = { :a => 1, :b => 2 } -- cgit v1.2.3 From e4ceea3795ecc7adcec28a1b9d63782be1401256 Mon Sep 17 00:00:00 2001 From: jeem <jeem@hughesorama.com> Date: Sun, 9 Aug 2009 20:04:15 -0500 Subject: make private_and_public_methods unmemoizable [#2372 state:resolved] Signed-off-by: Joshua Peek <josh@joshpeek.com> --- activesupport/lib/active_support/memoizable.rb | 2 +- .../flush_cache_on_private_memoization_test.rb | 44 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 activesupport/test/flush_cache_on_private_memoization_test.rb diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index fa6db683d4..7724b9d88b 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -59,7 +59,7 @@ module ActiveSupport def flush_cache(*syms, &block) syms.each do |sym| - methods.each do |m| + (methods + private_methods + protected_methods).each do |m| if m.to_s =~ /^_unmemoized_(#{sym})/ ivar = ActiveSupport::Memoizable.memoized_ivar_for($1) instance_variable_get(ivar).clear if instance_variable_defined?(ivar) diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb new file mode 100644 index 0000000000..ddbd05b0e0 --- /dev/null +++ b/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -0,0 +1,44 @@ +require 'rubygems' +require 'activesupport' +require 'test/unit' + +class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase + extend ActiveSupport::Memoizable + + def test_public + assert_method_unmemoizable :pub + end + + def test_protected + assert_method_unmemoizable :prot + end + + def test_private + assert_method_unmemoizable :priv + end + + def pub; rand end + memoize :pub + + protected + + def prot; rand end + memoize :prot + + private + + def priv; rand end + memoize :priv + + def assert_method_unmemoizable(meth, message=nil) + full_message = build_message(message, "<?> not unmemoizable.\n", meth) + assert_block(full_message) do + a = send meth + b = send meth + unmemoize_all + c = send meth + a == b && a != c + end + end + +end \ No newline at end of file -- cgit v1.2.3 From e06a0b03c8ba29f4b05a35560645814ac88aefbe Mon Sep 17 00:00:00 2001 From: railsbob <anup.narkhede@gmail.com> Date: Sat, 8 Aug 2009 23:48:11 +0100 Subject: has_many :through create should not raise validation errors [#2934 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../associations/has_many_through_association.rb | 6 ++++- .../has_many_through_associations_test.rb | 29 +++++++++++++++++++++- activerecord/test/cases/reflection_test.rb | 4 +-- activerecord/test/models/company.rb | 2 ++ activerecord/test/models/contract.rb | 4 +++ activerecord/test/schema/schema.rb | 4 +++ 6 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 activerecord/test/models/contract.rb 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 f4507c979c..292da58831 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -20,7 +20,11 @@ module ActiveRecord ensure_owner_is_not_new transaction do - self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association) + object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association + raise_on_type_mismatch(object) + add_record_to_target_with_callbacks(object) do |r| + insert_record(object, false) + end object end end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 58ce2ca3a5..59985374d3 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -11,9 +11,12 @@ require 'models/author' require 'models/owner' require 'models/pet' require 'models/toy' +require 'models/contract' +require 'models/company' +require 'models/developer' class HasManyThroughAssociationsTest < ActiveRecord::TestCase - fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references + fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies def test_associate_existing assert_queries(2) { posts(:thinking);people(:david) } @@ -176,6 +179,30 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } end + def test_associate_with_create_and_invalid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create(:name => '0') } + assert_equal peeps, companies(:first_firm).developers.count + end + + def test_associate_with_create_and_valid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create(:name => 'developer') } + assert_equal peeps + 1, companies(:first_firm).developers.count + end + + def test_associate_with_create_bang_and_invalid_options + peeps = companies(:first_firm).developers.count + assert_raises(ActiveRecord::RecordInvalid) { companies(:first_firm).developers.create!(:name => '0') } + assert_equal peeps, companies(:first_firm).developers.count + end + + def test_associate_with_create_bang_and_valid_options + peeps = companies(:first_firm).developers.count + assert_nothing_raised { companies(:first_firm).developers.create!(:name => 'developer') } + assert_equal peeps + 1, companies(:first_firm).developers.count + end + def test_clear_associations assert_queries(2) { posts(:welcome);posts(:welcome).people(true) } diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 4083b990d9..a164f5e060 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -176,8 +176,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 29, Firm.reflect_on_all_associations.size - assert_equal 22, Firm.reflect_on_all_associations(:has_many).size + assert_equal 31, Firm.reflect_on_all_associations.size + assert_equal 24, Firm.reflect_on_all_associations(:has_many).size assert_equal 7, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 1c05e523e0..ab09f88a9f 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -9,6 +9,8 @@ class Company < AbstractCompany validates_presence_of :name has_one :dummy_account, :foreign_key => "firm_id", :class_name => "Account" + has_many :contracts + has_many :developers, :through => :contracts def arbitrary_method "I am Jack's profound disappointment" diff --git a/activerecord/test/models/contract.rb b/activerecord/test/models/contract.rb new file mode 100644 index 0000000000..606c99cd4e --- /dev/null +++ b/activerecord/test/models/contract.rb @@ -0,0 +1,4 @@ +class Contract < ActiveRecord::Base + belongs_to :company + belongs_to :developer +end \ No newline at end of file diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 5f60d5e137..9ab4cf6f43 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -131,6 +131,10 @@ ActiveRecord::Schema.define do t.integer :extendedWarranty, :null => false end + create_table :contracts, :force => true do |t| + t.integer :developer_id + t.integer :company_id + end create_table :customers, :force => true do |t| t.string :name -- cgit v1.2.3 From 4dda9b644df5e4386f693a4b7bd00fe787f41a28 Mon Sep 17 00:00:00 2001 From: Erik Ostrom <erik@echographia.com> Date: Sun, 9 Aug 2009 18:57:25 -0700 Subject: Add rindex to ActiveSupport::Multibyte::Chars. Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/multibyte/chars.rb | 13 +++++++++++++ activesupport/test/multibyte_chars_test.rb | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 96ed35f0e0..3c16999e43 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -210,6 +210,19 @@ module ActiveSupport #:nodoc: index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end + # Returns the position _needle_ in the string, counting in + # codepoints, searching backward from _offset_ or the end of the + # string. Returns +nil+ if _needle_ isn't found. + # + # Example: + # 'Café périferôl'.mb_chars.rindex('é') #=> 5 + # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13 + def rindex(needle, offset=nil) + offset ||= length + index = @wrapped_string.rindex(needle, offset) + index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil + end + # Like <tt>String#[]=</tt>, except instead of byte offsets you specify character offsets. # # Example: diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 661b33cc57..44548982e3 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -234,6 +234,13 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_equal 3, @chars.index('わ') end + def test_rindex_should_return_character_offset + assert_nil @chars.rindex('u') + assert_equal 1, @chars.rindex('に') + assert_equal 6, 'Café périferôl'.mb_chars.rindex('é') + assert_equal 12, 'Café périferôl'.mb_chars.rindex(/\w/u) + end + def test_indexed_insert_should_take_character_offsets @chars[2] = 'a' assert_equal 'こにaわ', @chars -- cgit v1.2.3 From 279b785839d87aac9caf46c261595fc0965d85a2 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 19:32:38 -0700 Subject: pare down core_ext dependency --- activesupport/test/multibyte_chars_test.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 44548982e3..f3c7f50458 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -1,5 +1,4 @@ # encoding: utf-8 - require 'abstract_unit' require 'multibyte_test_helpers' @@ -184,7 +183,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase end def test_sortability - words = %w(builder armor zebra).map(&:mb_chars).sort + words = %w(builder armor zebra).sort_by { |s| s.mb_chars } assert_equal %w(armor builder zebra), words end -- cgit v1.2.3 From 84d24cdae8269a161d36e00009c043bbd102cbbd Mon Sep 17 00:00:00 2001 From: Joshua Nichols <josh@technicalpickles.com> Date: Sat, 8 Aug 2009 17:19:47 -0400 Subject: Only load db/schema.rb if it exists; otherwise, display a message to run db:migrate or remove active_record in environment.rb. [#3012 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- railties/lib/tasks/databases.rake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index b82341ba94..687bc00b3c 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -292,7 +292,11 @@ namespace :db do desc "Load a schema.rb file into the database" task :load => :environment do file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb" - load(file) + if File.exists?(file) + load(file) + else + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + end end end -- cgit v1.2.3 From 82dd725fc195eb52eea9cbde9530ab9dff122e32 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist <s@sikachu.com> Date: Fri, 4 Jul 2008 14:04:50 +0700 Subject: Fix that irregular plural inflections should not be double-pluralized: 'people'.pluralize should return 'people' not 'peoples'. [#1183 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/inflector.rb | 3 +++ activesupport/test/inflector_test.rb | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 4ee96b13b4..67aea2782f 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -69,10 +69,13 @@ module ActiveSupport @uncountables.delete(plural) if singular[0,1].upcase == plural[0,1].upcase plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) + plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1]) singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) else plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) + plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) + plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 7d1554910e..76bdc0e973 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -256,6 +256,16 @@ class InflectorTest < Test::Unit::TestCase end end + Irregularities.each do |irregularity| + singular, plural = *irregularity + ActiveSupport::Inflector.inflections do |inflect| + define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do + inflect.irregular(singular, plural) + assert_equal plural, ActiveSupport::Inflector.pluralize(plural) + end + end + end + [ :all, [] ].each do |scope| ActiveSupport::Inflector.inflections do |inflect| define_method("test_clear_inflections_with_#{scope.kind_of?(Array) ? "no_arguments" : scope}") do -- cgit v1.2.3 From 734e903af5913342c65d4c294e45f9095fa89986 Mon Sep 17 00:00:00 2001 From: Joshua Peek <josh@joshpeek.com> Date: Sun, 9 Aug 2009 22:38:50 -0500 Subject: Deprecate router generation "best match" sorting --- .../lib/action_controller/routing/resources.rb | 4 +-- .../lib/action_controller/routing/route_set.rb | 32 +++++++++++++++++++--- actionpack/test/controller/caching_test.rb | 2 +- actionpack/test/controller/routing_test.rb | 8 ++++-- actionpack/test/controller/url_rewriter_test.rb | 6 ++-- 5 files changed, 39 insertions(+), 13 deletions(-) diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb index 7fd3ffd3f1..4862cf7115 100644 --- a/actionpack/lib/action_controller/routing/resources.rb +++ b/actionpack/lib/action_controller/routing/resources.rb @@ -529,13 +529,13 @@ module ActionController resource = Resource.new(entities, options) with_options :controller => resource.controller do |map| + map_associations(resource, options) + map_collection_actions(map, resource) map_default_collection_actions(map, resource) map_new_actions(map, resource) map_member_actions(map, resource) - map_associations(resource, options) - if block_given? with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 0c499f3b46..09f6024d39 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -407,9 +407,24 @@ module ActionController # don't use the recalled keys when determining which routes to check routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] - routes.each do |route| + routes[1].each_with_index do |route, index| results = route.__send__(method, options, merged, expire_on) - return results if results && (!results.is_a?(Array) || results.first) + if results && (!results.is_a?(Array) || results.first) + + # Compare results with Rails 3.0 behavior + if routes[0][index] != route + routes[0].each do |route2| + new_results = route2.__send__(method, options, merged, expire_on) + if new_results && (!new_results.is_a?(Array) || new_results.first) + ActiveSupport::Deprecation.warn "The URL you generated will use the first matching route in routes.rb rather than the \"best\" match. " + + "In Rails 3.0 #{new_results} would of been generated instead of #{results}" + break + end + end + end + + return results + end end end @@ -448,7 +463,10 @@ module ActionController @routes_by_controller ||= Hash.new do |controller_hash, controller| controller_hash[controller] = Hash.new do |action_hash, action| action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) + key_hash[keys] = [ + routes_for_controller_and_action_and_keys(controller, action, keys), + deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) + ] end end end @@ -460,10 +478,16 @@ module ActionController merged = options if expire_on[:controller] action = merged[:action] || 'index' - routes_by_controller[controller][action][merged.keys] + routes_by_controller[controller][action][merged.keys][1] end def routes_for_controller_and_action_and_keys(controller, action, keys) + routes.select do |route| + route.matches_controller_and_action? controller, action + end + end + + def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys) selected = routes.select do |route| route.matches_controller_and_action? controller, action end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 68529cc8f7..346fa09414 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -51,7 +51,7 @@ class PageCachingTest < ActionController::TestCase ActionController::Routing::Routes.clear! ActionController::Routing::Routes.draw do |map| - map.main '', :controller => 'posts' + map.main '', :controller => 'posts', :format => nil map.formatted_posts 'posts.:format', :controller => 'posts' map.resources :posts map.connect ':controller/:action/:id' diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5f9ae6292c..2534c232c7 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1610,7 +1610,7 @@ class RouteTest < Test::Unit::TestCase end end -class RouteSetTest < Test::Unit::TestCase +class RouteSetTest < ActiveSupport::TestCase def set @set ||= ROUTING::RouteSet.new end @@ -2191,8 +2191,10 @@ class RouteSetTest < Test::Unit::TestCase map.connect "/ws/people", :controller => "people", :action => "index", :ws => true end - url = set.generate(:controller => "people", :action => "index", :ws => true) - assert_equal "/ws/people", url + assert_deprecated { + url = set.generate(:controller => "people", :action => "index", :ws => true) + assert_equal "/ws/people", url + } end def test_generate_changes_controller_module diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 863f8414c5..0e149cf8ae 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -304,7 +304,7 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes_with_nil_keys ActionController::Routing::Routes.clear! ActionController::Routing::Routes.draw do |map| - map.main '', :controller => 'posts' + map.main '', :controller => 'posts', :format => nil map.resources :posts map.connect ':controller/:action/:id' end @@ -314,9 +314,9 @@ class UrlWriterTests < ActionController::TestCase controller = kls.new params = {:action => :index, :controller => :posts, :format => :xml} - assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) + assert_equal("http://www.basecamphq.com/posts.xml", controller.send(:url_for, params)) params[:format] = nil - assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) + assert_equal("http://www.basecamphq.com/", controller.send(:url_for, params)) ensure ActionController::Routing::Routes.load! end -- cgit v1.2.3 From 5704ecffadffa04981ec5999c362cb691d4a676e Mon Sep 17 00:00:00 2001 From: Jatinder Singh <jatinder.saundh@gmail.com> Date: Sun, 9 Aug 2009 20:43:56 -0700 Subject: AR should respect default values for MySQL BINARY and VARBINARY columns. [#1273 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../connection_adapters/mysql_adapter.rb | 4 +-- activerecord/test/cases/column_definition_test.rb | 34 ++++++++++++++++++++++ 2 files changed, 36 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 2812dbb522..c1f44b51f0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -76,7 +76,7 @@ module ActiveRecord module ConnectionAdapters class MysqlColumn < Column #:nodoc: def extract_default(default) - if type == :binary || type == :text + if sql_type =~ /blob/i || type == :text if default.blank? return null ? nil : '' else @@ -90,7 +90,7 @@ module ActiveRecord end def has_default? - return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns + return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns super end diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 98abc8eac8..fc9a0ac96e 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -33,4 +33,38 @@ class ColumnDefinitionTest < ActiveRecord::TestCase column.limit, column.precision, column.scale, column.default, column.null) assert_equal %Q{title varchar(20) DEFAULT 'Hello' NOT NULL}, column_def.to_sql end + + if current_adapter?(:MysqlAdapter) + def test_should_set_default_for_mysql_binary_data_types + binary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "binary(1)") + assert_equal "a", binary_column.default + + varbinary_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "varbinary(1)") + assert_equal "a", varbinary_column.default + end + + def test_should_not_set_default_for_blob_and_text_data_types + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "a", "blob") + end + + assert_raise ArgumentError do + ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", "Hello", "text") + end + + text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text") + assert_equal nil, text_column.default + + not_null_text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text", false) + assert_equal "", not_null_text_column.default + end + + def test_has_default_should_return_false_for_blog_and_test_data_types + blob_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "blob") + assert !blob_column.has_default? + + text_column = ActiveRecord::ConnectionAdapters::MysqlColumn.new("title", nil, "text") + assert !text_column.has_default? + end + end end -- cgit v1.2.3 From 7e3364ac4634f7017305c4bc725710ab0e7ba4c2 Mon Sep 17 00:00:00 2001 From: Gabe da Silveira <gabe@websaviour.com> Date: Sun, 9 Aug 2009 13:16:57 -0700 Subject: Fix that counter_cache breaks with has_many :dependent => :nullify. [#1196 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../associations/has_many_association.rb | 1 + .../associations/has_many_associations_test.rb | 25 ++++++++++++++++++++++ .../test/cases/associations/join_model_test.rb | 6 +++--- activerecord/test/fixtures/posts.yml | 3 +++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index e4b631bc54..73d3c23cd3 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -74,6 +74,7 @@ module ActiveRecord "#{@reflection.primary_key_name} = NULL", "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})" ) + @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter? end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 6edb52092f..ee39cf695e 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -516,6 +516,23 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 0, new_firm.clients_of_firm.size end + def test_deleting_updates_counter_cache + topic = Topic.first + assert_equal topic.replies.to_a.size, topic.replies_count + + topic.replies.delete(topic.replies.first) + topic.reload + assert_equal topic.replies.to_a.size, topic.replies_count + end + + def test_deleting_updates_counter_cache_without_dependent_destroy + post = posts(:welcome) + + assert_difference "post.reload.taggings_count", -1 do + post.taggings.delete(post.taggings.first) + end + end + def test_deleting_a_collection force_signal37_to_load_all_clients_of_firm companies(:first_firm).clients_of_firm.create("name" => "Another Client") @@ -561,6 +578,14 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end + def test_clearing_updates_counter_cache + topic = Topic.first + + topic.replies.clear + topic.reload + assert_equal 0, topic.replies_count + end + def test_clearing_a_dependent_association_collection firm = companies(:first_firm) client_id = firm.dependent_clients_of_firm.first.id diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 4907cfe2b0..c035600e69 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -319,11 +319,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_belongs_to_polymorphic_with_counter_cache - assert_equal 0, posts(:welcome)[:taggings_count] + assert_equal 1, posts(:welcome)[:taggings_count] tagging = posts(:welcome).taggings.create(:tag => tags(:general)) - assert_equal 1, posts(:welcome, :reload)[:taggings_count] + assert_equal 2, posts(:welcome, :reload)[:taggings_count] tagging.destroy - assert posts(:welcome, :reload)[:taggings_count].zero? + assert_equal 1, posts(:welcome, :reload)[:taggings_count] end def test_unavailable_through_reflection diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml index 92e5d1908f..f817493190 100644 --- a/activerecord/test/fixtures/posts.yml +++ b/activerecord/test/fixtures/posts.yml @@ -4,6 +4,7 @@ welcome: title: Welcome to the weblog body: Such a lovely day comments_count: 2 + taggings_count: 1 type: Post thinking: @@ -11,6 +12,8 @@ thinking: author_id: 1 title: So I was thinking body: Like I hopefully always am + comments_count: 1 + taggings_count: 1 type: SpecialPost authorless: -- cgit v1.2.3 From 47f7c936a276269b8bff23bd992fa44e6868abc0 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper <jeremy@bitsweat.net> Date: Sun, 9 Aug 2009 21:30:55 -0700 Subject: Fix test dependency on taggings --- activerecord/test/cases/associations/has_many_associations_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ee39cf695e..f7178f2c5e 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -10,11 +10,12 @@ require 'models/author' require 'models/comment' require 'models/person' require 'models/reader' +require 'models/tagging' class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :author_addresses, - :people, :posts, :readers + :people, :posts, :readers, :taggings def setup Client.destroyed_client_ids.clear -- cgit v1.2.3 From d2d464e26e9d41b7299675e145ae1455cce37dd4 Mon Sep 17 00:00:00 2001 From: Morgan Schweers <mrs@jobster.com> Date: Wed, 20 Aug 2008 11:32:20 -0700 Subject: Fix that creating a table whose primary key prefix type is :table_name generates an incorrectly pluralized primary key. [#872 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../active_record/connection_adapters/abstract/schema_statements.rb | 2 +- activerecord/test/cases/migration_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 2473c772e3..94d15fdb69 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -107,7 +107,7 @@ module ActiveRecord # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) table_definition = TableDefinition.new(self) - table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false + table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false yield table_definition if block_given? diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f0f21615e0..332a23a2ce 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -224,7 +224,7 @@ if ActiveRecord::Base.connection.supports_migrations? t.column :foo, :string end - assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort + assert_equal %w(foo testing_id), Person.connection.columns(:testings).map { |c| c.name }.sort ensure Person.connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil @@ -237,7 +237,7 @@ if ActiveRecord::Base.connection.supports_migrations? t.column :foo, :string end - assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort + assert_equal %w(foo testingid), Person.connection.columns(:testings).map { |c| c.name }.sort ensure Person.connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil -- cgit v1.2.3 From f0602214e0ff4638fafb819a7ffbd4ce0e37efb7 Mon Sep 17 00:00:00 2001 From: Jaime Bellmyer <online@bellmyer.com> Date: Thu, 26 Feb 2009 09:48:58 -0600 Subject: raises exception (ActiveRecord::ConfigurationError with message) on habtm association creation if join table contains a primary key Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../has_and_belongs_to_many_association.rb | 4 ++ .../connection_adapters/abstract_adapter.rb | 7 ++++ .../connection_adapters/mysql_adapter.rb | 10 +++++ .../connection_adapters/postgresql_adapter.rb | 11 ++++++ .../connection_adapters/sqlite_adapter.rb | 4 ++ .../cases/associations/habtm_join_table_test.rb | 45 ++++++++++++++++++++++ activerecord/test/cases/pk_test.rb | 18 +++++++++ 7 files changed, 99 insertions(+) create mode 100644 activerecord/test/cases/associations/habtm_join_table_test.rb diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index fd23e59e82..b5d8de2544 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -29,6 +29,10 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) + if ActiveRecord::Base.connection.supports_primary_key? && ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]) + raise ActiveRecord::ConfigurationError, "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." + end + if record.new_record? if force record.save! diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c533d4cdb6..fab70f34b9 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -56,6 +56,13 @@ module ActiveRecord false end + # Can this adapter determine the primary key for tables not attached + # to an ActiveRecord class, such as join tables? Backend specific, as + # the abstract adapter always returns +false+. + def supports_primary_key? + false + end + # Does this adapter support using DISTINCT within COUNT? This is +true+ # for all adapters except sqlite. def supports_count_distinct? diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c1f44b51f0..4edb64c2c0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -208,6 +208,10 @@ module ActiveRecord true end + def supports_primary_key? #:nodoc: + true + end + def supports_savepoints? #:nodoc: true end @@ -550,6 +554,12 @@ module ActiveRecord keys.length == 1 ? [keys.first, nil] : nil end + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + def case_sensitive_equality_operator "= BINARY" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e77ae93c21..224c007f3c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -250,6 +250,11 @@ module ActiveRecord true end + # Does PostgreSQL support finding primary key on non-ActiveRecord tables? + def supports_primary_key? #:nodoc: + true + end + # Does PostgreSQL support standard conforming strings? def supports_standard_conforming_strings? # Temporarily set the client message level above error to prevent unintentional @@ -811,6 +816,12 @@ module ActiveRecord nil end + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + # Renames a table. def rename_table(name, new_name) execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index c0f5046bff..0c18c38642 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -101,6 +101,10 @@ module ActiveRecord true end + def supports_primary_key? #:nodoc: + true + end + def requires_reloading? true end diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb new file mode 100644 index 0000000000..0586df1716 --- /dev/null +++ b/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -0,0 +1,45 @@ +require 'cases/helper' + +class MyReader < ActiveRecord::Base + has_and_belongs_to_many :my_books +end + +class MyBook < ActiveRecord::Base + has_and_belongs_to_many :my_readers +end + +class JoinTableTest < ActiveRecord::TestCase + def setup + ActiveRecord::Base.connection.create_table :my_books, :force => true do |t| + t.string :name + end + assert ActiveRecord::Base.connection.table_exists?(:my_books) + + ActiveRecord::Base.connection.create_table :my_readers, :force => true do |t| + t.string :name + end + assert ActiveRecord::Base.connection.table_exists?(:my_readers) + + ActiveRecord::Base.connection.create_table :my_books_my_readers, :force => true do |t| + t.integer :my_book_id + t.integer :my_reader_id + end + assert ActiveRecord::Base.connection.table_exists?(:my_books_my_readers) + end + + def teardown + ActiveRecord::Base.connection.drop_table :my_books + ActiveRecord::Base.connection.drop_table :my_readers + ActiveRecord::Base.connection.drop_table :my_books_my_readers + end + + uses_transaction :test_should_raise_exception_when_join_table_has_a_primary_key + def test_should_raise_exception_when_join_table_has_a_primary_key + if ActiveRecord::Base.connection.supports_primary_key? + assert_raise ActiveRecord::ConfigurationError do + jaime = MyReader.create(:name=>"Jaime") + jaime.my_books << MyBook.create(:name=>'Great Expectations') + end + end + end +end diff --git a/activerecord/test/cases/pk_test.rb b/activerecord/test/cases/pk_test.rb index 948a570b93..c121e0aa0f 100644 --- a/activerecord/test/cases/pk_test.rb +++ b/activerecord/test/cases/pk_test.rb @@ -98,4 +98,22 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_instance_destroy_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).destroy } end + + def test_supports_primary_key + assert_nothing_raised NoMethodError do + ActiveRecord::Base.connection.supports_primary_key? + end + end + + def test_primary_key_returns_value_if_it_exists + if ActiveRecord::Base.connection.supports_primary_key? + assert_equal 'id', ActiveRecord::Base.connection.primary_key('developers') + end + end + + def test_primary_key_returns_nil_if_it_does_not_exist + if ActiveRecord::Base.connection.supports_primary_key? + assert_nil ActiveRecord::Base.connection.primary_key('developers_projects') + end + end end -- cgit v1.2.3 From 9c1bac0b7fcb627640db6824dca3e6e829a3c3e6 Mon Sep 17 00:00:00 2001 From: Jaime Bellmyer <online@bellmyer.com> Date: Mon, 9 Mar 2009 13:23:27 -0500 Subject: raises an exception on habtm join table inserts if join table contains a primary key. Caches this check to save time on subsequent inserts. [#2086 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../associations/has_and_belongs_to_many_association.rb | 16 ++++++++++++++-- .../test/cases/associations/habtm_join_table_test.rb | 13 ++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index b5d8de2544..d91c555dad 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -1,6 +1,11 @@ module ActiveRecord module Associations class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc: + def initialize(owner, reflection) + super + @primary_key_list = {} + end + def create(attributes = {}) create_record(attributes) { |record| insert_record(record) } end @@ -17,6 +22,12 @@ module ActiveRecord @reflection.reset_column_information end + def has_primary_key? + return @has_primary_key unless @has_primary_key.nil? + @has_primary_key = (ActiveRecord::Base.connection.supports_primary_key? && + ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table])) + end + protected def construct_find_options!(options) options[:joins] = @join_sql @@ -29,8 +40,9 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if ActiveRecord::Base.connection.supports_primary_key? && ActiveRecord::Base.connection.primary_key(@reflection.options[:join_table]) - raise ActiveRecord::ConfigurationError, "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." + if has_primary_key? + raise ActiveRecord::ConfigurationError, + "Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})." end if record.new_record? diff --git a/activerecord/test/cases/associations/habtm_join_table_test.rb b/activerecord/test/cases/associations/habtm_join_table_test.rb index 0586df1716..bf3e04c3eb 100644 --- a/activerecord/test/cases/associations/habtm_join_table_test.rb +++ b/activerecord/test/cases/associations/habtm_join_table_test.rb @@ -8,7 +8,7 @@ class MyBook < ActiveRecord::Base has_and_belongs_to_many :my_readers end -class JoinTableTest < ActiveRecord::TestCase +class HabtmJoinTableTest < ActiveRecord::TestCase def setup ActiveRecord::Base.connection.create_table :my_books, :force => true do |t| t.string :name @@ -42,4 +42,15 @@ class JoinTableTest < ActiveRecord::TestCase end end end + + uses_transaction :test_should_cache_result_of_primary_key_check + def test_should_cache_result_of_primary_key_check + if ActiveRecord::Base.connection.supports_primary_key? + ActiveRecord::Base.connection.stubs(:primary_key).with('my_books_my_readers').returns(false).once + weaz = MyReader.create(:name=>'Weaz') + + weaz.my_books << MyBook.create(:name=>'Great Expectations') + weaz.my_books << MyBook.create(:name=>'Greater Expectations') + end + end end -- cgit v1.2.3 From 0c391b46fb39b697bbae1493caade23e2ddbd8a6 Mon Sep 17 00:00:00 2001 From: Leonardo Borges <leonardoborges.rj@gmail.com> Date: Sun, 9 Aug 2009 12:56:25 +0400 Subject: PostgreSQL: XML datatype support [#1874 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/CHANGELOG | 2 ++ .../abstract/schema_definitions.rb | 15 ++++++++++ .../connection_adapters/postgresql_adapter.rb | 34 +++++++++++++--------- activerecord/test/cases/migration_test.rb | 28 ++++++++++++------ activerecord/test/cases/schema_dumper_test.rb | 12 +++++++- .../test/schema/postgresql_specific_schema.rb | 15 ++++++++-- 6 files changed, 81 insertions(+), 25 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 9adc6b887f..d40251b9ca 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* PostgreSQL: XML datatype support. #1874 [Leonardo Borges] + * quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing] * SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] 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 f346e3ebc8..520f3c8c0c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -315,6 +315,20 @@ module ActiveRecord @base = base end + #Handles non supported datatypes - e.g. XML + def method_missing(symbol, *args) + if symbol.to_s == 'xml' + xml_column_fallback(args) + end + end + + def xml_column_fallback(*args) + case @base.adapter_name.downcase + when 'sqlite', 'mysql' + options = args.extract_options! + column(args[0], :text, options) + end + end # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name) @@ -705,3 +719,4 @@ module ActiveRecord end end + diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 224c007f3c..84cf1ad9fd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -40,6 +40,12 @@ module ActiveRecord end module ConnectionAdapters + class TableDefinition + def xml(*args) + options = args.extract_options! + column(args[0], 'xml', options) + end + end # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: # Instantiates a new PostgreSQL column definition in a table. @@ -68,7 +74,7 @@ module ActiveRecord # depending on the server specifics super end - + # Maps PostgreSQL-specific data types to logical Rails types. def simplified_type(field_type) case field_type @@ -100,10 +106,10 @@ module ActiveRecord :string # XML type when /^xml$/ - :string + :xml # Arrays when /^\D+\[\]$/ - :string + :string # Object identifier types when /^oid$/ :integer @@ -112,7 +118,7 @@ module ActiveRecord super end end - + # Extracts the value from a PostgreSQL column default definition. def self.extract_value_from_default(default) case default @@ -195,7 +201,8 @@ module ActiveRecord :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "bytea" }, - :boolean => { :name => "boolean" } + :boolean => { :name => "boolean" }, + :xml => { :name => "xml" } } # Returns 'PostgreSQL' as adapter name for identification purposes. @@ -278,7 +285,7 @@ module ActiveRecord def supports_ddl_transactions? true end - + def supports_savepoints? true end @@ -370,7 +377,7 @@ module ActiveRecord if value.kind_of?(String) && column && column.type == :binary "#{quoted_string_prefix}'#{escape_bytea(value)}'" elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ - "xml '#{quote_string(value)}'" + "xml E'#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ # Not truly string input, so doesn't require (or allow) escape string syntax. "'#{value.to_s}'" @@ -569,7 +576,7 @@ module ActiveRecord def rollback_db_transaction execute "ROLLBACK" end - + if defined?(PGconn::PQTRANS_IDLE) # The ruby-pg driver supports inspecting the transaction status, # while the ruby-postgres driver does not. @@ -920,18 +927,18 @@ module ActiveRecord sql = "DISTINCT ON (#{columns}) #{columns}, " sql << order_columns * ', ' end - + # Returns an ORDER BY clause for the passed order option. - # + # # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this # by wrapping the +sql+ string as a sub-select and ordering in that query. def add_order_by_for_association_limiting!(sql, options) #:nodoc: return sql if options[:order].blank? - + order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?) order.map! { |s| 'DESC' if s =~ /\bdesc$/i } order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ') - + sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}" end @@ -1055,7 +1062,7 @@ module ActiveRecord if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID # Because money output is formatted according to the locale, there are two # cases to consider (note the decimal separators): - # (1) $12,345,678.12 + # (1) $12,345,678.12 # (2) $12.345.678,12 case column = row[cell_index] when /^-?\D+[\d,]+\.\d{2}$/ # (1) @@ -1115,3 +1122,4 @@ module ActiveRecord end end end + diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 332a23a2ce..efa9ff3648 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -396,7 +396,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 9, wealth_column.precision assert_equal 7, wealth_column.scale end - + def test_native_types Person.delete_all Person.connection.add_column "people", "last_name", :string @@ -975,9 +975,9 @@ if ActiveRecord::Base.connection.supports_migrations? def test_migrator_one_down ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") - + ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) - + Person.reset_column_information assert Person.column_methods_hash.include?(:last_name) assert !Reminder.table_exists? @@ -1118,20 +1118,20 @@ if ActiveRecord::Base.connection.supports_migrations? assert Reminder.create("content" => "hello world", "remind_at" => Time.now) assert_equal "hello world", Reminder.find(:first).content end - + def test_migrator_rollback ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") assert_equal(3, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(2, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(1, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(0, ActiveRecord::Migrator.current_version) - + ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") assert_equal(0, ActiveRecord::Migrator.current_version) end @@ -1294,7 +1294,7 @@ if ActiveRecord::Base.connection.supports_migrations? end end - + class SexyMigrationsTest < ActiveRecord::TestCase def test_references_column_type_adds_id with_new_table do |t| @@ -1350,6 +1350,15 @@ if ActiveRecord::Base.connection.supports_migrations? end end + if current_adapter?(:PostgreSQLAdapter) + def test_xml_creates_xml_column + with_new_table do |t| + t.expects(:column).with(:data, 'xml', {}) + t.xml :data + end + end + end + protected def with_new_table Person.connection.create_table :delete_me, :force => true do |t| @@ -1567,3 +1576,4 @@ if ActiveRecord::Base.connection.supports_migrations? end end end + diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index e6a77f626b..1c43e3c5b5 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -161,7 +161,7 @@ class SchemaDumperTest < ActiveRecord::TestCase index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition end - + def test_schema_dump_should_honor_nonstandard_primary_keys output = standard_dump match = output.match(%r{create_table "movies"(.*)do}) @@ -196,6 +196,15 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output end + if current_adapter?(:PostgreSQLAdapter) + def test_schema_dump_includes_xml_shorthand_definition + output = standard_dump + if %r{create_table "postgresql_xml_data_type"} =~ output + assert_match %r{t.xml "data"}, output + end + end + end + def test_schema_dump_keeps_large_precision_integer_columns_as_decimal output = standard_dump # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers @@ -214,3 +223,4 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" end end + diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 576a4d03c6..3d8911bfe9 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -1,7 +1,7 @@ ActiveRecord::Schema.define do %w(postgresql_arrays postgresql_moneys postgresql_numbers postgresql_times postgresql_network_addresses postgresql_bit_strings - postgresql_oids defaults geometrics).each do |table_name| + postgresql_oids postgresql_xml_data_type defaults geometrics).each do |table_name| execute "DROP TABLE IF EXISTS #{quote_table_name table_name}" end @@ -100,4 +100,15 @@ _SQL obj_id OID ); _SQL -end \ No newline at end of file + + begin + execute <<_SQL + CREATE TABLE postgresql_xml_data_type ( + id SERIAL PRIMARY KEY, + data xml + ); +_SQL +rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table + end +end + -- cgit v1.2.3 From b3381cacaf7735ec6eb108e378ba255ebf6ffb14 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard <daniel.sheppard@gmail.com> Date: Sun, 9 Aug 2009 11:27:17 +0400 Subject: Fix that JSON parser fails to read escaped backslashes. [#973 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/json/backends/yaml.rb | 5 ++++- activesupport/test/json/decoding_test.rb | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb index 92dd31cfbc..59d2c37e40 100644 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -20,7 +20,7 @@ module ActiveSupport rescue ArgumentError => e raise ParseError, "Invalid JSON string" end - + protected # Ensure that ":" and "," are always followed by a space def convert_json_to_yaml(json) #:nodoc: @@ -42,6 +42,8 @@ module ActiveSupport end when ":","," marks << scanner.pos - 1 unless quoting + when "\\" + scanner.skip(/\\/) end end @@ -89,3 +91,4 @@ module ActiveSupport end end end + diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 4129a4fab6..05e420ae36 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -14,10 +14,10 @@ class TestJSONDecoding < ActiveSupport::TestCase %({"a": "a's, b's and c's", "b": "5,000"}) => {"a" => "a's, b's and c's", "b" => "5,000"}, # multibyte %({"matzue": "松江", "asakusa": "浅草"}) => {"matzue" => "松江", "asakusa" => "浅草"}, - %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, - %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, + %({"a": "2007-01-01"}) => {'a' => Date.new(2007, 1, 1)}, + %({"a": "2007-01-01 01:12:34 Z"}) => {'a' => Time.utc(2007, 1, 1, 1, 12, 34)}, # no time zone - %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, + %({"a": "2007-01-01 01:12:34"}) => {'a' => "2007-01-01 01:12:34"}, # needs to be *exact* %({"a": " 2007-01-01 01:12:34 Z "}) => {'a' => " 2007-01-01 01:12:34 Z "}, %({"a": "2007-01-01 : it's your birthday"}) => {'a' => "2007-01-01 : it's your birthday"}, @@ -29,6 +29,7 @@ class TestJSONDecoding < ActiveSupport::TestCase %({"a": null}) => {"a" => nil}, %({"a": true}) => {"a" => true}, %({"a": false}) => {"a" => false}, + %q({"bad":"\\\\","trailing":""}) => {"bad" => "\\", "trailing" => ""}, %q({"a": "http:\/\/test.host\/posts\/1"}) => {"a" => "http://test.host/posts/1"}, %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"}, %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, @@ -83,3 +84,4 @@ class TestJSONDecoding < ActiveSupport::TestCase assert_raise(ActiveSupport::JSON::ParseError) { ActiveSupport::JSON.decode(%({: 1})) } end end + -- cgit v1.2.3 From 793a9f122f66c28cfb58cd885a9a8cda35c4a0dd Mon Sep 17 00:00:00 2001 From: Jay Pignata <john.pignata@gmail.com> Date: Sun, 9 Aug 2009 09:13:20 -0400 Subject: Fixing isolation test [#3022 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/test/isolation_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activesupport/test/isolation_test.rb b/activesupport/test/isolation_test.rb index 5a1f285476..b83a7a0e49 100644 --- a/activesupport/test/isolation_test.rb +++ b/activesupport/test/isolation_test.rb @@ -73,7 +73,7 @@ else File.open(File.join(File.dirname(__FILE__), "fixtures", "isolation_test"), "w") {} ENV["CHILD"] = "1" - OUTPUT = `#{Gem.ruby} -I#{File.dirname(__FILE__)} #{File.expand_path(__FILE__)} -v` + OUTPUT = `#{Gem.ruby} -I#{File.dirname(__FILE__)} "#{File.expand_path(__FILE__)}" -v` ENV.delete("CHILD") def setup -- cgit v1.2.3 From 797588543ed70b4c5dcf51d7f1e4a77082172f0b Mon Sep 17 00:00:00 2001 From: Fabien Jakimowicz <fabien@jakimowicz.com> Date: Sun, 9 Aug 2009 15:24:06 +0200 Subject: Add support for errors in JSON format. [#1956 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activeresource/CHANGELOG | 2 + activeresource/lib/active_resource/base.rb | 6 +- activeresource/lib/active_resource/validations.rb | 24 +++++-- activeresource/test/base_errors_test.rb | 77 ++++++++++++++++------- 4 files changed, 83 insertions(+), 26 deletions(-) diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index aaa0eefe3c..113694e895 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add support for errors in JSON format. #1956 [Fabien Jakimowicz] + * Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh] * More thorough SSL support. #2370 [Roy Nicholson] diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 41ffb5413b..88de8b1c66 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -185,7 +185,7 @@ module ActiveResource # # Active Resource supports validations on resources and will return errors if any of these validations fail # (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by - # a response code of <tt>422</tt> and an XML representation of the validation errors. The save operation will + # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will # then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question. # # ryan = Person.find(1) @@ -194,10 +194,14 @@ module ActiveResource # # # When # # PUT http://api.people.com:3000/people/1.xml + # # or + # # PUT http://api.people.com:3000/people/1.json # # is requested with invalid values, the response is: # # # # Response (422): # # <errors type="array"><error>First cannot be empty</error></errors> + # # or + # # {"errors":["First cannot be empty"]} # # # # ryan.errors.invalid?(:first) # => true diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb index a2ba224998..4ff7be6a9e 100644 --- a/activeresource/lib/active_resource/validations.rb +++ b/activeresource/lib/active_resource/validations.rb @@ -7,11 +7,10 @@ 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 the XML response. - def from_xml(xml) + # Grabs errors from an array of messages (like ActiveRecord::Validations) + def from_array(messages) clear humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) } - messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue [] messages.each do |message| attr_message = humanized_attributes.keys.detect do |attr_name| if message[0, attr_name.size + 1] == "#{attr_name} " @@ -22,6 +21,18 @@ module ActiveResource self[:base] << message if attr_message.nil? end end + + # Grabs errors from the json response. + def from_json(json) + array = ActiveSupport::JSON.decode(json)['errors'] rescue [] + from_array array + end + + # Grabs errors from the XML response. + def from_xml(xml) + array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue [] + from_array array + end end # Module to support validation and errors with Active Resource objects. The module overrides @@ -56,7 +67,12 @@ module ActiveResource save_without_validation true rescue ResourceInvalid => error - errors.from_xml(error.response.body) + case error.response['Content-Type'] + when 'application/xml' + errors.from_xml(error.response.body) + when 'application/json' + errors.from_json(error.response.body) + end false end diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb index 28813821df..eca00e9ca8 100644 --- a/activeresource/test/base_errors_test.rb +++ b/activeresource/test/base_errors_test.rb @@ -4,45 +4,80 @@ require "fixtures/person" class BaseErrorsTest < Test::Unit::TestCase def setup ActiveResource::HttpMock.respond_to do |mock| - mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 422 + mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml'} + mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json'} end - @person = Person.new(:name => '', :age => '') - assert_equal @person.save, false end def test_should_mark_as_invalid - assert !@person.valid? + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert !@person.valid? + end + end end def test_should_parse_xml_errors - assert_kind_of ActiveResource::Errors, @person.errors - assert_equal 4, @person.errors.size + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert_kind_of ActiveResource::Errors, @person.errors + assert_equal 4, @person.errors.size + end + end end def test_should_parse_errors_to_individual_attributes - assert @person.errors[:name].any? - assert_equal ["can't be blank"], @person.errors[:age] - assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name] - assert_equal ["Person quota full for today."], @person.errors[:base] + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + assert @person.errors[:name].any? + assert_equal ["can't be blank"], @person.errors[:age] + assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name] + assert_equal ["Person quota full for today."], @person.errors[:base] + end + end end def test_should_iterate_over_errors - errors = [] - @person.errors.each { |attribute, message| errors << [attribute.to_s, message] } - assert errors.include?(["name", "can't be blank"]) + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + errors = [] + @person.errors.each { |attribute, message| errors << [attribute, message] } + assert errors.include?([:name, "can't be blank"]) + end + end end def test_should_iterate_over_full_errors - errors = [] - @person.errors.to_a.each { |message| errors << message } - assert errors.include?("Name can't be blank") + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + errors = [] + @person.errors.to_a.each { |message| errors << message } + assert errors.include?("Name can't be blank") + end + end end def test_should_format_full_errors - full = @person.errors.full_messages - assert full.include?("Age can't be blank") - assert full.include?("Name can't be blank") - assert full.include?("Name must start with a letter") - assert full.include?("Person quota full for today.") + [ :json, :xml ].each do |format| + invalid_user_using_format(format) do + full = @person.errors.full_messages + assert full.include?("Age can't be blank") + assert full.include?("Name can't be blank") + assert full.include?("Name must start with a letter") + assert full.include?("Person quota full for today.") + end + end + end + + private + def invalid_user_using_format(mime_type_reference) + previous_format = Person.format + Person.format = mime_type_reference + @person = Person.new(:name => '', :age => '') + assert_equal false, @person.save + + yield + ensure + Person.format = previous_format end end -- cgit v1.2.3 From 22f339825329e2d4463a4130e9fa68baf9d27eb6 Mon Sep 17 00:00:00 2001 From: Jeff Dean <jeff@zilkey.com> Date: Sun, 9 Aug 2009 03:29:34 -0400 Subject: Introduce validates_with to encapsulate attribute validations in a class. [#2630 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activemodel/CHANGELOG | 5 + activemodel/CHANGES | 2 +- activemodel/lib/active_model/validations/with.rb | 64 ++++++++++++ .../test/cases/validations/with_validation_test.rb | 116 +++++++++++++++++++++ activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/validator.rb | 68 ++++++++++++ 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 activemodel/CHANGELOG create mode 100644 activemodel/lib/active_model/validations/with.rb create mode 100644 activemodel/test/cases/validations/with_validation_test.rb create mode 100644 activerecord/lib/active_record/validator.rb diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG new file mode 100644 index 0000000000..142038cc87 --- /dev/null +++ b/activemodel/CHANGELOG @@ -0,0 +1,5 @@ +*Edge* + +* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean] + +* Extracted from Active Record and Active Resource. diff --git a/activemodel/CHANGES b/activemodel/CHANGES index a9f9c27507..217a6d6bf7 100644 --- a/activemodel/CHANGES +++ b/activemodel/CHANGES @@ -9,4 +9,4 @@ Changes from extracting bits to ActiveModel klass.add_observer(self) klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) - end \ No newline at end of file + end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb new file mode 100644 index 0000000000..851cdfebf0 --- /dev/null +++ b/activemodel/lib/active_model/validations/with.rb @@ -0,0 +1,64 @@ +module ActiveModel + module Validations + module ClassMethods + + # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions. + # + # class Person < ActiveRecord::Base + # validates_with MyValidator + # end + # + # class MyValidator < ActiveRecord::Validator + # def validate + # if some_complex_logic + # record.errors[:base] << "This record is invalid" + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # You may also pass it multiple classes, like so: + # + # class Person < ActiveRecord::Base + # validates_with MyValidator, MyOtherValidator, :on => :create + # end + # + # Configuration options: + # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</tt> + # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should + # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). + # The method, proc or string should return or evaluate to a true or false value. + # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should + # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). + # The method, proc or string should return or evaluate to a true or false value. + # + # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>: + # + # class Person < ActiveRecord::Base + # validates_with MyValidator, :my_custom_key => "my custom value" + # end + # + # class MyValidator < ActiveRecord::Validator + # def validate + # options[:my_custom_key] # => "my custom value" + # end + # end + # + def validates_with(*args) + configuration = args.extract_options! + + send(validation_method(configuration[:on]), configuration) do |record| + args.each do |klass| + klass.new(record, configuration.except(:on, :if, :unless)).validate + end + end + end + end + end +end + + diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb new file mode 100644 index 0000000000..f55fdc5864 --- /dev/null +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -0,0 +1,116 @@ +# encoding: utf-8 +require 'cases/helper' + +require 'models/topic' + +class ValidatesWithTest < ActiveRecord::TestCase + include ActiveModel::ValidationsRepairHelper + + repair_validations(Topic) + + ERROR_MESSAGE = "Validation error from validator" + OTHER_ERROR_MESSAGE = "Validation error from other validator" + + class ValidatorThatAddsErrors < ActiveRecord::Validator + def validate() + record.errors[:base] << ERROR_MESSAGE + end + end + + class OtherValidatorThatAddsErrors < ActiveRecord::Validator + def validate() + record.errors[:base] << OTHER_ERROR_MESSAGE + end + end + + class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator + def validate() + end + end + + class ValidatorThatValidatesOptions < ActiveRecord::Validator + def validate() + if options[:field] == :first_name + record.errors[:base] << ERROR_MESSAGE + end + end + end + + test "vaidation with class that adds errors" do + Topic.validates_with(ValidatorThatAddsErrors) + topic = Topic.new + assert !topic.valid?, "A class that adds errors causes the record to be invalid" + assert topic.errors[:base].include?(ERROR_MESSAGE) + end + + test "with a class that returns valid" do + Topic.validates_with(ValidatorThatDoesNotAddErrors) + topic = Topic.new + assert topic.valid?, "A class that does not add errors does not cause the record to be invalid" + end + + test "with a class that adds errors on update and a new record" do + Topic.validates_with(ValidatorThatAddsErrors, :on => :update) + topic = Topic.new + assert topic.valid?, "Validation doesn't run on create if 'on' is set to update" + end + + test "with a class that adds errors on create and a new record" do + Topic.validates_with(ValidatorThatAddsErrors, :on => :create) + topic = Topic.new + assert !topic.valid?, "Validation does run on create if 'on' is set to create" + assert topic.errors[:base].include?(ERROR_MESSAGE) + end + + test "with multiple classes" do + Topic.validates_with(ValidatorThatAddsErrors, OtherValidatorThatAddsErrors) + topic = Topic.new + assert !topic.valid? + assert topic.errors[:base].include?(ERROR_MESSAGE) + assert topic.errors[:base].include?(OTHER_ERROR_MESSAGE) + end + + test "with if statements that return false" do + Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 2") + topic = Topic.new + assert topic.valid? + end + + test "with if statements that return true" do + Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1") + topic = Topic.new + assert !topic.valid? + assert topic.errors[:base].include?(ERROR_MESSAGE) + end + + test "with unless statements that return true" do + Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 1") + topic = Topic.new + assert topic.valid? + end + + test "with unless statements that returns false" do + Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2") + topic = Topic.new + assert !topic.valid? + assert topic.errors[:base].include?(ERROR_MESSAGE) + end + + test "passes all non-standard configuration options to the validator class" do + topic = Topic.new + validator = mock() + validator.expects(:new).with(topic, {:foo => :bar}).returns(validator) + validator.expects(:validate) + + Topic.validates_with(validator, :if => "1 == 1", :foo => :bar) + assert topic.valid? + end + + test "validates_with with options" do + Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name) + topic = Topic.new + assert !topic.valid? + assert topic.errors[:base].include?(ERROR_MESSAGE) + end + +end diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 68b0251982..2f1e7573d8 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -69,6 +69,7 @@ module ActiveRecord autoload :TestCase, 'active_record/test_case' autoload :Timestamp, 'active_record/timestamp' autoload :Transactions, 'active_record/transactions' + autoload :Validator, 'active_record/validator' autoload :Validations, 'active_record/validations' module AttributeMethods diff --git a/activerecord/lib/active_record/validator.rb b/activerecord/lib/active_record/validator.rb new file mode 100644 index 0000000000..83a33f4dcd --- /dev/null +++ b/activerecord/lib/active_record/validator.rb @@ -0,0 +1,68 @@ +module ActiveRecord #:nodoc: + + # A simple base class that can be used along with ActiveRecord::Base.validates_with + # + # class Person < ActiveRecord::Base + # validates_with MyValidator + # end + # + # class MyValidator < ActiveRecord::Validator + # def validate + # if some_complex_logic + # record.errors[:base] = "This record is invalid" + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>, + # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>. + # + # class Person < ActiveRecord::Base + # validates_with MyValidator + # end + # + # class MyValidator < ActiveRecord::Validator + # def validate + # record # => The person instance being validated + # options # => Any non-standard options passed to validates_with + # end + # end + # + # To cause a validation error, you must add to the <tt>record<tt>'s errors directly + # from within the validators message + # + # class MyValidator < ActiveRecord::Validator + # def validate + # record.errors[:base] << "This is some custom error message" + # record.errors[:first_name] << "This is some complex validation" + # # etc... + # end + # end + # + # To add behavior to the initialize method, use the following signature: + # + # class MyValidator < ActiveRecord::Validator + # def initialize(record, options) + # super + # @my_custom_field = options[:field_name] || :first_name + # end + # end + # + class Validator + attr_reader :record, :options + + def initialize(record, options) + @record = record + @options = options + end + + def validate + raise "You must override this method" + end + end +end -- cgit v1.2.3 From 7eaed4fefebf7a2084a7936775b6fc1b085f22ba Mon Sep 17 00:00:00 2001 From: Tim Peters <tim.peters@adelaide.edu.au> Date: Wed, 10 Dec 2008 16:50:06 +1030 Subject: Use table prefix and suffix for schema_migrations index. [#1543 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- .../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 94d15fdb69..20787ec510 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -329,7 +329,7 @@ module ActiveRecord schema_migrations_table.column :version, :string, :null => false end add_index sm_table, :version, :unique => true, - :name => 'unique_schema_migrations' + :name => "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}" # Backwards-compatibility: if we find schema_info, assume we've # migrated up to that point: -- cgit v1.2.3 From 3e35ba248154209d9c46e3d9fa78e98c1b5d0a96 Mon Sep 17 00:00:00 2001 From: Cristi Balan <evil@che.lu> Date: Sun, 9 Aug 2009 14:05:56 +0300 Subject: Add tests for scoping schema_migrations index by global table prefix and suffix [#1543 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activerecord/test/cases/migration_test.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index efa9ff3648..6d3f938799 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -25,6 +25,24 @@ if ActiveRecord::Base.connection.supports_migrations? end end + class MigrationTableAndIndexTest < ActiveRecord::TestCase + def test_add_schema_info_respects_prefix_and_suffix + conn = ActiveRecord::Base.connection + + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + ActiveRecord::Base.table_name_prefix = 'foo_' + ActiveRecord::Base.table_name_suffix = '_bar' + conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name) if conn.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name) + + conn.initialize_schema_migrations_table + + assert_equal "foo_unique_schema_migrations_bar", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::Base.table_name_suffix = "" + end + end + class MigrationTest < ActiveRecord::TestCase self.use_transactional_fixtures = false -- cgit v1.2.3 From 7d16e94d827197d030fdd611db4310396aeb0114 Mon Sep 17 00:00:00 2001 From: Erik Ostrom <erik@echographia.com> Date: Sun, 9 Aug 2009 22:14:59 -0700 Subject: Correctly handle offsets in Multibyte::Chars#index and #rindex. The offset in codepoints was being passed directly to the wrapped string's index/rindex method. Now we translate the offset into bytes first. [#3028 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- activesupport/lib/active_support/multibyte/chars.rb | 8 +++++--- activesupport/test/multibyte_chars_test.rb | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 3c16999e43..64a35dca40 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -206,7 +206,8 @@ module ActiveSupport #:nodoc: # 'Café périferôl'.mb_chars.index('ô') #=> 12 # 'Café périferôl'.mb_chars.index(/\w/u) #=> 0 def index(needle, offset=0) - index = @wrapped_string.index(needle, offset) + wrapped_offset = self.first(offset).wrapped_string.length + index = @wrapped_string.index(needle, wrapped_offset) index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end @@ -215,11 +216,12 @@ module ActiveSupport #:nodoc: # string. Returns +nil+ if _needle_ isn't found. # # Example: - # 'Café périferôl'.mb_chars.rindex('é') #=> 5 + # 'Café périferôl'.mb_chars.rindex('é') #=> 6 # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13 def rindex(needle, offset=nil) offset ||= length - index = @wrapped_string.rindex(needle, offset) + wrapped_offset = self.first(offset).wrapped_string.length + index = @wrapped_string.rindex(needle, wrapped_offset) index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index f3c7f50458..ed37a1a0da 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -230,14 +230,19 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_nil @chars.index('u') assert_equal 0, @chars.index('こに') assert_equal 2, @chars.index('ち') + assert_equal 2, @chars.index('ち', -2) + assert_equal nil, @chars.index('ち', -1) assert_equal 3, @chars.index('わ') + assert_equal 5, 'ééxééx'.mb_chars.index('x', 4) end def test_rindex_should_return_character_offset assert_nil @chars.rindex('u') assert_equal 1, @chars.rindex('に') + assert_equal 2, @chars.rindex('ち', -2) + assert_nil @chars.rindex('ち', -3) assert_equal 6, 'Café périferôl'.mb_chars.rindex('é') - assert_equal 12, 'Café périferôl'.mb_chars.rindex(/\w/u) + assert_equal 13, 'Café périferôl'.mb_chars.rindex(/\w/u) end def test_indexed_insert_should_take_character_offsets -- cgit v1.2.3 From 8c32248acbd71f7906a037fad499e2f8cae61bed Mon Sep 17 00:00:00 2001 From: codeape <dan@codeape.net> Date: Sun, 9 Aug 2009 19:18:01 -0700 Subject: Introduce grouped_collection_select helper. [#1249 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- actionpack/CHANGELOG | 2 + .../lib/action_view/helpers/form_options_helper.rb | 67 ++++++++++++++++++++++ .../test/template/form_options_helper_test.rb | 34 +++++++++++ 3 files changed, 103 insertions(+) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 75de1fe2a6..30f3f31563 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Introduce grouped_collection_select helper. #1249 [Dan Codeape, Erik Ostrom] + * Make sure javascript_include_tag/stylesheet_link_tag does not append ".js" or ".css" onto external urls. #1664 [Matthew Rudy Jacobs] * Ruby 1.9: fix Content-Length for multibyte send_data streaming. #2661 [Sava Chankov] diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 8cb5882aab..4620a52272 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -162,6 +162,60 @@ module ActionView InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end + + # Returns <tt><select></tt>, <tt><optgroup></tt> and <tt><option></tt> tags for the collection of existing return values of + # +method+ for +object+'s class. The value returned from calling +method+ on the instance +object+ will + # be selected. If calling +method+ returns +nil+, no selection is made without including <tt>:prompt</tt> + # or <tt>:include_blank</tt> in the +options+ hash. + # + # Parameters: + # * +object+ - The instance of the class to be used for the select tag + # * +method+ - The attribute of +object+ corresponding to the select tag + # * +collection+ - An array of objects representing the <tt><optgroup></tt> tags. + # * +group_method+ - The name of a method which, when called on a member of +collection+, returns an + # array of child objects representing the <tt><option></tt> tags. + # * +group_label_method+ - The name of a method which, when called on a member of +collection+, returns a + # string to be used as the +label+ attribute for its <tt><optgroup></tt> tag. + # * +option_key_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the +value+ attribute for its <tt><option></tt> tag. + # * +option_value_method+ - The name of a method which, when called on a child object of a member of + # +collection+, returns a value to be used as the contents of its <tt><option></tt> tag. + # + # Example object structure for use with this method: + # class Continent < ActiveRecord::Base + # has_many :countries + # # attribs: id, name + # end + # class Country < ActiveRecord::Base + # belongs_to :continent + # # attribs: id, name, continent_id + # end + # class City < ActiveRecord::Base + # belongs_to :country + # # attribs: id, name, country_id + # end + # + # Sample usage: + # grouped_collection_select(:city, :country_id, @continents, :countries, :name, :id, :name) + # + # Possible output: + # <select name="city[country_id]"> + # <optgroup label="Africa"> + # <option value="1">South Africa</option> + # <option value="3">Somalia</option> + # </optgroup> + # <optgroup label="Europe"> + # <option value="7" selected="selected">Denmark</option> + # <option value="2">Ireland</option> + # </optgroup> + # </select> + # + def grouped_collection_select(object, method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + InstanceTag.new(object, method, self, options.delete(:object)).to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + end + + + # Return select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # @@ -490,6 +544,15 @@ module ActionView ) end + def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + value = value(object) + content_tag( + "select", add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options + ) + end + def to_time_zone_select_tag(priority_zones, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) @@ -524,6 +587,10 @@ module ActionView @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end + def grouped_collection_select(method, collection, group_method, group_label_method, option_key_method, option_value_method, options = {}, html_options = {}) + @template.grouped_collection_select(@object_name, method, collection, group_method, group_label_method, option_key_method, option_value_method, objectify_options(options), @default_options.merge(html_options)) + end + def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 73624406be..aa40e46aa8 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -763,6 +763,40 @@ class FormOptionsHelperTest < ActionView::TestCase html end + def test_grouped_collection_select + @continents = [ + Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) + ] + + @post = Post.new + @post.origin = 'dk' + + assert_dom_equal( + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, + grouped_collection_select("post", "origin", @continents, :countries, :continent_name, :country_id, :country_name) + ) + end + + def test_grouped_collection_select_under_fields_for + @continents = [ + Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) + ] + + @post = Post.new + @post.origin = 'dk' + + fields_for :post, @post do |f| + concat f.grouped_collection_select("origin", @continents, :countries, :continent_name, :country_id, :country_name) + end + + assert_dom_equal( + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, + output_buffer + ) + end + private def dummy_posts -- cgit v1.2.3 From 600a89f2082beadf4af9fe140a1a2ae56386cd49 Mon Sep 17 00:00:00 2001 From: Kamal Fariz Mahyuddin <kamal.fariz@gmail.com> Date: Mon, 10 Aug 2009 14:12:26 +0800 Subject: find_cmd should return the full path of the db command [#1488 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net> --- railties/lib/commands/dbconsole.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb index 8002264f7e..e6f11a45db 100644 --- a/railties/lib/commands/dbconsole.rb +++ b/railties/lib/commands/dbconsole.rb @@ -33,11 +33,15 @@ end def find_cmd(*commands) dirs_on_path = ENV['PATH'].to_s.split(File::PATH_SEPARATOR) commands += commands.map{|cmd| "#{cmd}.exe"} if RUBY_PLATFORM =~ /win32/ - commands.detect do |cmd| - dirs_on_path.detect do |path| - File.executable? File.join(path, cmd) + + full_path_command = nil + found = commands.detect do |cmd| + dir = dirs_on_path.detect do |path| + full_path_command = File.join(path, cmd) + File.executable? full_path_command end - end || abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") + end + found ? full_path_command : abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.") end case config["adapter"] -- cgit v1.2.3