aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile11
-rwxr-xr-xRakefile2
-rwxr-xr-xactionmailer/Rakefile2
-rw-r--r--actionmailer/lib/action_mailer/base.rb6
-rw-r--r--actionmailer/lib/action_mailer/old_api.rb4
-rw-r--r--actionmailer/lib/action_mailer/tmail_compat.rb21
-rw-r--r--actionmailer/test/old_base/tmail_compat_test.rb9
-rw-r--r--actionpack/CHANGELOG7
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb24
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb2
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb72
-rw-r--r--actionpack/lib/action_view/template/error.rb5
-rw-r--r--actionpack/lib/action_view/test_case.rb5
-rw-r--r--actionpack/test/abstract/callbacks_test.rb28
-rw-r--r--actionpack/test/controller/caching_test.rb2
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb8
-rw-r--r--actionpack/test/controller/mime_responds_test.rb2
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb18
l---------actionpack/test/fixtures/layout_tests/layouts/symlinked1
-rw-r--r--actionpack/test/template/form_options_helper_test.rb32
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb35
-rw-r--r--actionpack/test/template/lookup_context_test.rb20
-rw-r--r--actionpack/test/template/template_error_test.rb13
-rw-r--r--actionpack/test/template/test_case_test.rb23
-rwxr-xr-xactivemodel/Rakefile2
-rw-r--r--activemodel/activemodel.gemspec3
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activemodel/test/cases/errors_test.rb6
-rw-r--r--activerecord/CHANGELOG22
-rwxr-xr-xactiverecord/Rakefile13
-rw-r--r--activerecord/lib/active_record/aggregations.rb65
-rw-r--r--activerecord/lib/active_record/association_preload.rb127
-rw-r--r--activerecord/lib/active_record/associations.rb269
-rw-r--r--activerecord/lib/active_record/associations/association_collection.rb150
-rw-r--r--activerecord/lib/active_record/associations/association_proxy.rb206
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb103
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb75
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency.rb7
-rw-r--r--activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb10
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb83
-rw-r--r--activerecord/lib/active_record/associations/has_association.rb42
-rw-r--r--activerecord/lib/active_record/associations/has_many_association.rb30
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb8
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb134
-rw-r--r--activerecord/lib/active_record/associations/has_one_through_association.rb34
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb41
-rw-r--r--activerecord/lib/active_record/associations/through_association.rb73
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb8
-rw-r--r--activerecord/lib/active_record/autosave_association.rb7
-rw-r--r--activerecord/lib/active_record/base.rb79
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb23
-rw-r--r--activerecord/lib/active_record/fixtures.rb13
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/reflection.rb58
-rw-r--r--activerecord/lib/active_record/relation.rb19
-rw-r--r--activerecord/lib/active_record/relation/batches.rb6
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb18
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb21
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb2
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb6
-rw-r--r--activerecord/lib/active_record/result.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb18
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/migration.rb8
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/migration.rb6
-rw-r--r--activerecord/test/cases/adapter_test.rb12
-rw-r--r--activerecord/test/cases/adapters/mysql/reserved_word_test.rb6
-rw-r--r--activerecord/test/cases/associations/association_proxy_test.rb52
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb168
-rw-r--r--activerecord/test/cases/associations/callbacks_test.rb9
-rw-r--r--activerecord/test/cases/associations/eager_load_nested_include_test.rb1
-rw-r--r--activerecord/test/cases/associations/eager_test.rb42
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb83
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb13
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb7
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb131
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb24
-rw-r--r--activerecord/test/cases/associations/inverse_associations_test.rb71
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb12
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb6
-rw-r--r--activerecord/test/cases/autosave_association_test.rb31
-rw-r--r--activerecord/test/cases/base_test.rb7
-rw-r--r--activerecord/test/cases/batches_test.rb1
-rw-r--r--activerecord/test/cases/calculations_test.rb5
-rw-r--r--activerecord/test/cases/helper.rb20
-rw-r--r--activerecord/test/cases/inheritance_test.rb2
-rw-r--r--activerecord/test/cases/lifecycle_test.rb7
-rw-r--r--activerecord/test/cases/locking_test.rb52
-rw-r--r--activerecord/test/cases/migration_test.rb3
-rw-r--r--activerecord/test/cases/modules_test.rb1
-rw-r--r--activerecord/test/cases/nested_attributes_test.rb5
-rw-r--r--activerecord/test/cases/pooled_connections_test.rb2
-rw-r--r--activerecord/test/cases/query_cache_test.rb2
-rw-r--r--activerecord/test/cases/readonly_test.rb24
-rw-r--r--activerecord/test/cases/reflection_test.rb30
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/relations_test.rb22
-rw-r--r--activerecord/test/cases/session_store/session_test.rb2
-rw-r--r--activerecord/test/cases/unconnected_test.rb3
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb13
-rw-r--r--activerecord/test/cases/yaml_serialization_test.rb26
-rw-r--r--activerecord/test/connections/native_sqlite3/connection.rb9
-rw-r--r--activerecord/test/connections/native_sqlite3/in_memory_connection.rb18
-rw-r--r--activerecord/test/connections/native_sqlite3_mem/connection.rb19
-rw-r--r--activerecord/test/fixtures/ships.yml1
-rw-r--r--activerecord/test/models/comment.rb5
-rw-r--r--activerecord/test/models/company.rb21
-rw-r--r--activerecord/test/models/customer.rb2
-rw-r--r--activerecord/test/models/pirate.rb4
-rw-r--r--activerecord/test/models/post.rb2
-rw-r--r--activerecord/test/models/project.rb4
-rw-r--r--activerecord/test/models/reply.rb7
-rw-r--r--activerecord/test/models/sponsor.rb5
-rw-r--r--activerecord/test/models/topic.rb4
-rw-r--r--activerecord/test/schema/schema.rb2
-rwxr-xr-xactiveresource/Rakefile2
-rw-r--r--activesupport/lib/active_support/callbacks.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb54
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb2
-rw-r--r--activesupport/lib/active_support/json/backends/yaml.rb9
-rw-r--r--activesupport/lib/active_support/json/encoding.rb6
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb10
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb6
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/libxmlsax.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogiri.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/nokogirisax.rb1
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb1
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb (renamed from activesupport/test/core_ext/bigdecimal.rb)13
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb1
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb2
-rw-r--r--activesupport/test/json/encoding_test.rb31
-rw-r--r--activesupport/test/ordered_hash_test.rb28
-rw-r--r--activesupport/test/test_xml_mini.rb123
-rwxr-xr-xci/ci_build.rb18
-rwxr-xr-xrailties/Rakefile2
-rw-r--r--railties/guides/rails_guides.rb4
-rw-r--r--railties/guides/source/contribute.textile4
-rw-r--r--railties/guides/source/ruby_on_rails_guides_guidelines.textile4
-rw-r--r--railties/lib/rails/application/configuration.rb25
-rw-r--r--railties/lib/rails/generators.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb14
-rw-r--r--railties/lib/rails/generators/base.rb2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/Gemfile5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml2
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb5
-rw-r--r--railties/lib/rails/paths.rb8
-rw-r--r--railties/lib/rails/rack/logger.rb2
-rw-r--r--railties/test/application/rake_test.rb19
-rw-r--r--railties/test/generators/app_generator_test.rb2
-rw-r--r--railties/test/generators/migration_generator_test.rb7
-rw-r--r--railties/test/generators/model_generator_test.rb8
-rw-r--r--railties/test/generators/plugin_new_generator_test.rb7
-rw-r--r--railties/test/generators_test.rb18
-rw-r--r--railties/test/railties/shared_tests.rb10
176 files changed, 2070 insertions, 1667 deletions
diff --git a/Gemfile b/Gemfile
index ada8bc2c52..6808c604b9 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,11 +9,16 @@ else
end
gem "rack", :git => "git://github.com/rack/rack.git"
+gem "rack-test", :git => "git://github.com/brynary/rack-test.git"
gem "rake", ">= 0.8.7"
gem "mocha", ">= 0.9.8"
-gem "rdoc", "~> 3.0"
-gem "horo", "= 1.0.3"
+
+group :doc do
+ gem "rdoc", "~> 3.4"
+ gem "horo", "= 1.0.3"
+ gem "RedCloth", "~> 4.2"
+end
# for perf tests
gem "faker"
@@ -43,7 +48,7 @@ platforms :ruby do
gem "nokogiri", ">= 1.4.4"
# AR
- gem "sqlite3-ruby", "~> 1.3.1", :require => 'sqlite3'
+ gem "sqlite3", "~> 1.3.3"
group :db do
gem "pg", ">= 0.9.0"
diff --git a/Rakefile b/Rakefile
index 4b94595dc1..53acd077d7 100755
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,4 @@
#!/usr/bin/env rake
-gem 'rdoc', '~> 3.0'
-require 'rdoc'
require 'rdoc/task'
require 'net/http'
diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile
index 123ef9bbbf..df996acbc2 100755
--- a/actionmailer/Rakefile
+++ b/actionmailer/Rakefile
@@ -17,7 +17,7 @@ namespace :test do
task :isolated do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob("test/**/*_test.rb").all? do |file|
- system(ruby, '-Ilib:test', file)
+ sh(ruby, '-Ilib:test', file)
end or raise "Failures"
end
end
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb
index d67d563181..6ae1eac42a 100644
--- a/actionmailer/lib/action_mailer/base.rb
+++ b/actionmailer/lib/action_mailer/base.rb
@@ -246,7 +246,7 @@ module ActionMailer #:nodoc:
# but Action Mailer translates them appropriately and sets the correct values.
#
# As you can pass in any header, you need to either quote the header as a string, or pass it in as
- # an underscorised symbol, so the following will work:
+ # an underscored symbol, so the following will work:
#
# class Notifier < ActionMailer::Base
# default 'Content-Transfer-Encoding' => '7bit',
@@ -298,7 +298,7 @@ module ActionMailer #:nodoc:
#
# * <tt>sendmail_settings</tt> - Allows you to override options for the <tt>:sendmail</tt> delivery method.
# * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>.
- # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@addres</tt>
+ # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt> with <tt>-f sender@address</tt>
# added automatically before the message is sent.
#
# * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method.
@@ -404,7 +404,7 @@ module ActionMailer #:nodoc:
end
end
- def respond_to?(method, *args) #:nodoc:
+ def respond_to?(method, include_private = false) #:nodoc:
super || action_methods.include?(method.to_s)
end
diff --git a/actionmailer/lib/action_mailer/old_api.rb b/actionmailer/lib/action_mailer/old_api.rb
index dedb447ced..04728cafb0 100644
--- a/actionmailer/lib/action_mailer/old_api.rb
+++ b/actionmailer/lib/action_mailer/old_api.rb
@@ -242,12 +242,12 @@ module ActionMailer
ct.to_s.split("/")
end
- def parse_content_type(defaults=nil)
+ def parse_content_type
if @content_type.blank?
[ nil, {} ]
else
ctype, *attrs = @content_type.split(/;\s*/)
- attrs = Hash[attrs.map { |attr| attr.split(/\=/, 2) }]
+ attrs = Hash[attrs.map { |attr| attr.split(/=/, 2) }]
[ctype, {"charset" => @charset}.merge!(attrs)]
end
end
diff --git a/actionmailer/lib/action_mailer/tmail_compat.rb b/actionmailer/lib/action_mailer/tmail_compat.rb
index 26cc474e91..1b2cdcfb27 100644
--- a/actionmailer/lib/action_mailer/tmail_compat.rb
+++ b/actionmailer/lib/action_mailer/tmail_compat.rb
@@ -2,16 +2,18 @@ module Mail
class Message
def set_content_type(*args)
- ActiveSupport::Deprecation.warn('Message#set_content_type is deprecated, please just call ' <<
- 'Message#content_type with the same arguments', caller[0,2])
+ message = 'Message#set_content_type is deprecated, please just call ' <<
+ 'Message#content_type with the same arguments'
+ ActiveSupport::Deprecation.warn(message, caller[0,2])
content_type(*args)
end
alias :old_transfer_encoding :transfer_encoding
def transfer_encoding(value = nil)
if value
- ActiveSupport::Deprecation.warn('Message#transfer_encoding is deprecated, please call ' <<
- 'Message#content_transfer_encoding with the same arguments', caller[0,2])
+ message = 'Message#transfer_encoding is deprecated, ' <<
+ 'please call Message#content_transfer_encoding with the same arguments'
+ ActiveSupport::Deprecation.warn(message, caller[0,2])
content_transfer_encoding(value)
else
old_transfer_encoding
@@ -19,16 +21,17 @@ module Mail
end
def transfer_encoding=(value)
- ActiveSupport::Deprecation.warn('Message#transfer_encoding= is deprecated, please call ' <<
- 'Message#content_transfer_encoding= with the same arguments', caller[0,2])
+ message = 'Message#transfer_encoding= is deprecated, ' <<
+ 'please call Message#content_transfer_encoding= with the same arguments'
+ ActiveSupport::Deprecation.warn(message, caller[0,2])
self.content_transfer_encoding = value
end
def original_filename
- ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' <<
- 'please call Message#filename', caller[0,2])
+ message = 'Message#original_filename is deprecated, please call Message#filename'
+ ActiveSupport::Deprecation.warn(message, caller[0,2])
filename
end
end
-end \ No newline at end of file
+end
diff --git a/actionmailer/test/old_base/tmail_compat_test.rb b/actionmailer/test/old_base/tmail_compat_test.rb
index 23706e99ff..51558c2bfa 100644
--- a/actionmailer/test/old_base/tmail_compat_test.rb
+++ b/actionmailer/test/old_base/tmail_compat_test.rb
@@ -1,6 +1,14 @@
require 'abstract_unit'
class TmailCompatTest < ActiveSupport::TestCase
+ def setup
+ @silence = ActiveSupport::Deprecation.silenced
+ ActiveSupport::Deprecation.silenced = false
+ end
+
+ def teardown
+ ActiveSupport::Deprecation.silenced = @silence
+ end
def test_set_content_type_raises_deprecation_warning
mail = Mail.new
@@ -31,5 +39,4 @@ class TmailCompatTest < ActiveSupport::TestCase
end
assert_equal mail.content_transfer_encoding, "base64"
end
-
end
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 78fadce6cb..ee9d30e1fb 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.1.0 (unreleased)*
+* Add an :authenticity_token option to form_tag for custom handling or to omit the token (pass :authenticity_token => false). [Jakub Kuźma, Igor Wiedler]
+
+* HTML5 button_tag helper. [Rizwan Reza]
+
* Template lookup now searches further up in the inheritance chain. [Artemave]
* Brought back config.action_view.cache_template_loading, which allows to decide whether templates should be cached or not. [Piotr Sarnacki]
@@ -33,14 +37,17 @@
* Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche]
+
*Rails 3.0.2 (unreleased)*
* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson]
+
*Rails 3.0.1 (October 15, 2010)*
* No Changes, just a version bump.
+
*Rails 3.0.0 (August 29, 2010)*
* password_field renders with nil value by default making the use of passwords secure by default, if you want to render you should do for instance f.password_field(:password, :value => @user.password) [Santiago Pastorino]
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 20becc0bb1..f6bc5e0d37 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.5.0')
s.add_dependency('rack', '~> 1.2.1')
- s.add_dependency('rack-test', '~> 0.5.6')
+ s.add_dependency('rack-test', '~> 0.5.7')
s.add_dependency('rack-mount', '~> 0.6.13')
s.add_dependency('tzinfo', '~> 0.3.23')
s.add_dependency('erubis', '~> 2.6.6')
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index 88f973e62d..0be04b70a1 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -59,7 +59,7 @@ module ActionController #:nodoc:
key = fragment_cache_key(key)
instrument_fragment_cache :write_fragment, key do
- content = content.html_safe.to_str if content.respond_to?(:html_safe)
+ content = content.to_str
cache_store.write(key, content, options)
end
content
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 1d2f7e4f19..4f4cb96a74 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -43,7 +43,7 @@ module ActionDispatch
alias :etag? :etag
def initialize(*)
- status, header, body = super
+ super
@cache_control = {}
@etag = self["ETag"]
diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
index 71e736ce9f..dbe3206808 100644
--- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb
@@ -43,20 +43,20 @@ module ActionDispatch
end
def call(env)
- status, headers, body = @app.call(env)
-
- # Only this middleware cares about RoutingError. So, let's just raise
- # it here.
- # TODO: refactor this middleware to handle the X-Cascade scenario without
- # having to raise an exception.
- if headers['X-Cascade'] == 'pass'
- raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
+ begin
+ status, headers, body = @app.call(env)
+ exception = nil
+
+ # Only this middleware cares about RoutingError. So, let's just raise
+ # it here.
+ if headers['X-Cascade'] == 'pass'
+ raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}"
+ end
+ rescue Exception => exception
+ raise exception if env['action_dispatch.show_exceptions'] == false
end
- [status, headers, body]
- rescue Exception => exception
- raise exception if env['action_dispatch.show_exceptions'] == false
- render_exception(env, exception)
+ exception ? render_exception(env, exception) : [status, headers, body]
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index bd6ffbab5d..50d8ca9484 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -6,5 +6,5 @@
</h1>
<pre><%=h @exception.message %></pre>
-<%= render :file => "rescues/_trace.erb" %>
-<%= render :file => "rescues/_request_and_response.erb" %>
+<%= render :template => "rescues/_trace" %>
+<%= render :template => "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
index 02fa18211d..c658559be9 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb
@@ -13,9 +13,5 @@
<p><%=h @exception.sub_template_message %></p>
-<% @real_exception = @exception
- @exception = @exception.original_exception || @exception %>
-<%= render :file => "rescues/_trace.erb" %>
-<% @exception = @real_exception %>
-
-<%= render :file => "rescues/_request_and_response.erb" %>
+<%= render :template => "rescues/_trace" %>
+<%= render :template => "rescues/_request_and_response" %>
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 03bfe178e5..683fa19380 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -450,7 +450,7 @@ module ActionDispatch
end
def raise_routing_error
- raise ActionController::RoutingError.new("No route matches #{options.inspect}")
+ raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
def different_controller?
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index 30e6c078b1..385378ea29 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -44,8 +44,8 @@ module ActionView
private
# TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
- if controller.fragment_exist?(name, options)
- controller.read_fragment(name, options)
+ if fragment = controller.read_fragment(name, options)
+ fragment
else
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6f0e2c99ba..d7b9e0b4f4 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -858,8 +858,7 @@ module ActionView
end
end
- module InstanceTagMethods #:nodoc:
- extend ActiveSupport::Concern
+ class InstanceTag
include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
attr_reader :object, :method_name, :object_name
@@ -1025,7 +1024,7 @@ module ActionView
self.class.value_before_type_cast(object, @method_name)
end
- module ClassMethods
+ class << self
def value(object, method_name)
object.send method_name if object
end
@@ -1111,10 +1110,6 @@ module ActionView
end
end
- class InstanceTag
- include InstanceTagMethods
- end
-
class FormBuilder
# The methods which wrap a form helper call.
class_attribute :field_helpers
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 6ac8577785..7698602022 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -533,7 +533,7 @@ module ActionView
else
selected = Array.wrap(selected)
options = selected.extract_options!.symbolize_keys
- [ options[:selected] || selected , options[:disabled] ]
+ [ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 9500e85e8b..d6b74974e9 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -25,6 +25,9 @@ module ActionView
# * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post".
# If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt>
# is added to simulate the verb over post.
+ # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to
+ # pass custom authenticity token string, or to not add authenticity_token field at all
+ # (by passing <tt>false</tt>).
# * A list of parameters to feed to the URL the form will be posted to.
# * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the
# submit behaviour. By default this behaviour is an ajax submit.
@@ -47,6 +50,12 @@ module ActionView
# <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
#
+ # form_tag('http://far.away.com/form', :authenticity_token => false)
+ # # form without authenticity token
+ #
+ # form_tag('http://far.away.com/form', :authenticity_token => "cf50faa3fe97702ca1ae")
+ # # form with custom authenticity token
+ #
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
html_options = html_options_for_form(url_for_options, options, *parameters_for_url)
if block_given?
@@ -401,6 +410,56 @@ module ActionView
tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys)
end
+ # Creates a button element that defines a <tt>submit</tt> button,
+ # <tt>reset</tt>button or a generic button which can be used in
+ # JavaScript, for example. You can use the button tag as a regular
+ # submit tag but it isn't supported in legacy browsers. However,
+ # button tag allows richer labels such as images and emphasis.
+ #
+ # ==== Options
+ # * <tt>:confirm => 'question?'</tt> - If present, the
+ # unobtrusive JavaScript drivers will provide a prompt with
+ # the question specified. If the user accepts, the form is
+ # processed normally, otherwise no action is taken.
+ # * <tt>:disabled</tt> - If true, the user will not be able to
+ # use this input.
+ # * <tt>:disable_with</tt> - Value of this parameter will be
+ # used as the value for a disabled version of the submit
+ # button when the form is submitted. This feature is provided
+ # by the unobtrusive JavaScript driver.
+ # * Any other key creates standard HTML options for the tag.
+ #
+ # ==== Examples
+ # button_tag
+ # # => <button name="button" type="button">Button</button>
+ #
+ # button_tag "<strong>Ask me!</strong>"
+ # # => <button name="button" type="button">
+ # <strong>Ask me!</strong>
+ # </button>
+ #
+ # button_tag "Checkout", :disable_with => "Please wait..."
+ # # => <button data-disable-with="Please wait..." name="button"
+ # type="button">Checkout</button>
+ #
+ def button_tag(label = "Button", options = {})
+ options.stringify_keys!
+
+ if disable_with = options.delete("disable_with")
+ options["data-disable-with"] = disable_with
+ end
+
+ if confirm = options.delete("confirm")
+ options["data-confirm"] = confirm
+ end
+
+ ["type", "name"].each do |option|
+ options[option] = "button" unless options[option]
+ end
+
+ content_tag :button, label, { "type" => options.delete("type") }.update(options)
+ end
+
# Displays an image which when clicked will submit the form.
#
# <tt>source</tt> is passed to AssetTagHelper#path_to_image
@@ -534,6 +593,7 @@ module ActionView
html_options["action"] = url_for(url_for_options, *parameters_for_url)
html_options["accept-charset"] = "UTF-8"
html_options["data-remote"] = true if html_options.delete("remote")
+ html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token")
end
end
@@ -541,6 +601,7 @@ module ActionView
snowman_tag = tag(:input, :type => "hidden",
:name => "utf8", :value => "&#x2713;".html_safe)
+ authenticity_token = html_options.delete("authenticity_token")
method = html_options.delete("method").to_s
method_tag = case method
@@ -549,10 +610,10 @@ module ActionView
''
when /^post$/i, "", nil
html_options["method"] = "post"
- token_tag
+ token_tag(authenticity_token)
else
html_options["method"] = "post"
- tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag
+ tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag(authenticity_token)
end
tags = snowman_tag << method_tag
@@ -572,11 +633,12 @@ module ActionView
output.safe_concat("</form>")
end
- def token_tag
- unless protect_against_forgery?
+ def token_tag(token)
+ if token == false || !protect_against_forgery?
''
else
- tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token)
+ token = form_authenticity_token if token.nil?
+ tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token)
end
end
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index d7d98e1dd5..e246646963 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -56,6 +56,7 @@ module ActionView
attr_reader :original_exception, :backtrace
def initialize(template, assigns, original_exception)
+ super(original_exception.message)
@template, @assigns, @original_exception = template, assigns.dup, original_exception
@sub_templates = nil
@backtrace = original_exception.backtrace
@@ -65,10 +66,6 @@ module ActionView
@template.identifier
end
- def message
- ActiveSupport::Deprecation.silence { original_exception.message }
- end
-
def sub_template_message
if @sub_templates
"Trace of template inclusion: " +
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 4026f7a40e..2ce109ea99 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -156,10 +156,8 @@ module ActionView
# The instance of ActionView::Base that is used by +render+.
def view
@view ||= begin
- view = ActionView::Base.new(ActionController::Base.view_paths, {}, @controller)
+ view = @controller.view_context
view.singleton_class.send :include, _helpers
- view.singleton_class.send :include, @controller._routes.url_helpers
- view.singleton_class.send :delegate, :alert, :notice, :to => "request.flash"
view.extend(Locals)
view.locals = self.locals
view.output_buffer = self.output_buffer
@@ -171,6 +169,7 @@ module ActionView
INTERNAL_IVARS = %w{
@__name__
+ @__io__
@_assertion_wrapped
@_assertions
@_result
diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb
index 5308fc849b..3bdde86291 100644
--- a/actionpack/test/abstract/callbacks_test.rb
+++ b/actionpack/test/abstract/callbacks_test.rb
@@ -22,7 +22,7 @@ module AbstractController
class TestCallbacks1 < ActiveSupport::TestCase
test "basic callbacks work" do
controller = Callback1.new
- result = controller.process(:index)
+ controller.process(:index)
assert_equal "Hello world", controller.response_body
end
end
@@ -62,7 +62,7 @@ module AbstractController
end
test "before_filter works" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
@@ -78,7 +78,7 @@ module AbstractController
test "before_filter with overwritten condition" do
@controller = Callback2Overwrite.new
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "", @controller.response_body
end
end
@@ -103,12 +103,12 @@ module AbstractController
end
test "before_filter works with procs" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
test "after_filter works with procs" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "Goodbye", @controller.instance_variable_get("@second")
end
end
@@ -152,7 +152,7 @@ module AbstractController
end
test "when :except is specified, an after filter is not triggered on that action" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
@@ -186,17 +186,17 @@ module AbstractController
end
test "when :only is specified with an array, a before filter is triggered on that action" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "Hello, World", @controller.response_body
end
test "when :only is specified with an array, a before filter is not triggered on other actions" do
- result = @controller.process(:sekrit_data)
+ @controller.process(:sekrit_data)
assert_equal "true", @controller.response_body
end
test "when :except is specified with an array, an after filter is not triggered on that action" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert !@controller.instance_variable_defined?("@authenticated")
end
end
@@ -216,12 +216,12 @@ module AbstractController
end
test "when a callback is modified in a child with :only, it works for the :only action" do
- result = @controller.process(:index)
+ @controller.process(:index)
assert_equal "Hello world", @controller.response_body
end
test "when a callback is modified in a child with :only, it does not work for other actions" do
- result = @controller.process(:not_index)
+ @controller.process(:not_index)
assert_equal "", @controller.response_body
end
end
@@ -245,14 +245,14 @@ module AbstractController
assert_equal "Success", controller.response_body
end
end
-
+
class CallbacksWithArgs < ControllerWithCallbacks
set_callback :process_action, :before, :first
def first
@text = "Hello world"
end
-
+
def index(text)
self.response_body = @text + text
end
@@ -261,7 +261,7 @@ module AbstractController
class TestCallbacksWithArgs < ActiveSupport::TestCase
test "callbacks still work when invoking process with multiple args" do
controller = CallbacksWithArgs.new
- result = controller.process(:index, " Howdy!")
+ controller.process(:index, " Howdy!")
assert_equal "Hello world Howdy!", controller.response_body
end
end
diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb
index c7b54eb0ba..af02c7f9fa 100644
--- a/actionpack/test/controller/caching_test.rb
+++ b/actionpack/test/controller/caching_test.rb
@@ -257,7 +257,6 @@ class ActionCachingMockController
end
def request
- mocked_path = @mock_path
Object.new.instance_eval(<<-EVAL)
def path; '#{@mock_path}' end
def format; 'all' end
@@ -416,7 +415,6 @@ class ActionCacheTest < ActionController::TestCase
get :index
assert_response :success
- new_cached_time = content_to_cache
assert_not_equal cached_time, @response.body
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index e6fe0b1f04..cac0881133 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -139,20 +139,20 @@ class ACLogSubscriberTest < ActionController::TestCase
wait
assert_equal 4, logs.size
- assert_match(/Exist fragment\? views\/foo/, logs[1])
+ assert_match /Read fragment views\/foo/, logs[1]
assert_match(/Write fragment views\/foo/, logs[2])
ensure
@controller.config.perform_caching = true
end
-
+
def test_with_fragment_cache_and_percent_in_key
@controller.config.perform_caching = true
get :with_fragment_cache_and_percent_in_key
wait
assert_equal 4, logs.size
- assert_match(/Exist fragment\? views\/foo%bar/, logs[1])
- assert_match(/Write fragment views\/foo%bar/, logs[2])
+ assert_match /Read fragment views\/foo/, logs[1]
+ assert_match /Write fragment views\/foo/, logs[2]
ensure
@controller.config.perform_caching = true
end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index fe14d24327..5debf96232 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -658,7 +658,7 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :using_hash_resource
assert_equal "application/json", @response.content_type
- assert_equal %Q[{"result":["david",13]}], @response.body
+ assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body
end
def test_using_resource_with_block
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 2c9aa6187b..405af2a650 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -12,6 +12,14 @@ module RequestForgeryProtectionActions
render :inline => "<%= button_to('New', '/') {} %>"
end
+ def external_form
+ render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => 'external_token') {} %>"
+ end
+
+ def external_form_without_protection
+ render :inline => "<%= form_tag('http://farfar.away/form', :authenticity_token => false) {} %>"
+ end
+
def unsafe
render :text => 'pwn'
end
@@ -65,6 +73,16 @@ module RequestForgeryProtectionTests
assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
end
+ def test_should_render_external_form_with_external_token
+ get :external_form
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', 'external_token'
+ end
+
+ def test_should_render_external_form_without_token
+ get :external_form_without_protection
+ assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token, false
+ end
+
def test_should_allow_get
get :index
assert_response :success
diff --git a/actionpack/test/fixtures/layout_tests/layouts/symlinked b/actionpack/test/fixtures/layout_tests/layouts/symlinked
new file mode 120000
index 0000000000..0ddc1154ab
--- /dev/null
+++ b/actionpack/test/fixtures/layout_tests/layouts/symlinked
@@ -0,0 +1 @@
+../../symlink_parent \ No newline at end of file
diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb
index bc04398afa..69b1d4ff7b 100644
--- a/actionpack/test/template/form_options_helper_test.rb
+++ b/actionpack/test/template/form_options_helper_test.rb
@@ -7,7 +7,7 @@ class FormOptionsHelperTest < ActionView::TestCase
tests ActionView::Helpers::FormOptionsHelper
silence_warnings do
- Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin)
+ Post = Struct.new('Post', :title, :author_name, :body, :secret, :written_on, :category, :origin, :allow_comments)
Continent = Struct.new('Continent', :continent_name, :countries)
Country = Struct.new('Country', :country_id, :country_name)
Firm = Struct.new('Firm', :time_zone)
@@ -130,6 +130,13 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_boolean_array_options_for_select_with_selection_and_disabled_value
+ assert_dom_equal(
+ "<option value=\"true\">true</option>\n<option value=\"false\" selected=\"selected\">false</option>",
+ options_for_select([ true, false ], :selected => false, :disabled => nil)
+ )
+ end
+
def test_array_options_for_string_include_in_other_string_bug_fix
assert_dom_equal(
"<option value=\"ruby\">ruby</option>\n<option value=\"rubyonrails\" selected=\"selected\">rubyonrails</option>",
@@ -177,7 +184,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_string_and_option_value_is_integer
- albums = [ Album.new(1, "first","rap"), Album.new(2, "second","pop")]
+ albums = [ Album.new(1, "first","rap"), Album.new(2, "second","pop")]
assert_dom_equal(
%(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>),
options_from_collection_for_select(albums, "id", "genre", :selected => "1")
@@ -185,7 +192,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_integer_and_option_value_is_string
- albums = [ Album.new("1", "first","rap"), Album.new("2", "second","pop")]
+ albums = [ Album.new("1", "first","rap"), Album.new("2", "second","pop")]
assert_dom_equal(
%(<option selected="selected" value="1">rap</option>\n<option value="2">pop</option>),
@@ -194,7 +201,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_string_and_option_value_is_float
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0" selected="selected">pop</option>),
@@ -203,7 +210,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_value_as_nil
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0">pop</option>),
@@ -212,7 +219,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_disabled_value_as_nil
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
assert_dom_equal(
%(<option value="1.0">rap</option>\n<option value="2.0">pop</option>),
@@ -221,7 +228,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_disabled_value_as_array
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
+ albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop")]
assert_dom_equal(
%(<option disabled="disabled" value="1.0">rap</option>\n<option disabled="disabled" value="2.0">pop</option>),
@@ -230,7 +237,7 @@ class FormOptionsHelperTest < ActionView::TestCase
end
def test_collection_options_with_preselected_values_as_string_array_and_option_value_is_float
- albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop"), Album.new(3.0, "third","country") ]
+ albums = [ Album.new(1.0, "first","rap"), Album.new(2.0, "second","pop"), Album.new(3.0, "third","country") ]
assert_dom_equal(
%(<option value="1.0" selected="selected">rap</option>\n<option value="2.0">pop</option>\n<option value="3.0" selected="selected">country</option>),
@@ -364,6 +371,15 @@ class FormOptionsHelperTest < ActionView::TestCase
)
end
+ def test_select_with_boolean_method
+ @post = Post.new
+ @post.allow_comments = false
+ assert_dom_equal(
+ "<select id=\"post_allow_comments\" name=\"post[allow_comments]\"><option value=\"true\">true</option>\n<option value=\"false\" selected=\"selected\">false</option></select>",
+ select("post", "allow_comments", %w( true false ))
+ )
+ end
+
def test_select_under_fields_for
@post = Post.new
@post.category = "<mus>"
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index f3933a25b9..4a584b8db8 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -385,6 +385,41 @@ class FormTagHelperTest < ActionView::TestCase
)
end
+ def test_button_tag
+ assert_dom_equal(
+ %(<button name="button" type="button">Button</button>),
+ button_tag
+ )
+ end
+
+ def test_button_tag_with_submit_type
+ assert_dom_equal(
+ %(<button name="button" type="submit">Save</button>),
+ button_tag("Save", :type => "submit")
+ )
+ end
+
+ def test_button_tag_with_reset_type
+ assert_dom_equal(
+ %(<button name="button" type="reset">Reset</button>),
+ button_tag("Reset", :type => "reset")
+ )
+ end
+
+ def test_button_tag_with_disabled_option
+ assert_dom_equal(
+ %(<button name="button" type="reset" disabled="disabled">Reset</button>),
+ button_tag("Reset", :type => "reset", :disabled => true)
+ )
+ end
+
+ def test_button_tag_escape_content
+ assert_dom_equal(
+ %(<button name="button" type="reset" disabled="disabled">&lt;b&gt;Reset&lt;/b&gt;</button>),
+ button_tag("<b>Reset</b>", :type => "reset", :disabled => true)
+ )
+ end
+
def test_image_submit_tag_with_confirmation
assert_dom_equal(
%(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />),
diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb
index a629b3c046..f7a684779c 100644
--- a/actionpack/test/template/lookup_context_test.rb
+++ b/actionpack/test/template/lookup_context_test.rb
@@ -251,3 +251,23 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase
assert_equal "Foo", template.source
end
end
+
+class TestMissingTemplate < ActiveSupport::TestCase
+ def setup
+ @lookup_context = ActionView::LookupContext.new("/Path/to/views", {})
+ end
+
+ test "if no template was found we get a helpful error message including the inheritance chain" do
+ e = assert_raise ActionView::MissingTemplate do
+ @lookup_context.find("foo", %w(parent child))
+ end
+ assert_match %r{Missing template parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ end
+
+ test "if no partial was found we get a helpful error message including the inheritance chain" do
+ e = assert_raise ActionView::MissingTemplate do
+ @lookup_context.find("foo", %w(parent child), true)
+ end
+ assert_match %r{Missing partial parent/foo, child/foo with .* Searched in:\n \* "/Path/to/views"\n}, e.message
+ end
+end
diff --git a/actionpack/test/template/template_error_test.rb b/actionpack/test/template/template_error_test.rb
new file mode 100644
index 0000000000..3a874082d9
--- /dev/null
+++ b/actionpack/test/template/template_error_test.rb
@@ -0,0 +1,13 @@
+require "abstract_unit"
+
+class TemplateErrorTest < ActiveSupport::TestCase
+ def test_provides_original_message
+ error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ assert_equal "original", error.message
+ end
+
+ def test_provides_useful_inspect
+ error = ActionView::Template::Error.new("test", {}, Exception.new("original"))
+ assert_equal "#<ActionView::Template::Error: original>", error.inspect
+ end
+end
diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb
index a745999622..11c355dc6d 100644
--- a/actionpack/test/template/test_case_test.rb
+++ b/actionpack/test/template/test_case_test.rb
@@ -116,6 +116,27 @@ module ActionView
end
end
+ class ControllerHelperMethod < ActionView::TestCase
+ module SomeHelper
+ def some_method
+ render :partial => 'test/from_helper'
+ end
+ end
+
+ helper SomeHelper
+
+ test "can call a helper method defined on the current controller from a helper" do
+ @controller.singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1
+ def render_from_helper
+ 'controller_helper_method'
+ end
+ EOF
+ @controller.class.helper_method :render_from_helper
+
+ assert_equal 'controller_helper_method', some_method
+ end
+ end
+
class AssignsTest < ActionView::TestCase
setup do
ActiveSupport::Deprecation.stubs(:warn)
@@ -146,7 +167,7 @@ module ActionView
end
end
end
-
+
class HelperExposureTest < ActionView::TestCase
helper(Module.new do
def render_from_helper
diff --git a/activemodel/Rakefile b/activemodel/Rakefile
index 0372c7a03e..0a10912695 100755
--- a/activemodel/Rakefile
+++ b/activemodel/Rakefile
@@ -14,7 +14,7 @@ namespace :test do
task :isolated do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
Dir.glob("#{dir}/test/**/*_test.rb").all? do |file|
- system(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file)
+ sh(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file)
end or raise "Failures"
end
end
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 64aa7ad922..fec9c7ff8b 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -22,6 +22,5 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.5.0')
- s.add_dependency('bcrypt-ruby', '~> 2.1.2')
-
+ s.add_dependency('bcrypt-ruby', '~> 2.1.4')
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index fdca852c7a..0dc10e3c7d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -147,7 +147,7 @@ module ActiveModel
def empty?
all? { |k, v| v && v.empty? }
end
-
+ alias_method :blank?, :empty?
# Returns an xml formatted representation of the Errors hash.
#
# p.errors.add(:name, "can't be blank")
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 27821c333b..8cb8f7ba44 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -25,7 +25,13 @@ class ErrorsTest < ActiveModel::TestCase
def self.lookup_ancestors
[self]
end
+ end
+ test "should return true if no errors" do
+ person = Person.new
+ person.errors[:foo]
+ assert person.errors.empty?
+ assert person.errors.blank?
end
test "method validate! should work" do
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 8ebc87145c..d84fe655a7 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,15 @@
*Rails 3.1.0 (unreleased)*
+* Removed support for accessing attributes on a has_and_belongs_to_many join table. This has been
+ documented as deprecated behaviour since April 2006. Please use has_many :through instead.
+ [Jon Leighton]
+
+* Added a create_association! method for has_one and belongs_to associations. [Jon Leighton]
+
+* Migration files generated from model and constructive migration generators
+ (for example, add_name_to_users) use the reversible migration's `change`
+ method instead of the ordinary `up` and `down` methods. [Prem Sichanugrist]
+
* Removed support for interpolated SQL conditions. Please use scoping
along with attribute conditionals as a replacement.
@@ -22,7 +32,7 @@ along with attribute conditionals as a replacement.
User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
-* When a model is generated add_index is added by default for belongs_to or references columns
+* When a model is generated add_index is added by default for belongs_to or references columns
rails g model post user:belongs_to will generate the following:
@@ -103,7 +113,15 @@ IrreversibleMigration exception will be raised when going down.
[Aaron Patterson]
-*Rails 3.0.2 (unreleased)*
+
+*Rails 3.0.3 (November 16, 2010)*
+
+* Support find by class like this: Post.where(:name => Post)
+
+
+*Rails 3.0.2 (November 15, 2010)*
+
+* Dramatic speed increase (see: http://engineering.attinteractive.com/2010/10/arel-two-point-ohhhhh-yaaaaaa/) [Aaron Patterson]
* reorder is deprecated in favor of except(:order).order(...) [Santiago Pastorino]
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index f9b77c1799..e414c4fb1c 100755
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -41,7 +41,7 @@ namespace :test do
end
end
-%w( mysql mysql2 postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
+%w( mysql mysql2 postgresql sqlite3 sqlite3_mem firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter|
Rake::TestTask.new("test_#{adapter}") { |t|
connection_path = "test/connections/#{adapter =~ /jdbc/ ? 'jdbc' : 'native'}_#{adapter}"
adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/]
@@ -62,7 +62,7 @@ end
(Dir["test/cases/**/*_test.rb"].reject {
|x| x =~ /\/adapters\//
} + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file|
- system(ruby, "-Ilib:test:#{connection_path}", file)
+ sh(ruby, "-Ilib:test:#{connection_path}", file)
end or raise "Failures"
end
@@ -72,6 +72,15 @@ end
end
end
+rule '.sqlite3' do |t|
+ sh %Q{sqlite3 "#{t.name}" "create table a (a integer); drop table a;"}
+end
+
+task :test_sqlite3 => [
+ 'test/fixtures/fixture_database.sqlite3',
+ 'test/fixtures/fixture_database_2.sqlite3'
+]
+
namespace :mysql do
desc 'Build the MySQL test databases'
task :build_databases do
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
index 8cd7389005..90d3b58c78 100644
--- a/activerecord/lib/active_record/aggregations.rb
+++ b/activerecord/lib/active_record/aggregations.rb
@@ -4,9 +4,7 @@ module ActiveRecord
extend ActiveSupport::Concern
def clear_aggregation_cache #:nodoc:
- self.class.reflect_on_all_aggregations.to_a.each do |assoc|
- instance_variable_set "@#{assoc.name}", nil
- end if self.persisted?
+ @aggregation_cache.clear if persisted?
end
# Active Record implements aggregation through a macro-like class method called +composed_of+
@@ -222,53 +220,32 @@ module ActiveRecord
private
def reader_method(name, class_name, mapping, allow_nil, constructor)
- module_eval do
- define_method(name) do |*args|
- force_reload = args.first || false
-
- unless instance_variable_defined?("@#{name}")
- instance_variable_set("@#{name}", nil)
- end
-
- if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
- object = case constructor
- when Symbol
- class_name.constantize.send(constructor, *attrs)
- when Proc, Method
- constructor.call(*attrs)
- else
- raise ArgumentError, 'Constructor must be a symbol denoting the constructor method to call or a Proc to be invoked.'
- end
- instance_variable_set("@#{name}", object)
- end
- instance_variable_get("@#{name}")
+ define_method(name) do
+ if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
+ attrs = mapping.collect {|pair| read_attribute(pair.first)}
+ object = constructor.respond_to?(:call) ?
+ constructor.call(*attrs) :
+ class_name.constantize.send(constructor, *attrs)
+ @aggregation_cache[name] = object
end
+ @aggregation_cache[name]
end
-
end
def writer_method(name, class_name, mapping, allow_nil, converter)
- module_eval do
- define_method("#{name}=") do |part|
- if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
- instance_variable_set("@#{name}", nil)
- else
- unless part.is_a?(class_name.constantize) || converter.nil?
- part = case converter
- when Symbol
- class_name.constantize.send(converter, part)
- when Proc, Method
- converter.call(part)
- else
- raise ArgumentError, 'Converter must be a symbol denoting the converter method to call or a Proc to be invoked.'
- end
- end
-
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
- instance_variable_set("@#{name}", part.freeze)
+ define_method("#{name}=") do |part|
+ if part.nil? && allow_nil
+ mapping.each { |pair| self[pair.first] = nil }
+ @aggregation_cache[name] = nil
+ else
+ unless part.is_a?(class_name.constantize) || converter.nil?
+ part = converter.respond_to?(:call) ?
+ converter.call(part) :
+ class_name.constantize.send(converter, part)
end
+
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
+ @aggregation_cache[name] = part.freeze
end
end
end
diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb
index ecf7b6c210..cba4bab3ef 100644
--- a/activerecord/lib/active_record/association_preload.rb
+++ b/activerecord/lib/active_record/association_preload.rb
@@ -126,21 +126,21 @@ module ActiveRecord
parent_records.each do |parent_record|
association_proxy = parent_record.send(reflection_name)
association_proxy.loaded
- association_proxy.target.push(*Array.wrap(associated_record))
+ association_proxy.target.concat(Array.wrap(associated_record))
association_proxy.send(:set_inverse_instance, associated_record)
end
end
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
parent_records.each do |parent_record|
- parent_record.send("set_#{reflection_name}_target", associated_record)
+ parent_record.send(:association_proxy, reflection_name).target = associated_record
end
end
- def set_association_collection_records(id_to_record_map, reflection_name, associated_records, key)
+ def set_association_collection_records(id_to_parent_map, reflection_name, associated_records, key)
associated_records.each do |associated_record|
- mapped_records = id_to_record_map[associated_record[key].to_s]
- add_preloaded_records_to_collection(mapped_records, reflection_name, associated_record)
+ parent_records = id_to_parent_map[associated_record[key].to_s]
+ add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
end
end
@@ -158,14 +158,15 @@ module ActiveRecord
seen_keys[seen_key] = true
mapped_records = id_to_record_map[seen_key]
mapped_records.each do |mapped_record|
- association_proxy = mapped_record.send("set_#{reflection_name}_target", associated_record)
+ association_proxy = mapped_record.send(:association_proxy, reflection_name)
+ association_proxy.target = associated_record
association_proxy.send(:set_inverse_instance, associated_record)
end
end
id_to_record_map.each do |id, records|
- next if seen_keys.include?(id.to_s)
- records.each {|record| record.send("set_#{reflection_name}_target", nil) }
+ next if seen_keys.include?(id)
+ add_preloaded_record_to_collection(records, reflection_name, nil)
end
end
@@ -174,29 +175,23 @@ module ActiveRecord
# <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
# and +ids+ is an Array of record IDs.
def construct_id_map(records, primary_key=nil)
- id_to_record_map = {}
- ids = []
- records.each do |record|
+ records.group_by do |record|
primary_key ||= record.class.primary_key
- ids << record[primary_key]
- mapped_records = (id_to_record_map[ids.last.to_s] ||= [])
- mapped_records << record
+ record[primary_key].to_s
end
- ids.uniq!
- return id_to_record_map, ids
end
def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
left = reflection.klass.arel_table
- id_to_record_map, ids = construct_id_map(records)
+ id_to_record_map = construct_id_map(records)
+
records.each {|record| record.send(reflection.name).loaded}
options = reflection.options
right = Arel::Table.new(options[:join_table]).alias('t0')
-
join_condition = left[reflection.klass.primary_key].eq(
right[reflection.association_foreign_key])
@@ -205,7 +200,7 @@ module ActiveRecord
# FIXME: options[:select] is always nil in the tests. Do we really
# need it?
options[:select] || left[Arel.star],
- right[reflection.primary_key_name].as(
+ right[reflection.foreign_key].as(
Arel.sql('the_parent_record_id'))
]
@@ -218,34 +213,42 @@ module ActiveRecord
custom_conditions = append_conditions(reflection, preload_options)
- all_associated_records = associated_records(ids) do |some_ids|
- method = in_or_equal(some_ids)
- conditions = right[reflection.primary_key_name].send(*method)
- conditions = custom_conditions.inject(conditions) do |ast, cond|
- ast.and cond
- end
-
- associated_records_proxy.where(conditions).to_a
- end
+ klass = associated_records_proxy.klass
- set_association_collection_records(id_to_record_map, reflection.name, all_associated_records, 'the_parent_record_id')
+ associated_records(id_to_record_map.keys) { |some_ids|
+ method = in_or_equal(some_ids)
+ conditions = right.create_and(
+ [right[reflection.foreign_key].send(*method)] +
+ custom_conditions)
+
+ relation = associated_records_proxy.where(conditions)
+ klass.connection.select_all(relation.arel.to_sql, 'SQL', relation.bind_values)
+ }.map! { |row|
+ parent_records = id_to_record_map[row['the_parent_record_id'].to_s]
+ associated_record = klass.instantiate row
+ add_preloaded_records_to_collection(
+ parent_records, reflection.name, associated_record)
+ associated_record
+ }
end
def preload_has_one_association(records, reflection, preload_options={})
- return if records.first.send("loaded_#{reflection.name}?")
- id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
+ return if records.first.send(:association_proxy, reflection.name).loaded?
+ id_to_record_map = construct_id_map(records, reflection.options[:primary_key])
options = reflection.options
- records.each {|record| record.send("set_#{reflection.name}_target", nil)}
+
+ add_preloaded_record_to_collection(records, reflection.name, nil)
+
if options[:through]
through_records = preload_through_records(records, reflection, options[:through])
unless through_records.empty?
through_reflection = reflections[options[:through]]
- through_primary_key = through_reflection.primary_key_name
+ through_primary_key = through_reflection.foreign_key
source = reflection.source_reflection.name
through_records.first.class.preload_associations(through_records, source)
if through_reflection.macro == :belongs_to
- id_to_record_map = construct_id_map(records, through_primary_key).first
+ id_to_record_map = construct_id_map(records, through_primary_key)
through_primary_key = through_reflection.klass.primary_key
end
@@ -255,7 +258,7 @@ module ActiveRecord
end
end
else
- set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options), reflection.foreign_key)
end
end
@@ -263,8 +266,8 @@ module ActiveRecord
return if records.first.send(reflection.name).loaded?
options = reflection.options
- primary_key_name = reflection.through_reflection_primary_key_name
- id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
+ foreign_key = reflection.through_reflection_foreign_key
+ id_to_record_map = construct_id_map(records, foreign_key || reflection.options[:primary_key])
records.each {|record| record.send(reflection.name).loaded}
if options[:through]
@@ -280,14 +283,14 @@ module ActiveRecord
end
else
- set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
- reflection.primary_key_name)
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(id_to_record_map.keys, reflection, preload_options),
+ reflection.foreign_key)
end
end
def preload_through_records(records, reflection, through_association)
if reflection.options[:source_type]
- interface = reflection.source_reflection.options[:foreign_type]
+ interface = reflection.source_reflection.foreign_type
preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
records.compact!
@@ -317,20 +320,17 @@ module ActiveRecord
end
def preload_belongs_to_association(records, reflection, preload_options={})
- return if records.first.send("loaded_#{reflection.name}?")
+ return if records.first.send(:association_proxy, reflection.name).loaded?
options = reflection.options
- primary_key_name = reflection.primary_key_name
klasses_and_ids = {}
if options[:polymorphic]
- polymorph_type = options[:foreign_type]
-
# Construct a mapping from klass to a list of ids to load and a mapping of those ids back
# to their parent_records
records.each do |record|
- if klass = record.send(polymorph_type)
- klass_id = record.send(primary_key_name)
+ if klass = record.send(reflection.foreign_type)
+ klass_id = record.send(reflection.foreign_key)
if klass_id
id_map = klasses_and_ids[klass.constantize] ||= {}
(id_map[klass_id.to_s] ||= []) << record
@@ -339,25 +339,30 @@ module ActiveRecord
end
else
id_map = records.group_by do |record|
- key = record.send(primary_key_name)
+ key = record.send(reflection.foreign_key)
key && key.to_s
end
- id_map.delete nil
klasses_and_ids[reflection.klass] = id_map unless id_map.empty?
end
klasses_and_ids.each do |klass, _id_map|
- table = klass.arel_table
primary_key = (reflection.options[:primary_key] || klass.primary_key).to_s
- method = in_or_equal(_id_map.keys)
- conditions = table[primary_key].send(*method)
+ keys = _id_map.keys.compact
- custom_conditions = append_conditions(reflection, preload_options)
- conditions = custom_conditions.inject(conditions) do |ast, cond|
- ast.and cond
- end
+ unless keys.empty?
+ table = klass.arel_table
+ method = in_or_equal(keys)
+ conditions = table[primary_key].send(*method)
- associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
+ custom_conditions = append_conditions(reflection, preload_options)
+ conditions = custom_conditions.inject(conditions) do |ast, cond|
+ ast.and cond
+ end
+
+ associated_records = klass.unscoped.where(conditions).apply_finder_options(options.slice(:include, :select, :joins, :order)).to_a
+ else
+ associated_records = []
+ end
set_association_single_records(_id_map, reflection.name, associated_records, primary_key)
end
@@ -369,14 +374,14 @@ module ActiveRecord
conditions = []
- key = reflection.primary_key_name
+ key = reflection.foreign_key
if interface = reflection.options[:as]
key = "#{interface}_id"
conditions << table["#{interface}_type"].eq(base_class.sti_name)
end
- conditions += append_conditions(reflection, preload_options)
+ conditions.concat append_conditions(reflection, preload_options)
find_options = {
:select => preload_options[:select] || options[:select] || table[Arel.star],
@@ -388,9 +393,7 @@ module ActiveRecord
associated_records(ids) do |some_ids|
method = in_or_equal(some_ids)
- where = conditions.inject(table[key].send(*method)) do |ast, cond|
- ast.and cond
- end
+ where = table.create_and(conditions + [table[key].send(*method)])
reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => where)).to_a
end
@@ -413,7 +416,7 @@ module ActiveRecord
in_clause_length = connection.in_clause_length || ids.size
records = []
ids.each_slice(in_clause_length) do |some_ids|
- records += yield(some_ids)
+ records.concat yield(some_ids)
end
records
end
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b49cf8de95..891ac52f8a 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -119,8 +119,8 @@ module ActiveRecord
# These classes will be loaded when associations are created.
# So there is no need to eager load them.
autoload :AssociationCollection, 'active_record/associations/association_collection'
+ autoload :SingularAssociation, 'active_record/associations/singular_association'
autoload :AssociationProxy, 'active_record/associations/association_proxy'
- autoload :HasAssociation, 'active_record/associations/has_association'
autoload :ThroughAssociation, 'active_record/associations/through_association'
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
@@ -132,24 +132,40 @@ module ActiveRecord
# Clears out the association cache.
def clear_association_cache #:nodoc:
- self.class.reflect_on_all_associations.to_a.each do |assoc|
- instance_variable_set "@#{assoc.name}", nil
- end if self.persisted?
+ @association_cache.clear if persisted?
end
+ # :nodoc:
+ attr_reader :association_cache
+
+ protected
+
+ # Returns the proxy for the given association name, instantiating it if it doesn't
+ # already exist
+ def association_proxy(name)
+ association = association_instance_get(name)
+
+ if association.nil?
+ reflection = self.class.reflect_on_association(name)
+ association = reflection.proxy_class.new(self, reflection)
+ association_instance_set(name, association)
+ end
+
+ association
+ end
+
private
# Returns the specified association instance if it responds to :loaded?, nil otherwise.
def association_instance_get(name)
- ivar = "@#{name}"
- if instance_variable_defined?(ivar)
- association = instance_variable_get(ivar)
+ if @association_cache.key? name
+ association = @association_cache[name]
association if association.respond_to?(:loaded?)
end
end
# Set the specified association instance.
def association_instance_set(name, association)
- instance_variable_set("@#{name}", association)
+ @association_cache[name] = association
end
# Associations are a set of macro-like class methods for tying objects together through
@@ -193,7 +209,7 @@ module ActiveRecord
# other=(other) | X | X | X
# build_other(attributes={}) | X | | X
# create_other(attributes={}) | X | | X
- # other.create!(attributes={}) | | | X
+ # create_other!(attributes={}) | X | | X
#
# ===Collection associations (one-to-many / many-to-many)
# | | | has_many
@@ -333,26 +349,31 @@ module ActiveRecord
# === One-to-one associations
#
# * Assigning an object to a +has_one+ association automatically saves that object and
- # the object being replaced (if there is one), in order to update their primary
- # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
- # * If either of these saves fail (due to one of the objects being invalid) the assignment
- # statement returns +false+ and the assignment is cancelled.
+ # the object being replaced (if there is one), in order to update their foreign
+ # keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
+ # * If either of these saves fail (due to one of the objects being invalid), an
+ # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
+ # cancelled.
# * If you wish to assign an object to a +has_one+ association without saving it,
- # use the <tt>association.build</tt> method (documented below).
+ # use the <tt>build_association</tt> method (documented below). The object being
+ # replaced will still be saved to update its foreign key.
# * Assigning an object to a +belongs_to+ association does not save the object, since
- # the foreign key field belongs on the parent. It does not save the parent either.
+ # the foreign key field belongs on the parent. It does not save the parent either.
#
# === Collections
#
# * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically
- # saves that object, except if the parent object (the owner of the collection) is not yet
- # stored in the database.
+ # saves that object, except if the parent object (the owner of the collection) is not yet
+ # stored in the database.
# * If saving any of the objects being added to a collection (via <tt>push</tt> or similar)
- # fails, then <tt>push</tt> returns +false+.
+ # fails, then <tt>push</tt> returns +false+.
+ # * If saving fails while replacing the collection (via <tt>association=</tt>), an
+ # <tt>ActiveRecord::RecordNotSaved</tt> exception is raised and the assignment is
+ # cancelled.
# * You can add an object to a collection without automatically saving it by using the
- # <tt>collection.build</tt> method (documented below).
+ # <tt>collection.build</tt> method (documented below).
# * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically
- # saved when the parent is saved.
+ # saved when the parent is saved.
#
# === Association callbacks
#
@@ -999,12 +1020,7 @@ module ActiveRecord
reflection = create_has_many_reflection(association_id, options, &extension)
configure_dependency_for_has_many(reflection)
add_association_callbacks(reflection.name, reflection.options)
-
- if options[:through]
- collection_accessor_methods(reflection, HasManyThroughAssociation)
- else
- collection_accessor_methods(reflection, HasManyAssociation)
- end
+ collection_accessor_methods(reflection)
end
# Specifies a one-to-one association with another class. This method should only be used
@@ -1022,12 +1038,14 @@ module ActiveRecord
# [build_association(attributes = {})]
# Returns a new object of the associated type that has been instantiated
# with +attributes+ and linked to this object through a foreign key, but has not
- # yet been saved. <b>Note:</b> This ONLY works if an association already exists.
- # It will NOT work if the association is +nil+.
+ # yet been saved.
# [create_association(attributes = {})]
# Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that
# has already been saved (if it passed the validation).
+ # [create_association!(attributes = {})]
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (+association+ is replaced with the symbol passed as the first argument, so
# <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.)
@@ -1039,6 +1057,7 @@ module ActiveRecord
# * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
# * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
# * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
+ # * <tt>Account#create_beneficiary!</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save!; b</tt>)
#
# === Options
#
@@ -1116,14 +1135,12 @@ module ActiveRecord
def has_one(association_id, options = {})
if options[:through]
reflection = create_has_one_through_reflection(association_id, options)
- association_accessor_methods(reflection, ActiveRecord::Associations::HasOneThroughAssociation)
else
reflection = create_has_one_reflection(association_id, options)
- association_accessor_methods(reflection, HasOneAssociation)
- association_constructor_method(:build, reflection, HasOneAssociation)
- association_constructor_method(:create, reflection, HasOneAssociation)
+ association_constructor_methods(reflection)
configure_dependency_for_has_one(reflection)
end
+ association_accessor_methods(reflection)
end
# Specifies a one-to-one association with another class. This method should only be used
@@ -1145,6 +1162,9 @@ module ActiveRecord
# Returns a new object of the associated type that has been instantiated
# with +attributes+, linked to this object through a foreign key, and that
# has already been saved (if it passed the validation).
+ # [create_association!(attributes = {})]
+ # Does the same as <tt>create_association</tt>, but raises <tt>ActiveRecord::RecordInvalid</tt>
+ # if the record is invalid.
#
# (+association+ is replaced with the symbol passed as the first argument, so
# <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.)
@@ -1156,6 +1176,7 @@ module ActiveRecord
# * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
# * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
# * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
+ # * <tt>Post#create_author!</tt> (similar to <tt>post.author = Author.new; post.author.save!; post.author</tt>)
# The declaration can also include an options hash to specialize the behavior of the association.
#
# === Options
@@ -1177,6 +1198,11 @@ module ActiveRecord
# association will use "person_id" as the default <tt>:foreign_key</tt>. Similarly,
# <tt>belongs_to :favorite_person, :class_name => "Person"</tt> will use a foreign key
# of "favorite_person_id".
+ # [:foreign_type]
+ # Specify the column used to store the associated object's type, if this is a polymorphic
+ # association. By default this is guessed to be the name of the association with a "_type"
+ # suffix. So a class that defines a <tt>belongs_to :taggable, :polymorphic => true</tt>
+ # association will use "taggable_type" as the default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key of associated object used for the association.
# By default this is id.
@@ -1235,12 +1261,10 @@ module ActiveRecord
def belongs_to(association_id, options = {})
reflection = create_belongs_to_reflection(association_id, options)
- if reflection.options[:polymorphic]
- association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
- else
- association_accessor_methods(reflection, BelongsToAssociation)
- association_constructor_method(:build, reflection, BelongsToAssociation)
- association_constructor_method(:create, reflection, BelongsToAssociation)
+ association_accessor_methods(reflection)
+
+ unless reflection.options[:polymorphic]
+ association_constructor_methods(reflection)
end
add_counter_cache_callbacks(reflection) if options[:counter_cache]
@@ -1277,12 +1301,6 @@ module ActiveRecord
# end
# end
#
- # Deprecated: Any additional fields added to the join table will be placed as attributes when
- # pulling records out through +has_and_belongs_to_many+ associations. Records returned from join
- # tables with additional attributes will be marked as readonly (because we can't save changes
- # to the additional attributes). It's strongly recommended that you upgrade any
- # associations with attributes to a real join model (see introduction).
- #
# Adds the following methods for retrieval and query:
#
# [collection(force_reload = false)]
@@ -1425,7 +1443,7 @@ module ActiveRecord
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
def has_and_belongs_to_many(association_id, options = {}, &extension)
reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
- collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
+ collection_accessor_methods(reflection)
# Don't use a before_destroy callback since users' before_destroy
# callbacks will be executed after the association is wiped out.
@@ -1457,57 +1475,29 @@ module ActiveRecord
table_name_prefix + join_table + table_name_suffix
end
- def association_accessor_methods(reflection, association_proxy_class)
+ def association_accessor_methods(reflection)
redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
- association = association_instance_get(reflection.name)
-
- if association.nil? || force_reload || association.stale_target?
- association = association_proxy_class.new(self, reflection)
- retval = force_reload ? reflection.klass.uncached { association.reload } : association.reload
- if retval.nil? and association_proxy_class == BelongsToAssociation
- association_instance_set(reflection.name, nil)
- return nil
- end
- association_instance_set(reflection.name, association)
- end
-
- association.target.nil? ? nil : association
- end
+ association = association_proxy(reflection.name)
- redefine_method("loaded_#{reflection.name}?") do
- association = association_instance_get(reflection.name)
- association && association.loaded?
- end
-
- redefine_method("#{reflection.name}=") do |new_value|
- association = association_instance_get(reflection.name)
-
- if association.nil? || association.target != new_value
- association = association_proxy_class.new(self, reflection)
+ if force_reload
+ reflection.klass.uncached { association.reload }
+ elsif !association.loaded? || association.stale_target?
+ association.reload
end
- association.replace(new_value)
- association_instance_set(reflection.name, new_value.nil? ? nil : association)
+ association.target.nil? ? nil : association
end
- redefine_method("set_#{reflection.name}_target") do |target|
- return if target.nil? and association_proxy_class == BelongsToAssociation
- association = association_proxy_class.new(self, reflection)
- association.target = target
- association_instance_set(reflection.name, association)
+ redefine_method("#{reflection.name}=") do |record|
+ association_proxy(reflection.name).replace(record)
end
end
- def collection_reader_method(reflection, association_proxy_class)
+ def collection_reader_method(reflection)
redefine_method(reflection.name) do |*params|
force_reload = params.first unless params.empty?
- association = association_instance_get(reflection.name)
-
- unless association
- association = association_proxy_class.new(self, reflection)
- association_instance_set(reflection.name, association)
- end
+ association = association_proxy(reflection.name)
if force_reload
reflection.klass.uncached { association.reload }
@@ -1527,15 +1517,12 @@ module ActiveRecord
end
end
- def collection_accessor_methods(reflection, association_proxy_class, writer = true)
- collection_reader_method(reflection, association_proxy_class)
+ def collection_accessor_methods(reflection, writer = true)
+ collection_reader_method(reflection)
if writer
redefine_method("#{reflection.name}=") do |new_value|
- # Loads proxy class instance (defined in collection_reader_method) if not already loaded
- association = send(reflection.name)
- association.replace(new_value)
- association
+ association_proxy(reflection.name).replace(new_value)
end
redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
@@ -1547,21 +1534,17 @@ module ActiveRecord
end
end
- def association_constructor_method(constructor, reflection, association_proxy_class)
- redefine_method("#{constructor}_#{reflection.name}") do |*params|
- attributees = params.first unless params.empty?
- replace_existing = params[1].nil? ? true : params[1]
- association = association_instance_get(reflection.name)
-
- unless association
- association = association_proxy_class.new(self, reflection)
- association_instance_set(reflection.name, association)
- end
-
- if association_proxy_class == HasOneAssociation
- association.send(constructor, attributees, replace_existing)
- else
- association.send(constructor, attributees)
+ def association_constructor_methods(reflection)
+ constructors = {
+ "build_#{reflection.name}" => "build",
+ "create_#{reflection.name}" => "create",
+ "create_#{reflection.name}!" => "create!"
+ }
+
+ constructors.each do |name, proxy_name|
+ redefine_method(name) do |*params|
+ attributes = params.first unless params.empty?
+ association_proxy(reflection.name).send(proxy_name, attributes)
end
end
end
@@ -1612,16 +1595,14 @@ module ActiveRecord
# - set the foreign key to NULL if the option is set to :nullify
# - do not delete the parent record if there is any child record if the
# option is set to :restrict
- #
- # The +extra_conditions+ parameter, which is not used within the main
- # Active Record codebase, is meant to allow plugins to define extra
- # finder conditions.
- def configure_dependency_for_has_many(reflection, extra_conditions = nil)
- if reflection.options.include?(:dependent)
+ def configure_dependency_for_has_many(reflection)
+ if reflection.options[:dependent]
+ method_name = "has_many_dependent_for_#{reflection.name}"
+
case reflection.options[:dependent]
- when :destroy
- method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
- define_method(method_name) do
+ when :destroy, :delete_all, :nullify
+ define_method(method_name) do
+ if reflection.options[:dependent] == :destroy
send(reflection.name).each do |o|
# No point in executing the counter update since we're going to destroy the parent anyway
counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym
@@ -1633,35 +1614,21 @@ module ActiveRecord
o.destroy
end
end
- before_destroy method_name
- when :delete_all
- before_destroy do |record|
- self.class.send(:delete_all_has_many_dependencies,
- record,
- reflection.name,
- reflection.klass,
- reflection.dependent_conditions(record, self.class, extra_conditions))
- end
- when :nullify
- before_destroy do |record|
- self.class.send(:nullify_has_many_dependencies,
- record,
- reflection.name,
- reflection.klass,
- reflection.primary_key_name,
- reflection.dependent_conditions(record, self.class, extra_conditions))
- end
- when :restrict
- method_name = "has_many_dependent_restrict_for_#{reflection.name}".to_sym
- define_method(method_name) do
- unless send(reflection.name).empty?
- raise DeleteRestrictionError.new(reflection)
- end
+
+ # AssociationProxy#delete_all looks at the :dependent option and acts accordingly
+ send(reflection.name).delete_all
+ end
+ when :restrict
+ define_method(method_name) do
+ unless send(reflection.name).empty?
+ raise DeleteRestrictionError.new(reflection)
end
- before_destroy method_name
- else
- raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
+ end
+ else
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, :nullify or :restrict (#{reflection.options[:dependent].inspect})"
end
+
+ before_destroy method_name
end
end
@@ -1686,7 +1653,7 @@ module ActiveRecord
class_eval <<-eoruby, __FILE__, __LINE__ + 1
def #{method_name}
association = #{reflection.name}
- association.update_attribute(#{reflection.primary_key_name.inspect}, nil) if association
+ association.update_attribute(#{reflection.foreign_key.inspect}, nil) if association
end
eoruby
when :restrict
@@ -1724,14 +1691,6 @@ module ActiveRecord
end
end
- def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions)
- association_class.delete_all(dependent_conditions)
- end
-
- def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions)
- association_class.update_all("#{primary_key_name} = NULL", dependent_conditions)
- end
-
mattr_accessor :valid_keys_for_has_many_association
@@valid_keys_for_has_many_association = [
:class_name, :table_name, :foreign_key, :primary_key,
@@ -1780,13 +1739,7 @@ module ActiveRecord
def create_belongs_to_reflection(association_id, options)
options.assert_valid_keys(valid_keys_for_belongs_to_association)
- reflection = create_reflection(:belongs_to, association_id, options, self)
-
- if options[:polymorphic]
- reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
- end
-
- reflection
+ create_reflection(:belongs_to, association_id, options, self)
end
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
@@ -1806,7 +1759,7 @@ module ActiveRecord
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
- if reflection.association_foreign_key == reflection.primary_key_name
+ if reflection.association_foreign_key == reflection.foreign_key
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
end
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb
index 108e316672..b75e02c66b 100644
--- a/activerecord/lib/active_record/associations/association_collection.rb
+++ b/activerecord/lib/active_record/associations/association_collection.rb
@@ -18,8 +18,6 @@ module ActiveRecord
# If you need to work on all current children, new and existing records,
# +load_target+ and the +loaded+ flag are your friends.
class AssociationCollection < AssociationProxy #:nodoc:
- include HasAssociation
-
delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
def select(select = nil)
@@ -31,42 +29,11 @@ module ActiveRecord
end
end
- def scoped
- with_scope(@scope) { @reflection.klass.scoped }
- end
-
def find(*args)
- options = args.extract_options!
-
- # If using a custom finder_sql, scan the entire collection.
if @reflection.options[:finder_sql]
- expects_array = args.first.kind_of?(Array)
- ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
-
- if ids.size == 1
- id = ids.first
- record = load_target.detect { |r| id == r.id }
- expects_array ? [ record ] : record
- else
- load_target.select { |r| ids.include?(r.id) }
- end
+ find_by_scan(*args)
else
- merge_options_from_reflection!(options)
- construct_find_options!(options)
-
- with_scope(:find => @scope[:find].slice(:conditions, :order)) do
- relation = @reflection.klass.send(:construct_finder_arel, options, @reflection.klass.send(:current_scoped_methods))
-
- case args.first
- when :first, :last
- relation.send(args.first)
- when :all
- records = relation.all
- @reflection.options[:uniq] ? uniq(records) : records
- else
- relation.find(*args)
- end
- end
+ find_by_sql(*args)
end
end
@@ -186,14 +153,13 @@ module ActiveRecord
@reflection.klass.count_by_sql(custom_counter_sql)
else
-
if @reflection.options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" unless column_name
options.merge!(:distinct => true)
end
- value = @reflection.klass.send(:with_scope, @scope) { @reflection.klass.count(column_name, options) }
+ value = scoped.count(column_name, options)
limit = @reflection.options[:limit]
offset = @reflection.options[:offset]
@@ -348,7 +314,11 @@ module ActiveRecord
transaction do
delete(@target - other_array)
- concat(other_array - @target)
+
+ unless concat(other_array - @target)
+ raise RecordNotSaved, "Failed to replace #{@reflection.name} because one or more of the "
+ "new records could not be saved."
+ end
end
end
@@ -359,46 +329,43 @@ module ActiveRecord
loaded? ? @target.include?(record) : exists?(record)
end
- def proxy_respond_to?(method, include_private = false)
+ def respond_to?(method, include_private = false)
super || @reflection.klass.respond_to?(method, include_private)
end
protected
- def construct_find_options!(options)
+
+ def association_scope
+ options = @reflection.options.slice(:order, :limit, :joins, :group, :having, :offset)
+ super.apply_finder_options(options)
+ end
+
+ def select_value
+ super || uniq_select_value
+ end
+
+ def uniq_select_value
+ @reflection.options[:uniq] && "DISTINCT #{@reflection.quoted_table_name}.*"
end
def load_target
- if !@owner.new_record? || foreign_key_present
+ if (!@owner.new_record? || foreign_key_present?) && !loaded?
+ targets = []
+
begin
- unless loaded?
- if @target.is_a?(Array) && @target.any?
- @target = find_target.map do |f|
- i = @target.index(f)
- if i
- @target.delete_at(i).tap do |t|
- keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
- f.attributes.except(*keys).each do |k,v|
- t.send("#{k}=", v)
- end
- end
- else
- f
- end
- end + @target
- else
- @target = find_target
- end
- end
+ targets = find_target
rescue ActiveRecord::RecordNotFound
reset
end
+
+ @target = merge_target_lists(targets, @target)
end
- loaded if target
+ loaded
target
end
- def method_missing(method, *args)
+ def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
if match && match.creator?
attributes = match.attribute_names
@@ -410,15 +377,9 @@ module ActiveRecord
elsif @reflection.klass.scopes[method]
@_scopes_cache ||= {}
@_scopes_cache[method] ||= {}
- @_scopes_cache[method][args] ||= with_scope(@scope) { @reflection.klass.send(method, *args) }
+ @_scopes_cache[method][args] ||= scoped.readonly(nil).send(method, *args)
else
- with_scope(@scope) do
- if block_given?
- @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
- else
- @reflection.klass.send(method, *args)
- end
- end
+ scoped.readonly(nil).send(method, *args, &block)
end
end
@@ -438,7 +399,7 @@ module ActiveRecord
end
def reset_target!
- @target = Array.new
+ @target = []
end
def reset_scopes_cache!
@@ -473,6 +434,27 @@ module ActiveRecord
end
private
+ def merge_target_lists(loaded, existing)
+ return loaded if existing.empty?
+ return existing if loaded.empty?
+
+ loaded.map do |f|
+ i = existing.index(f)
+ if i
+ existing.delete_at(i).tap do |t|
+ keys = ["id"] + t.changes.keys + (f.attribute_names - t.attribute_names)
+ # FIXME: this call to attributes causes many NoMethodErrors
+ attributes = f.attributes
+ (attributes.keys - keys).each do |k|
+ t.send("#{k}=", attributes[k])
+ end
+ end
+ else
+ f
+ end
+ end + existing
+ end
+
# Do the relevant stuff to insert the given record into the association collection. The
# force param specifies whether or not an exception should be raised on failure. The
# validate param specifies whether validation should be performed (if force is false).
@@ -488,9 +470,7 @@ module ActiveRecord
ensure_owner_is_persisted!
transaction do
- with_scope(:create => @scope[:create].merge(scoped.where_values_hash)) do
- build_record(attrs, &block)
- end
+ scoped.scoping { build_record(attrs, &block) }
end
end
@@ -543,14 +523,32 @@ module ActiveRecord
def include_in_memory?(record)
if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
- @owner.send(proxy_reflection.through_reflection.name.to_sym).any? do |source|
+ @owner.send(proxy_reflection.through_reflection.name).any? { |source|
target = source.send(proxy_reflection.source_reflection.name)
target.respond_to?(:include?) ? target.include?(record) : target == record
- end
+ } || @target.include?(record)
else
@target.include?(record)
end
end
+
+ # If using a custom finder_sql, #find scans the entire collection.
+ def find_by_scan(*args)
+ expects_array = args.first.kind_of?(Array)
+ ids = args.flatten.compact.uniq.map { |arg| arg.to_i }
+
+ if ids.size == 1
+ id = ids.first
+ record = load_target.detect { |r| id == r.id }
+ expects_array ? [ record ] : record
+ else
+ load_target.select { |r| ids.include?(r.id) }
+ end
+ end
+
+ def find_by_sql(*args)
+ scoped.find(*args)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb
index 6720d83199..addc64cb42 100644
--- a/activerecord/lib/active_record/associations/association_proxy.rb
+++ b/activerecord/lib/active_record/associations/association_proxy.rb
@@ -7,14 +7,15 @@ module ActiveRecord
# This is the root class of all association proxies ('+ Foo' signifies an included module Foo):
#
# AssociationProxy
- # BelongsToAssociation
- # BelongsToPolymorphicAssociation
- # AssociationCollection + HasAssociation
+ # SingularAssociaton
+ # HasOneAssociation
+ # HasOneThroughAssociation + ThroughAssociation
+ # BelongsToAssociation
+ # BelongsToPolymorphicAssociation
+ # AssociationCollection
# HasAndBelongsToManyAssociation
# HasManyAssociation
# HasManyThroughAssociation + ThroughAssociation
- # HasOneAssociation + HasAssociation
- # HasOneThroughAssociation + ThroughAssociation
#
# Association proxies in Active Record are middlemen between the object that
# holds the association, known as the <tt>@owner</tt>, and the actual associated
@@ -50,10 +51,9 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class AssociationProxy #:nodoc:
- alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
- delegate :to_param, :to => :proxy_target
- instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ }
+
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
@@ -64,6 +64,10 @@ module ActiveRecord
construct_scope
end
+ def to_param
+ proxy_target.to_param
+ end
+
# Returns the owner of the proxy.
def proxy_owner
@owner
@@ -75,21 +79,15 @@ module ActiveRecord
@reflection
end
- # Returns the \target of the proxy, same as +target+.
- def proxy_target
- @target
- end
-
# Does the proxy or its \target respond to +symbol+?
def respond_to?(*args)
- proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
+ super || (load_target && @target.respond_to?(*args))
end
# Forwards <tt>===</tt> explicitly to the \target because the instance method
# removal above doesn't catch it. Loads the \target if needed.
def ===(other)
- load_target
- other === @target
+ other === load_target
end
# Returns the name of the table of the related class:
@@ -116,6 +114,7 @@ module ActiveRecord
# Reloads the \target and returns +self+ on success.
def reload
reset
+ construct_scope
load_target
self unless @target.nil?
end
@@ -127,23 +126,25 @@ module ActiveRecord
# Asserts the \target has been loaded setting the \loaded flag to +true+.
def loaded
- @loaded = true
+ @loaded = true
+ @stale_state = stale_state
end
# The target is stale if the target no longer points to the record(s) that the
# relevant foreign_key(s) refers to. If stale, the association accessor method
- # on the owner will reload the target. It's up to subclasses to implement this
- # method if relevant.
+ # on the owner will reload the target. It's up to subclasses to implement the
+ # state_state method if relevant.
#
# Note that if the target has not been loaded, it is not considered stale.
def stale_target?
- false
+ loaded? && @stale_state != stale_state
end
# Returns the target of this proxy, same as +proxy_target+.
- def target
- @target
- end
+ attr_reader :target
+
+ # Returns the \target of the proxy, same as +target+.
+ alias :proxy_target :target
# Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
def target=(target)
@@ -153,17 +154,16 @@ module ActiveRecord
# Forwards the call to the target. Loads the \target if needed.
def inspect
- load_target
- @target.inspect
+ load_target.inspect
end
def send(method, *args)
- if proxy_respond_to?(method)
- super
- else
- load_target
- @target.send(method, *args)
- end
+ return super if respond_to?(method)
+ load_target.send(method, *args)
+ end
+
+ def scoped
+ target_scope & @association_scope
end
protected
@@ -176,69 +176,85 @@ module ActiveRecord
@reflection.klass.send(:sanitize_sql, sql, table_name)
end
- # Merges into +options+ the ones coming from the reflection.
- def merge_options_from_reflection!(options)
- options.reverse_merge!(
- :group => @reflection.options[:group],
- :having => @reflection.options[:having],
- :limit => @reflection.options[:limit],
- :offset => @reflection.options[:offset],
- :joins => @reflection.options[:joins],
- :include => @reflection.options[:include],
- :select => @reflection.options[:select],
- :readonly => @reflection.options[:readonly]
- )
- end
-
- # Forwards +with_scope+ to the reflection.
- def with_scope(*args, &block)
- @reflection.klass.send :with_scope, *args, &block
+ # Construct the scope for this association.
+ #
+ # Note that the association_scope is merged into the targed_scope only when the
+ # scoped method is called. This is because at that point the call may be surrounded
+ # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
+ # actually gets built.
+ def construct_scope
+ @association_scope = association_scope if target_klass
end
- # Construct the scope used for find/create queries on the target
- def construct_scope
- @scope = {
- :find => construct_find_scope,
- :create => construct_create_scope
- }
+ def association_scope
+ scope = target_klass.unscoped
+ scope = scope.create_with(creation_attributes)
+ scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include))
+ scope = scope.select(select_value) if select_value = self.select_value
+ scope.where(construct_owner_conditions)
end
- # Implemented by subclasses
- def construct_find_scope
- raise NotImplementedError
+ def select_value
+ @reflection.options[:select]
end
# Implemented by (some) subclasses
- def construct_create_scope
- {}
+ def creation_attributes
+ { }
end
def aliased_table
- @reflection.klass.arel_table
+ target_klass.arel_table
end
# Set the inverse association, if possible
def set_inverse_instance(record)
if record && invertible_for?(record)
- record.send("set_#{inverse_reflection_for(record).name}_target", @owner)
+ inverse = record.send(:association_proxy, inverse_reflection_for(record).name)
+ inverse.target = @owner
end
end
- private
- # Forwards any missing method call to the \target.
- def method_missing(method, *args)
- if load_target
- unless @target.respond_to?(method)
- message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
- raise NoMethodError, message
- end
+ # This class of the target. belongs_to polymorphic overrides this to look at the
+ # polymorphic_type field on the owner.
+ def target_klass
+ @reflection.klass
+ end
+
+ # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
+ # through association's scope)
+ def target_scope
+ target_klass.scoped
+ end
- if block_given?
- @target.send(method, *args) { |*block_args| yield(*block_args) }
- else
- @target.send(method, *args)
+ # Returns a hash linking the owner to the association represented by the reflection
+ def construct_owner_attributes(reflection = @reflection)
+ attributes = {}
+ if reflection.macro == :belongs_to
+ attributes[reflection.association_primary_key] = @owner[reflection.foreign_key]
+ else
+ attributes[reflection.foreign_key] = @owner[reflection.active_record_primary_key]
+
+ if reflection.options[:as]
+ attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name
end
end
+ attributes
+ end
+
+ # Builds an array of arel nodes from the owner attributes hash
+ def construct_owner_conditions(table = aliased_table, reflection = @reflection)
+ conditions = construct_owner_attributes(reflection).map do |attr, value|
+ table[attr].eq(value)
+ end
+ table.create_and(conditions)
+ end
+
+ # Sets the owner attributes on the given record
+ def set_owner_attributes(record)
+ if @owner.persisted?
+ construct_owner_attributes.each { |key, value| record[key] = value }
+ end
end
# Loads the \target if needed and returns it.
@@ -252,23 +268,36 @@ module ActiveRecord
# ActiveRecord::RecordNotFound is rescued within the method, and it is
# not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
- return nil unless defined?(@loaded)
-
- if !loaded? && (!@owner.new_record? || foreign_key_present)
+ if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass
@target = find_target
end
- @loaded = true
+ loaded
@target
rescue ActiveRecord::RecordNotFound
reset
end
- # Can be overwritten by associations that might have the foreign key
- # available for an association without having the object itself (and
- # still being a new record). Currently, only +belongs_to+ presents
- # this scenario (both vanilla and polymorphic).
- def foreign_key_present
+ private
+
+ # Forwards any missing method call to the \target.
+ def method_missing(method, *args, &block)
+ if load_target
+ return super unless @target.respond_to?(method)
+ @target.send(method, *args, &block)
+ end
+ rescue NoMethodError => e
+ raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}")
+ end
+
+ # Should be true if there is a foreign key present on the @owner which
+ # references the target. This is used to determine whether we can load
+ # the target if the @owner is currently a new record (and therefore
+ # without a key).
+ #
+ # Currently implemented by belongs_to (vanilla and polymorphic) and
+ # has_one/has_many :through associations which go through a belongs_to
+ def foreign_key_present?
false
end
@@ -282,11 +311,6 @@ module ActiveRecord
end
end
- # Returns the ID of the owner, quoted if needed.
- def owner_quoted_id
- @owner.quoted_id
- end
-
# Can be redefined by subclasses, notably polymorphic belongs_to
# The record parameter is necessary to support polymorphic inverses as we must check for
# the association in the specific class of the record.
@@ -298,6 +322,14 @@ module ActiveRecord
def invertible_for?(record)
inverse_reflection_for(record)
end
+
+ # This should be implemented to return the values of the relevant key(s) on the owner,
+ # so that when state_state is different from the value stored on the last find_target,
+ # the target is stale.
+ #
+ # This is only relevant to certain associations, which is why it returns nil by default.
+ def stale_state
+ end
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 98c1c13524..e80b945dda 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -1,84 +1,50 @@
module ActiveRecord
# = Active Record Belongs To Associations
module Associations
- class BelongsToAssociation < AssociationProxy #:nodoc:
- def create(attributes = {})
- replace(@reflection.create_association(attributes))
- end
-
- def build(attributes = {})
- replace(@reflection.build_association(attributes))
- end
-
+ class BelongsToAssociation < SingularAssociation #:nodoc:
def replace(record)
- counter_cache_name = @reflection.counter_cache_column
-
- if record.nil?
- if counter_cache_name && @owner.persisted?
- @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
- end
-
- @target = @owner[@reflection.primary_key_name] = nil
- else
- raise_on_type_mismatch(record)
-
- if counter_cache_name && @owner.persisted? && record.id != @owner[@reflection.primary_key_name]
- @reflection.klass.increment_counter(counter_cache_name, record.id)
- @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
- end
-
- @target = (AssociationProxy === record ? record.target : record)
- @owner[@reflection.primary_key_name] = record_id(record) if record.persisted?
- @updated = true
- end
+ record = check_record(record)
+ update_counters(record)
+ replace_keys(record)
set_inverse_instance(record)
- loaded
- record
+ @updated = true if record
+
+ self.target = record
end
def updated?
@updated
end
- def stale_target?
- if @target && @target.persisted?
- target_id = @target.send(@reflection.association_primary_key).to_s
- foreign_key = @owner.send(@reflection.primary_key_name).to_s
-
- target_id != foreign_key
- else
- false
- end
- end
-
private
- def find_target
- find_method = if @reflection.options[:primary_key]
- "find_by_#{@reflection.options[:primary_key]}"
- else
- "find"
- end
+ def update_counters(record)
+ counter_cache_name = @reflection.counter_cache_column
- options = @reflection.options.dup.slice(:select, :include, :readonly)
+ if counter_cache_name && @owner.persisted? && different_target?(record)
+ if record
+ record.class.increment_counter(counter_cache_name, record.id)
+ end
- the_target = with_scope(:find => @scope[:find]) do
- @reflection.klass.send(find_method,
- @owner[@reflection.primary_key_name],
- options
- ) if @owner[@reflection.primary_key_name]
+ if foreign_key_present?
+ target_klass.decrement_counter(counter_cache_name, target_id)
+ end
end
- set_inverse_instance(the_target)
- the_target
end
- def construct_find_scope
- { :conditions => conditions }
+ # Checks whether record is different to the current target, without loading it
+ def different_target?(record)
+ record.nil? && @owner[@reflection.foreign_key] ||
+ record.id != @owner[@reflection.foreign_key]
+ end
+
+ def replace_keys(record)
+ @owner[@reflection.foreign_key] = record && record[@reflection.association_primary_key]
end
- def foreign_key_present
- !@owner[@reflection.primary_key_name].nil?
+ def foreign_key_present?
+ @owner[@reflection.foreign_key]
end
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
@@ -88,17 +54,16 @@ module ActiveRecord
inverse && inverse.macro == :has_one
end
- def record_id(record)
- record.send(@reflection.options[:primary_key] || :id)
+ def target_id
+ if @reflection.options[:primary_key]
+ @owner.send(@reflection.name).try(:id)
+ else
+ @owner[@reflection.foreign_key]
+ end
end
- def previous_record_id
- @previous_record_id ||= if @reflection.options[:primary_key]
- previous_record = @owner.send(@reflection.name)
- previous_record.nil? ? nil : previous_record.id
- else
- @owner[@reflection.primary_key_name]
- end
+ def stale_state
+ @owner[@reflection.foreign_key].to_s
end
end
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
index 90eff7399c..4f67b02d00 100644
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -1,80 +1,33 @@
module ActiveRecord
# = Active Record Belongs To Polymorphic Association
module Associations
- class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
- def replace(record)
- if record.nil?
- @target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
- else
- @target = (AssociationProxy === record ? record.target : record)
-
- @owner[@reflection.primary_key_name] = record_id(record)
- @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
+ class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
+ private
- @updated = true
+ def replace_keys(record)
+ super
+ @owner[@reflection.foreign_type] = record && record.class.base_class.name
end
- set_inverse_instance(record)
- loaded
- record
- end
-
- def updated?
- @updated
- end
-
- def stale_target?
- if @target && @target.persisted?
- target_id = @target.send(@reflection.association_primary_key).to_s
- foreign_key = @owner.send(@reflection.primary_key_name).to_s
- target_type = @target.class.base_class.name
- foreign_type = @owner.send(@reflection.options[:foreign_type]).to_s
-
- target_id != foreign_key || target_type != foreign_type
- else
- false
+ def different_target?(record)
+ super || record.class != target_klass
end
- end
-
- private
def inverse_reflection_for(record)
@reflection.polymorphic_inverse_of(record.class)
end
- def invertible_for?(record)
- inverse = inverse_reflection_for(record)
- inverse && inverse.macro == :has_one
- end
-
- def construct_find_scope
- { :conditions => conditions }
- end
-
- def find_target
- return nil if association_class.nil?
-
- target = association_class.send(:with_scope, :find => @scope[:find]) do
- association_class.find(
- @owner[@reflection.primary_key_name],
- :select => @reflection.options[:select],
- :include => @reflection.options[:include]
- )
- end
- set_inverse_instance(target)
- target
- end
-
- def foreign_key_present
- !@owner[@reflection.primary_key_name].nil?
+ def target_klass
+ type = @owner[@reflection.foreign_type]
+ type && type.constantize
end
- def record_id(record)
- record.send(@reflection.options[:primary_key] || :id)
+ def raise_on_type_mismatch(record)
+ # A polymorphic association cannot have a type mismatch, by definition
end
- def association_class
- @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
+ def stale_state
+ [super, @owner[@reflection.foreign_type].to_s]
end
end
end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
index bb6145656e..cb3edafab1 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency.rb
@@ -176,7 +176,7 @@ module ActiveRecord
join_part = join_parts.detect { |j|
j.reflection.name.to_s == name &&
- j.parent_table_name == parent.class.table_name }
+ j.parent_table_name == parent.class.table_name }
raise(ConfigurationError, "No such association") unless join_part
@@ -201,7 +201,7 @@ module ActiveRecord
macro = join_part.reflection.macro
if macro == :has_one
- return if record.instance_variable_defined?("@#{join_part.reflection.name}")
+ return if record.association_cache.key?(join_part.reflection.name)
association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
set_target_and_inverse(join_part, association, record)
else
@@ -223,7 +223,8 @@ module ActiveRecord
end
def set_target_and_inverse(join_part, association, record)
- association_proxy = record.send("set_#{join_part.reflection.name}_target", association)
+ association_proxy = record.send(:association_proxy, join_part.reflection.name)
+ association_proxy.target = association
association_proxy.send(:set_inverse_instance, association)
end
end
diff --git a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
index 02707dfae1..3fea24ebf8 100644
--- a/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/class_methods/join_dependency/join_association.rb
@@ -193,11 +193,11 @@ module ActiveRecord
first_key = second_key = nil
if through_reflection.macro == :belongs_to
- jt_primary_key = through_reflection.primary_key_name
+ jt_primary_key = through_reflection.foreign_key
jt_foreign_key = through_reflection.association_primary_key
else
jt_primary_key = through_reflection.active_record_primary_key
- jt_foreign_key = through_reflection.primary_key_name
+ jt_foreign_key = through_reflection.foreign_key
if through_reflection.options[:as] # has_many :through against a polymorphic join
jt_conditions <<
@@ -228,10 +228,10 @@ module ActiveRecord
second_key = source_reflection.association_foreign_key
jt_conditions <<
- join_table[reflection.source_reflection.options[:foreign_type]].
+ join_table[reflection.source_reflection.foreign_type].
eq(reflection.options[:source_type])
else
- second_key = source_reflection.primary_key_name
+ second_key = source_reflection.foreign_key
end
end
@@ -262,7 +262,7 @@ module ActiveRecord
end
def join_belongs_to_to(relation)
- foreign_key = options[:foreign_key] || reflection.primary_key_name
+ foreign_key = options[:foreign_key] || reflection.foreign_key
primary_key = options[:primary_key] || reflection.klass.primary_key
join_target_table(
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 b1d454545f..b28554dce1 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
@@ -2,24 +2,15 @@ module ActiveRecord
# = Active Record Has And Belongs To Many Association
module Associations
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
- def columns
- @reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
- end
-
- def reset_column_information
- @reflection.reset_column_information
- end
+ attr_reader :join_table
- def has_primary_key?
- @has_primary_key ||= @owner.connection.supports_primary_key? && @owner.connection.primary_key(@reflection.options[:join_table])
+ def initialize(owner, reflection)
+ @join_table_name = reflection.options[:join_table]
+ @join_table = Arel::Table.new(@join_table_name)
+ super
end
protected
- def construct_find_options!(options)
- options[:joins] = Arel::SqlLiteral.new(@scope[:find][:joins])
- options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
- options[:select] ||= (@reflection.options[:select] || Arel::SqlLiteral.new('*'))
- end
def count_records
load_target.size
@@ -33,26 +24,11 @@ module ActiveRecord
if @reflection.options[:insert_sql]
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
else
- relation = Arel::Table.new(@reflection.options[:join_table])
- timestamps = record_timestamp_columns(record)
- timezone = record.send(:current_time_from_proper_timezone) if timestamps.any?
-
- attributes = columns.map do |column|
- name = column.name
- value = case name.to_s
- when @reflection.primary_key_name.to_s
- @owner.id
- when @reflection.association_foreign_key.to_s
- record.id
- when *timestamps
- timezone
- else
- @owner.send(:quote_value, record[name], column) if record.has_attribute?(name)
- end
- [relation[name], value] unless value.nil?
- end
+ stmt = join_table.compile_insert(
+ join_table[@reflection.foreign_key] => @owner.id,
+ join_table[@reflection.association_foreign_key] => record.id
+ )
- stmt = relation.compile_insert Hash[attributes]
@owner.connection.insert stmt.to_sql
end
@@ -63,8 +39,8 @@ module ActiveRecord
if sql = @reflection.options[:delete_sql]
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
else
- relation = Arel::Table.new(@reflection.options[:join_table])
- stmt = relation.where(relation[@reflection.primary_key_name].eq(@owner.id).
+ relation = join_table
+ stmt = relation.where(relation[@reflection.foreign_key].eq(@owner.id).
and(relation[@reflection.association_foreign_key].in(records.map { |x| x.id }.compact))
).compile_delete
@owner.connection.delete stmt.to_sql
@@ -72,45 +48,28 @@ module ActiveRecord
end
def construct_joins
- "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
- end
+ right = join_table
+ left = @reflection.klass.arel_table
- def join_table
- Arel::Table.new(@reflection.options[:join_table])
+ condition = left[@reflection.klass.primary_key].eq(
+ right[@reflection.association_foreign_key])
+
+ right.create_join(right, right.create_on(condition))
end
def construct_owner_conditions
super(join_table)
end
- def construct_find_scope
- {
- :conditions => construct_conditions,
- :joins => construct_joins,
- :readonly => false,
- :order => @reflection.options[:order],
- :include => @reflection.options[:include],
- :limit => @reflection.options[:limit]
- }
+ def association_scope
+ super.joins(construct_joins)
end
- # Join tables with additional columns on top of the two foreign keys must be considered
- # ambiguous unless a select clause has been explicitly defined. Otherwise you can get
- # broken records back, if, for example, the join column also has an id column. This will
- # then overwrite the id column of the records coming back.
- def finding_with_ambiguous_select?(select_clause)
- !select_clause && columns.size != 2
+ def select_value
+ super || @reflection.klass.arel_table[Arel.star]
end
private
- def record_timestamp_columns(record)
- if record.record_timestamps
- record.send(:all_timestamp_attributes).map { |x| x.to_s }
- else
- []
- end
- end
-
def invertible_for?(record)
false
end
diff --git a/activerecord/lib/active_record/associations/has_association.rb b/activerecord/lib/active_record/associations/has_association.rb
deleted file mode 100644
index 0ecdb696ea..0000000000
--- a/activerecord/lib/active_record/associations/has_association.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-module ActiveRecord
- module Associations
- # Included in all has_* associations (i.e. everything except belongs_to)
- module HasAssociation #:nodoc:
- protected
- # Sets the owner attributes on the given record
- def set_owner_attributes(record)
- if @owner.persisted?
- construct_owner_attributes.each { |key, value| record[key] = value }
- end
- end
-
- # Returns a hash linking the owner to the association represented by the reflection
- def construct_owner_attributes(reflection = @reflection)
- attributes = {}
- if reflection.macro == :belongs_to
- attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name)
- else
- attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key)
-
- if reflection.options[:as]
- attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name
- end
- end
- attributes
- end
-
- # Builds an array of arel nodes from the owner attributes hash
- def construct_owner_conditions(table = aliased_table, reflection = @reflection)
- construct_owner_attributes(reflection).map do |attr, value|
- table[attr].eq(value)
- end
- end
-
- def construct_conditions
- conditions = construct_owner_conditions
- conditions << Arel.sql(sql_conditions) if sql_conditions
- aliased_table.create_and(conditions)
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb
index c3360463cd..b07441f3c6 100644
--- a/activerecord/lib/active_record/associations/has_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_association.rb
@@ -7,14 +7,6 @@ module ActiveRecord
# is provided by its child HasManyThroughAssociation.
class HasManyAssociation < AssociationCollection #:nodoc:
protected
- def owner_quoted_id
- if @reflection.options[:primary_key]
- @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
- else
- @owner.quoted_id
- end
- end
-
# Returns the number of records in this collection.
#
# If the association has a counter cache it gets that value. Otherwise
@@ -34,7 +26,7 @@ module ActiveRecord
elsif @reflection.options[:counter_sql] || @reflection.options[:finder_sql]
@reflection.klass.count_by_sql(custom_counter_sql)
else
- @reflection.klass.count(@scope[:find].slice(:conditions, :joins, :include))
+ scoped.count
end
# If there's nothing in the database and @target has no new records
@@ -66,12 +58,10 @@ module ActiveRecord
when :delete_all
@reflection.klass.delete(records.map { |r| r.id })
else
- updates = { @reflection.primary_key_name => nil }
+ updates = { @reflection.foreign_key => nil }
conditions = { @reflection.association_primary_key => records.map { |r| r.id } }
- with_scope(@scope) do
- @reflection.klass.update_all(updates, conditions)
- end
+ scoped.where(conditions).update_all(updates)
end
if has_cached_counter? && @reflection.options[:dependent] != :destroy
@@ -79,19 +69,7 @@ module ActiveRecord
end
end
- def construct_find_scope
- {
- :conditions => construct_conditions,
- :readonly => false,
- :order => @reflection.options[:order],
- :limit => @reflection.options[:limit],
- :include => @reflection.options[:include]
- }
- end
-
- def construct_create_scope
- construct_owner_attributes
- end
+ alias creation_attributes construct_owner_attributes
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index e2b008034e..400db6baf1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -31,18 +31,13 @@ module ActiveRecord
protected
def target_reflection_has_associated_record?
- if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.primary_key_name].blank?
+ if @reflection.through_reflection.macro == :belongs_to && @owner[@reflection.through_reflection.foreign_key].blank?
false
else
true
end
end
- def construct_find_options!(options)
- options[:joins] = [construct_joins] + Array.wrap(options[:joins])
- 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)
if record.new_record?
return false unless save_record(record, force, validate)
@@ -62,7 +57,6 @@ module ActiveRecord
def find_target
return [] unless target_reflection_has_associated_record?
- update_stale_state
scoped.all
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index c32aaf986e..6614cbbf18 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -1,122 +1,62 @@
module ActiveRecord
# = Active Record Belongs To Has One Association
module Associations
- class HasOneAssociation < AssociationProxy #:nodoc:
- include HasAssociation
-
- def create(attrs = {}, replace_existing = true)
- new_record(replace_existing) do |reflection|
- attrs = merge_with_conditions(attrs)
- reflection.create_association(attrs)
- end
- end
-
- def create!(attrs = {}, replace_existing = true)
- new_record(replace_existing) do |reflection|
- attrs = merge_with_conditions(attrs)
- reflection.create_association!(attrs)
- end
- end
+ class HasOneAssociation < SingularAssociation #:nodoc:
+ def replace(record, save = true)
+ record = check_record(record)
+ load_target
- def build(attrs = {}, replace_existing = true)
- new_record(replace_existing) do |reflection|
- attrs = merge_with_conditions(attrs)
- reflection.build_association(attrs)
- end
- end
+ @reflection.klass.transaction do
+ if @target && @target != record
+ remove_target!(@reflection.options[:dependent])
+ end
- def replace(obj, dont_save = false)
- load_target
+ if record
+ set_inverse_instance(record)
+ set_owner_attributes(record)
- unless @target.nil? || @target == obj
- if @reflection.options[:dependent] && !dont_save
- case @reflection.options[:dependent]
- when :delete
- @target.delete if @target.persisted?
- @owner.clear_association_cache
- when :destroy
- @target.destroy if @target.persisted?
- @owner.clear_association_cache
- when :nullify
- @target[@reflection.primary_key_name] = nil
- @target.save if @owner.persisted? && @target.persisted?
+ if @owner.persisted? && save && !record.save
+ nullify_owner_attributes(record)
+ set_owner_attributes(@target)
+ raise RecordNotSaved, "Failed to save the new associated #{@reflection.name}."
end
- else
- @target[@reflection.primary_key_name] = nil
- @target.save if @owner.persisted? && @target.persisted?
end
end
- if obj.nil?
- @target = nil
- else
- raise_on_type_mismatch(obj)
- set_owner_attributes(obj)
- @target = (AssociationProxy === obj ? obj.target : obj)
- end
-
- set_inverse_instance(obj)
- @loaded = true
-
- unless !@owner.persisted? || obj.nil? || dont_save
- return (obj.save ? self : false)
- else
- return (obj.nil? ? nil : self)
- end
+ self.target = record
end
- protected
- def owner_quoted_id
- if @reflection.options[:primary_key]
- @owner.class.quote_value(@owner.send(@reflection.options[:primary_key]))
- else
- @owner.quoted_id
- end
- end
-
private
- def find_target
- options = @reflection.options.dup.slice(:select, :order, :include, :readonly)
-
- the_target = with_scope(:find => @scope[:find]) do
- @reflection.klass.find(:first, options)
- end
- set_inverse_instance(the_target)
- the_target
+ def association_scope
+ super.order(@reflection.options[:order])
end
- def construct_find_scope
- { :conditions => construct_conditions }
- end
+ alias creation_attributes construct_owner_attributes
- def construct_create_scope
- construct_owner_attributes
+ # The reason that the save param for replace is false, if for create (not just build),
+ # is because the setting of the foreign keys is actually handled by the scoping when
+ # the record is instantiated, and so they are set straight away and do not need to be
+ # updated within replace.
+ def set_new_record(record)
+ replace(record, false)
end
- def new_record(replace_existing)
- # Make sure we load the target first, if we plan on replacing the existing
- # instance. Otherwise, if the target has not previously been loaded
- # elsewhere, the instance we create will get orphaned.
- load_target if replace_existing
- record = @reflection.klass.send(:with_scope, :create => @scope[:create]) do
- yield @reflection
- end
-
- if replace_existing
- replace(record, true)
+ def remove_target!(method)
+ if [:delete, :destroy].include?(method)
+ @target.send(method)
else
- record[@reflection.primary_key_name] = @owner.id if @owner.persisted?
- self.target = record
- set_inverse_instance(record)
- end
+ nullify_owner_attributes(@target)
- record
+ if @target.persisted? && @owner.persisted? && !@target.save
+ set_owner_attributes(@target)
+ raise RecordNotSaved, "Failed to remove the existing associated #{@reflection.name}. " +
+ "The record failed to save when after its foreign key was set to nil."
+ end
+ end
end
- def merge_with_conditions(attrs={})
- attrs ||= {}
- attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
- attrs
+ def nullify_owner_attributes(record)
+ record[@reflection.foreign_key] = nil
end
end
end
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 c9ae930e93..dcd74e7346 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -4,37 +4,31 @@ module ActiveRecord
class HasOneThroughAssociation < HasOneAssociation
include ThroughAssociation
- def replace(new_value)
- create_through_record(new_value)
- @target = new_value
+ def replace(record)
+ create_through_record(record)
+ self.target = record
end
private
- def create_through_record(new_value)
- proxy = @owner.send(@reflection.through_reflection.name) ||
- @owner.send(:association_instance_get, @reflection.through_reflection.name)
- record = proxy.target
+ def create_through_record(record)
+ through_proxy = @owner.send(:association_proxy, @reflection.through_reflection.name)
+ through_record = through_proxy.send(:load_target)
- if record && !new_value
- record.destroy
- elsif new_value
- attributes = construct_join_attributes(new_value)
+ if through_record && !record
+ through_record.destroy
+ elsif record
+ attributes = construct_join_attributes(record)
- if record
- record.update_attributes(attributes)
+ if through_record
+ through_record.update_attributes(attributes)
elsif @owner.new_record?
- proxy.build(attributes)
+ through_proxy.build(attributes)
else
- proxy.create(attributes)
+ through_proxy.create(attributes)
end
end
end
-
- def find_target
- update_stale_state
- scoped.first
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
new file mode 100644
index 0000000000..b6f49c6f36
--- /dev/null
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ module Associations
+ class SingularAssociation < AssociationProxy #:nodoc:
+ def create(attributes = {})
+ record = scoped.scoping { @reflection.create_association(attributes) }
+ set_new_record(record)
+ record
+ end
+
+ def create!(attributes = {})
+ build(attributes).tap { |record| record.save! }
+ end
+
+ def build(attributes = {})
+ record = scoped.scoping { @reflection.build_association(attributes) }
+ set_new_record(record)
+ record
+ end
+
+ private
+ def find_target
+ scoped.first.tap { |record| set_inverse_instance(record) }
+ end
+
+ # Implemented by subclasses
+ def replace(record)
+ raise NotImplementedError
+ end
+
+ def set_new_record(record)
+ replace(record)
+ end
+
+ def check_record(record)
+ record = record.target if AssociationProxy === record
+ raise_on_type_mismatch(record) if record
+ record
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb
index 57718285f8..d2112fb2b6 100644
--- a/activerecord/lib/active_record/associations/through_association.rb
+++ b/activerecord/lib/active_record/associations/through_association.rb
@@ -3,43 +3,25 @@ module ActiveRecord
module Associations
module ThroughAssociation
- def scoped
- with_scope(@scope) do
- @reflection.klass.scoped &
- @reflection.through_reflection.klass.scoped
- end
- end
-
- def stale_target?
- if @target && @reflection.through_reflection.macro == :belongs_to && defined?(@through_foreign_key)
- previous_key = @through_foreign_key.to_s
- current_key = @owner.send(@reflection.through_reflection.primary_key_name).to_s
+ protected
- previous_key != current_key
- else
- false
- end
+ def target_scope
+ super & @reflection.through_reflection.klass.scoped
end
- protected
-
- def construct_find_scope
- {
- :conditions => construct_conditions,
- :joins => construct_joins,
- :include => @reflection.options[:include] || @reflection.source_reflection.options[:include],
- :select => construct_select,
- :order => @reflection.options[:order],
- :limit => @reflection.options[:limit],
- :readonly => @reflection.options[:readonly]
- }
+ def association_scope
+ scope = super.joins(construct_joins).where(conditions)
+ unless @reflection.options[:include]
+ scope = scope.includes(@reflection.source_reflection.options[:include])
+ end
+ scope
end
# This scope affects the creation of the associated records (not the join records). At the
# moment we only support creating on a :through association when the source reflection is a
# belongs_to. Thus it's not necessary to set a foreign key on the associated record(s), so
# this scope has can legitimately be empty.
- def construct_create_scope
+ def creation_attributes
{ }
end
@@ -55,11 +37,6 @@ module ActiveRecord
super(aliased_through_table, @reflection.through_reflection)
end
- def construct_select
- @reflection.options[:select] ||
- @reflection.options[:uniq] && "DISTINCT #{@reflection.quoted_table_name}.*"
- end
-
def construct_joins
right = aliased_through_table
left = @reflection.klass.arel_table
@@ -69,14 +46,14 @@ module ActiveRecord
if @reflection.source_reflection.macro == :belongs_to
reflection_primary_key = @reflection.source_reflection.options[:primary_key] ||
@reflection.klass.primary_key
- source_primary_key = @reflection.source_reflection.primary_key_name
+ source_primary_key = @reflection.source_reflection.foreign_key
if @reflection.options[:source_type]
- column = @reflection.source_reflection.options[:foreign_type]
+ column = @reflection.source_reflection.foreign_type
conditions <<
right[column].eq(@reflection.options[:source_type])
end
else
- reflection_primary_key = @reflection.source_reflection.primary_key_name
+ reflection_primary_key = @reflection.source_reflection.foreign_key
source_primary_key = @reflection.source_reflection.options[:primary_key] ||
@reflection.through_reflection.klass.primary_key
if @reflection.source_reflection.options[:as]
@@ -100,12 +77,12 @@ module ActiveRecord
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = {
- @reflection.source_reflection.primary_key_name =>
+ @reflection.source_reflection.foreign_key =>
associate.send(@reflection.source_reflection.association_primary_key)
}
if @reflection.options[:source_type]
- join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name)
+ join_attributes.merge!(@reflection.source_reflection.foreign_type => associate.class.base_class.name)
end
if @reflection.through_reflection.options[:conditions].is_a?(Hash)
@@ -121,18 +98,13 @@ module ActiveRecord
end
def build_conditions
- association_conditions = @reflection.options[:conditions]
through_conditions = build_through_conditions
source_conditions = @reflection.source_reflection.options[:conditions]
uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
- if association_conditions || through_conditions || source_conditions || uses_sti
+ if through_conditions || source_conditions || uses_sti
all = []
-
- [association_conditions, source_conditions].each do |conditions|
- all << interpolate_sql(sanitize_sql(conditions)) if conditions
- end
-
+ all << interpolate_sql(sanitize_sql(source_conditions)) if source_conditions
all << through_conditions if through_conditions
all << build_sti_condition if uses_sti
@@ -157,13 +129,16 @@ module ActiveRecord
alias_method :sql_conditions, :conditions
- def update_stale_state
- construct_scope if stale_target?
-
+ def stale_state
if @reflection.through_reflection.macro == :belongs_to
- @through_foreign_key = @owner.send(@reflection.through_reflection.primary_key_name)
+ @owner[@reflection.through_reflection.foreign_key].to_s
end
end
+
+ def foreign_key_present?
+ @reflection.through_reflection.macro == :belongs_to &&
+ !@owner[@reflection.through_reflection.foreign_key].nil?
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 506f6e878f..660fa9a564 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -20,7 +20,7 @@ module ActiveRecord
# be cached. Usually caching only pays off for attributes with expensive conversion
# methods, like time related columns (e.g. +created_at+, +updated_at+).
def cache_attributes(*attribute_names)
- attribute_names.each {|attr| cached_attributes << attr.to_s}
+ cached_attributes.merge attribute_names.map { |attr| attr.to_s }
end
# Returns the attributes which are cached. By default time related columns
@@ -39,7 +39,7 @@ module ActiveRecord
if serialized_attributes.include?(attr_name)
define_read_method_for_serialized_attribute(attr_name)
else
- define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name])
+ define_read_method(attr_name, attr_name, columns_hash[attr_name])
end
if attr_name == primary_key && attr_name != "id"
@@ -55,7 +55,7 @@ module ActiveRecord
# Define read method for serialized attribute.
def define_read_method_for_serialized_attribute(attr_name)
access_code = "@attributes_cache['#{attr_name}'] ||= unserialize_attribute('#{attr_name}')"
- generated_attribute_methods.module_eval("def #{attr_name}; #{access_code}; end", __FILE__, __LINE__)
+ generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__)
end
# Define an attribute reader method. Cope with nil column.
@@ -64,7 +64,7 @@ module ActiveRecord
access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
unless attr_name.to_s == self.primary_key.to_s
- access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
+ access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ")
end
if cache_attribute?(attr_name)
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 4a18719551..70ed16eeaf 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -343,8 +343,8 @@ module ActiveRecord
association.destroy
else
key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id
- if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave)
- association[reflection.primary_key_name] = key
+ if autosave != false && (new_record? || association.new_record? || association[reflection.foreign_key] != key || autosave)
+ association[reflection.foreign_key] = key
saved = association.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
@@ -367,7 +367,8 @@ module ActiveRecord
if association.updated?
association_id = association.send(reflection.options[:primary_key] || :id)
- self[reflection.primary_key_name] = association_id
+ self[reflection.foreign_key] = association_id
+ association.loaded
end
saved if autosave
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 396ab226a9..47dc2d4a3a 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -829,7 +829,7 @@ module ActiveRecord #:nodoc:
def arel_engine
@arel_engine ||= begin
if self == ActiveRecord::Base
- Arel::Table.engine
+ ActiveRecord::Base
else
connection_handler.connection_pools[name] ? self : superclass.arel_engine
end
@@ -870,20 +870,36 @@ module ActiveRecord #:nodoc:
reset_scoped_methods
end
+ # Specifies how the record is loaded by +Marshal+.
+ #
+ # +_load+ sets an instance variable for each key in the hash it takes as input.
+ # Override this method if you require more complex marshalling.
+ def _load(data)
+ record = allocate
+ record.init_with(Marshal.load(data))
+ record
+ end
+
+
+ # Finder methods must instantiate through this method to work with the
+ # single-table inheritance model that makes it possible to create
+ # objects of different types from the same table.
+ def instantiate(record) # :nodoc:
+ model = find_sti_class(record[inheritance_column]).allocate
+ model.init_with('attributes' => record)
+ model
+ end
+
private
def relation #:nodoc:
@relation ||= Relation.new(self, arel_table)
- finder_needs_type_condition? ? @relation.where(type_condition) : @relation
- end
- # Finder methods must instantiate through this method to work with the
- # single-table inheritance model that makes it possible to create
- # objects of different types from the same table.
- def instantiate(record)
- model = find_sti_class(record[inheritance_column]).allocate
- model.init_with('attributes' => record)
- model
+ if finder_needs_type_condition?
+ @relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
+ else
+ @relation
+ end
end
def find_sti_class(type_name)
@@ -914,10 +930,9 @@ module ActiveRecord #:nodoc:
def type_condition
sti_column = arel_table[inheritance_column.to_sym]
- condition = sti_column.eq(sti_name)
- descendants.each { |subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) }
+ sti_names = ([self] + descendants).map { |model| model.sti_name }
- condition
+ sti_column.in(sti_names)
end
# Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -1364,6 +1379,8 @@ MSG
# hence you can't have attributes that aren't part of the table columns.
def initialize(attributes = nil)
@attributes = attributes_from_column_definition
+ @association_cache = {}
+ @aggregation_cache = {}
@attributes_cache = {}
@new_record = true
@readonly = false
@@ -1382,6 +1399,22 @@ MSG
result
end
+ # Populate +coder+ with attributes about this record that should be
+ # serialized. The structure of +coder+ defined in this method is
+ # guaranteed to match the structure of +coder+ passed to the +init_with+
+ # method.
+ #
+ # Example:
+ #
+ # class Post < ActiveRecord::Base
+ # end
+ # coder = {}
+ # Post.new.encode_with(coder)
+ # coder # => { 'id' => nil, ... }
+ def encode_with(coder)
+ coder['attributes'] = attributes
+ end
+
# Initialize an empty model object from +coder+. +coder+ must contain
# the attributes necessary for initializing an empty model object. For
# example:
@@ -1395,12 +1428,24 @@ MSG
def init_with(coder)
@attributes = coder['attributes']
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
+ @association_cache = {}
+ @aggregation_cache = {}
@readonly = @destroyed = @marked_for_destruction = false
@new_record = false
_run_find_callbacks
_run_initialize_callbacks
end
+ # Specifies how the record is dumped by +Marshal+.
+ #
+ # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this
+ # method if you require more complex marshalling.
+ def _dump(level)
+ dump = {}
+ encode_with(dump)
+ Marshal.dump(dump)
+ end
+
# Returns a String, which Action Pack uses for constructing an URL to this
# object. The default implementation returns this record's id as a String,
# or nil if this record's unsaved.
@@ -1533,7 +1578,7 @@ MSG
# Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
# nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
def attribute_present?(attribute)
- !read_attribute(attribute).blank?
+ !_read_attribute(attribute).blank?
end
# Returns the column object for the named attribute.
@@ -1606,9 +1651,9 @@ MSG
@changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
end
- clear_aggregation_cache
- clear_association_cache
- @attributes_cache = {}
+ @aggregation_cache = {}
+ @association_cache = {}
+ @attributes_cache = {}
@new_record = true
ensure_proper_type
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index ee9a0af35c..01e53b46c8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/deprecation'
+
module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseStatements
@@ -229,6 +231,8 @@ module ActiveRecord
#
# This method *modifies* the +sql+ parameter.
#
+ # This method is deprecated!! Stop using it!
+ #
# ===== Examples
# add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
# generates
@@ -243,6 +247,7 @@ module ActiveRecord
end
sql
end
+ deprecate :add_limit_offset!
def default_sequence_name(table, column)
nil
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index d555308485..1db397f584 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -60,7 +60,7 @@ module ActiveRecord
result =
if @query_cache[sql].key?(binds)
ActiveSupport::Notifications.instrument("sql.active_record",
- :sql => sql, :name => "CACHE", :connection_id => self.object_id)
+ :sql => sql, :name => "CACHE", :connection_id => object_id)
@query_cache[sql][binds]
else
@query_cache[sql][binds] = yield
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0282493219..5ff5813699 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -77,8 +77,8 @@ module ActiveRecord
false
end
- # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
- # does not.
+ # Does this adapter support savepoints? PostgreSQL and MySQL do,
+ # SQLite < 3.6.8 does not.
def supports_savepoints?
false
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index ce2352486b..47acf0b254 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -364,9 +364,14 @@ module ActiveRecord
# statement API. For those queries, we need to use this method. :'(
log(sql, name) do
result = @connection.query(sql)
- cols = result.fetch_fields.map { |field| field.name }
- rows = result.to_a
- result.free
+ cols = []
+ rows = []
+
+ if result
+ cols = result.fetch_fields.map { |field| field.name }
+ rows = result.to_a
+ result.free
+ end
ActiveRecord::Result.new(cols, rows)
end
end
@@ -400,7 +405,7 @@ module ActiveRecord
def begin_db_transaction #:nodoc:
exec_without_stmt "BEGIN"
- rescue Exception
+ rescue Mysql::Error
# Transactions aren't supported
end
@@ -439,6 +444,7 @@ module ActiveRecord
end
sql
end
+ deprecate :add_limit_offset!
# SCHEMA STATEMENTS ========================================
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index d76fc4103e..b04383d5bf 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -62,6 +62,10 @@ module ActiveRecord
sqlite_version >= '2.0.0'
end
+ def supports_savepoints?
+ sqlite_version >= '3.6.8'
+ end
+
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
def supports_statement_cache?
@@ -189,6 +193,18 @@ module ActiveRecord
exec_query(sql, name).rows
end
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
def begin_db_transaction #:nodoc:
@connection.transaction
end
@@ -314,12 +330,7 @@ module ActiveRecord
protected
def select(sql, name = nil, binds = []) #:nodoc:
- result = exec_query(sql, name, binds)
- columns = result.columns.map { |column|
- column.sub(/^"?\w+"?\./, '')
- }
-
- result.rows.map { |row| Hash[columns.zip(row)] }
+ exec_query(sql, name, binds).to_a
end
def table_structure(table_name)
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 6fb723f2f5..b6f0511b9a 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -615,14 +615,9 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic]
- if value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- target_type = $1
- target_type_name = (association.options[:foreign_type] || "#{association.name}_type").to_s
-
- # support polymorphic belongs_to as "label (Type)"
- row[target_type_name] = target_type
- end
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
end
row[fk_name] = Fixtures.identify(value)
@@ -634,7 +629,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
targets.each do |target|
join_fixtures["#{label}_#{target}"] = Fixture.new(
- { association.primary_key_name => row[primary_key_name],
+ { association.foreign_key => row[primary_key_name],
association.association_foreign_key => Fixtures.identify(target) },
nil, @connection)
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index a4fc18148e..9924755ddf 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -409,7 +409,7 @@ db_namespace = namespace :db do
ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]}`
+ `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}`
when "sqlite", "sqlite3"
dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
`#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 0310e7050d..ceeb0ec39d 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/module/deprecation'
module ActiveRecord
# = Active Record Reflection
@@ -196,8 +197,17 @@ module ActiveRecord
@quoted_table_name ||= klass.quoted_table_name
end
+ def foreign_key
+ @foreign_key ||= options[:foreign_key] || derive_foreign_key
+ end
+
def primary_key_name
- @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
+ foreign_key
+ end
+ deprecate :primary_key_name => :foreign_key
+
+ def foreign_type
+ @foreign_type ||= options[:foreign_type] || "#{name}_type"
end
def primary_key_column
@@ -251,7 +261,7 @@ module ActiveRecord
false
end
- def through_reflection_primary_key_name
+ def through_reflection_foreign_key
end
def source_reflection
@@ -298,22 +308,36 @@ module ActiveRecord
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
end
- def dependent_conditions(record, base_class, extra_conditions)
- dependent_conditions = []
- dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
- dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
- dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
- dependent_conditions << extra_conditions if extra_conditions
- dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
- dependent_conditions = dependent_conditions.gsub('@', '\@')
- dependent_conditions
- end
-
# Returns +true+ if +self+ is a +belongs_to+ reflection.
def belongs_to?
macro == :belongs_to
end
+ def proxy_class
+ case macro
+ when :belongs_to
+ if options[:polymorphic]
+ Associations::BelongsToPolymorphicAssociation
+ else
+ Associations::BelongsToAssociation
+ end
+ when :has_and_belongs_to_many
+ Associations::HasAndBelongsToManyAssociation
+ when :has_many
+ if options[:through]
+ Associations::HasManyThroughAssociation
+ else
+ Associations::HasManyAssociation
+ end
+ when :has_one
+ if options[:through]
+ Associations::HasOneThroughAssociation
+ else
+ Associations::HasOneAssociation
+ end
+ end
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
@@ -321,7 +345,7 @@ module ActiveRecord
class_name
end
- def derive_primary_key_name
+ def derive_foreign_key
if belongs_to?
"#{name}_id"
elsif options[:as]
@@ -403,11 +427,11 @@ module ActiveRecord
end
def through_reflection_primary_key
- through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
+ through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.foreign_key
end
- def through_reflection_primary_key_name
- through_reflection.primary_key_name if through_reflection.belongs_to?
+ def through_reflection_foreign_key
+ through_reflection.foreign_key if through_reflection.belongs_to?
end
private
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 20e983b5f7..1441e9750e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -10,7 +10,9 @@ module ActiveRecord
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
+ # These are explicitly delegated to improve performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
+ delegate :table_name, :primary_key, :to => :klass
attr_reader :table, :klass, :loaded
attr_accessor :extensions
@@ -30,13 +32,12 @@ module ActiveRecord
def insert(values)
im = arel.compile_insert values
im.into @table
- primary_key_name = @klass.primary_key
- primary_key_value = primary_key_name && Hash === values ? values[primary_key] : nil
+ primary_key_value = primary_key && Hash === values ? values[table[primary_key]] : nil
@klass.connection.insert(
im.to_sql,
'SQL',
- primary_key_name,
+ primary_key,
primary_key_value)
end
@@ -177,7 +178,7 @@ module ActiveRecord
stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)))
stmt.take limit
stmt.order(*order)
- stmt.key = @klass.arel_table[@klass.primary_key]
+ stmt.key = table[primary_key]
@klass.connection.update stmt.to_sql
end
end
@@ -320,7 +321,7 @@ module ActiveRecord
# # Delete multiple rows
# Todo.delete([2,3,4])
def delete(id_or_array)
- where(@klass.primary_key => id_or_array).delete_all
+ where(primary_key => id_or_array).delete_all
end
def reload
@@ -336,10 +337,6 @@ module ActiveRecord
self
end
- def primary_key
- @primary_key ||= table[@klass.primary_key]
- end
-
def to_sql
@to_sql ||= arel.to_sql
end
@@ -373,10 +370,6 @@ module ActiveRecord
to_a.inspect
end
- def table_name
- @klass.table_name
- end
-
protected
def method_missing(method, *args, &block)
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index b41e935ed5..359af9820f 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -65,7 +65,7 @@ module ActiveRecord
batch_size = options.delete(:batch_size) || 1000
relation = relation.except(:order).order(batch_order).limit(batch_size)
- records = relation.where(primary_key.gteq(start)).all
+ records = relation.where(table[primary_key].gteq(start)).all
while records.any?
yield records
@@ -73,7 +73,7 @@ module ActiveRecord
break if records.size < batch_size
if primary_key_offset = records.last.id
- records = relation.where(primary_key.gt(primary_key_offset)).to_a
+ records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
else
raise "Primary key not included in the custom select clause"
end
@@ -83,7 +83,7 @@ module ActiveRecord
private
def batch_order
- "#{@klass.table_name}.#{@klass.primary_key} ASC"
+ "#{table_name}.#{primary_key} ASC"
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index fd45bb24dd..b75a65e3ca 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -168,7 +168,7 @@ module ActiveRecord
unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
- column_name = @klass.primary_key if column_name == :all
+ column_name = primary_key if column_name == :all
end
distinct = nil if column_name =~ /\s*DISTINCT\s+/i
@@ -211,7 +211,7 @@ module ActiveRecord
group_attr = @group_values
association = @klass.reflect_on_association(group_attr.first.to_sym)
associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
- group_fields = Array(associated ? association.primary_key_name : group_attr)
+ group_fields = Array(associated ? association.foreign_key : group_attr)
group_aliases = group_fields.map { |field| column_alias_for(field) }
group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
[aliaz, column_for(field)]
@@ -282,15 +282,11 @@ module ActiveRecord
end
def type_cast_calculated_value(value, column, operation = nil)
- if value.is_a?(String) || value.nil?
- case operation
- when 'count' then value.to_i
- when 'sum' then type_cast_using_column(value || '0', column)
- when 'average' then value.try(:to_d)
- else type_cast_using_column(value, column)
- end
- else
- type_cast_using_column(value, column)
+ case operation
+ when 'count' then value.to_i
+ when 'sum' then type_cast_using_column(value || '0', column)
+ when 'average' then value.try(:to_d)
+ else type_cast_using_column(value, column)
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8bc28c06cb..e447de92a4 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -171,13 +171,13 @@ module ActiveRecord
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
- relation = select(primary_key).limit(1)
+ relation = select("1").limit(1)
case id
when Array, Hash
relation = relation.where(id)
else
- relation = relation.where(primary_key.eq(id)) if id
+ relation = relation.where(table[primary_key].eq(id)) if id
end
relation.first ? true : false
@@ -188,7 +188,8 @@ module ActiveRecord
def find_with_associations
including = (@eager_load_values + @includes_values).uniq
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, [])
- rows = construct_relation_for_association_find(join_dependency).to_a
+ relation = construct_relation_for_association_find(join_dependency)
+ rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
rescue ThrowResult
[]
@@ -225,10 +226,10 @@ module ActiveRecord
def construct_limited_ids_condition(relation)
orders = relation.order_values
- values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders)
+ values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
- ids_array = relation.select(values).collect {|row| row[@klass.primary_key]}
- ids_array.empty? ? raise(ThrowResult) : primary_key.in(ids_array)
+ ids_array = relation.select(values).collect {|row| row[primary_key]}
+ ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
def find_by_attributes(match, attributes, *args)
@@ -290,24 +291,24 @@ module ActiveRecord
def find_one(id)
id = id.id if ActiveRecord::Base === id
- column = columns_hash[primary_key.name.to_s]
+ column = columns_hash[primary_key]
substitute = connection.substitute_for(column, @bind_values)
- relation = where(primary_key.eq(substitute))
+ relation = where(table[primary_key].eq(substitute))
relation.bind_values = [[column, id]]
record = relation.first
unless record
conditions = arel.where_sql
conditions = " [#{conditions}]" if conditions
- raise RecordNotFound, "Couldn't find #{@klass.name} with #{@klass.primary_key}=#{id}#{conditions}"
+ raise RecordNotFound, "Couldn't find #{@klass.name} with #{primary_key}=#{id}#{conditions}"
end
record
end
def find_some(ids)
- result = where(primary_key.in(ids)).all
+ result = where(table[primary_key].in(ids)).all
expected_size =
if @limit_value && ids.size > @limit_value
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index b3f1b9e36a..61d9974570 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -20,7 +20,7 @@ module ActiveRecord
case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
values = value.to_a.map { |x|
- x.respond_to?(:quoted_id) ? x.quoted_id : x
+ x.is_a?(ActiveRecord::Base) ? x.id : x
}
attribute.in(values)
when Range, Arel::Relation
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 0ab55ae864..2cbb103eb9 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -128,7 +128,7 @@ module ActiveRecord
def create_with(value)
relation = clone
- relation.create_with_value = value
+ relation.create_with_value = value && (@create_with_value || {}).merge(value)
relation
end
@@ -152,7 +152,7 @@ module ActiveRecord
order_clause = arel.order_clauses
order = order_clause.empty? ?
- "#{@klass.table_name}.#{@klass.primary_key} DESC" :
+ "#{table_name}.#{primary_key} DESC" :
reverse_sql_order(order_clause).join(', ')
except(:order).order(Arel.sql(order))
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index ae777208db..69a7642ec5 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -46,13 +46,15 @@ module ActiveRecord
merged_relation.where_values = merged_wheres
- (Relation::SINGLE_VALUE_METHODS - [:lock]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with]).each do |method|
value = r.send(:"#{method}_value")
merged_relation.send(:"#{method}_value=", value) unless value.nil?
end
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
+ merged_relation = merged_relation.create_with(r.create_with_value) if r.create_with_value
+
# Apply scope extension modules
merged_relation.send :apply_modules, r.extensions
@@ -112,7 +114,7 @@ module ActiveRecord
options.assert_valid_keys(VALID_FIND_OPTIONS)
finders = options.dup
- finders.delete_if { |key, value| value.nil? }
+ finders.delete_if { |key, value| value.nil? && key != :limit }
([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
relation = relation.send(finder, finders[finder])
diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb
index 8deff1478f..47a5ac2700 100644
--- a/activerecord/lib/active_record/result.rb
+++ b/activerecord/lib/active_record/result.rb
@@ -23,7 +23,7 @@ module ActiveRecord
private
def hash_rows
@hash_rows ||= @rows.map { |row|
- ActiveSupport::OrderedHash[@columns.zip(row)]
+ Hash[@columns.zip(row)]
}
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index e30b481fe1..a893c0ad85 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -83,7 +83,7 @@ HEADER
# first dump primary key column
if @connection.respond_to?(:pk_and_sequence_for)
- pk, pk_seq = @connection.pk_and_sequence_for(table)
+ pk, _ = @connection.pk_and_sequence_for(table)
elsif @connection.respond_to?(:primary_key)
pk = @connection.primary_key(table)
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 868f761a33..48d2f7c9a9 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -325,16 +325,14 @@ module ActiveRecord
@_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
if @_start_transaction_state[:level] < 1
restore_state = remove_instance_variable(:@_start_transaction_state)
- if restore_state
- @attributes = @attributes.dup if @attributes.frozen?
- @new_record = restore_state[:new_record]
- @destroyed = restore_state[:destroyed]
- if restore_state.has_key?(:id)
- self.id = restore_state[:id]
- else
- @attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
- end
+ @attributes = @attributes.dup if @attributes.frozen?
+ @new_record = restore_state[:new_record]
+ @destroyed = restore_state[:destroyed]
+ if restore_state.has_key?(:id)
+ self.id = restore_state[:id]
+ else
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
index 126b6f434b..ce8d7eed42 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb
@@ -1,4 +1,11 @@
class <%= migration_class_name %> < ActiveRecord::Migration
+<%- if migration_action == 'add' -%>
+ def change
+<% attributes.each do |attribute| -%>
+ add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %>
+<%- end -%>
+ end
+<%- else -%>
def up
<% attributes.each do |attribute| -%>
<%- if migration_action -%>
@@ -14,4 +21,5 @@ class <%= migration_class_name %> < ActiveRecord::Migration
<%- end -%>
<%- end -%>
end
+<%- end -%>
end
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
index 7d4e1a7404..cd2552d9b8 100644
--- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/model/templates/migration.rb
@@ -1,5 +1,5 @@
class <%= migration_class_name %> < ActiveRecord::Migration
- def up
+ def change
create_table :<%= table_name %> do |t|
<% for attribute in attributes -%>
t.<%= attribute.type %> :<%= attribute.name %>
@@ -13,8 +13,4 @@ class <%= migration_class_name %> < ActiveRecord::Migration
add_index :<%= table_name %>, :<%= attribute.name %>_id
<% end -%>
end
-
- def down
- drop_table :<%= table_name %>
- end
end
diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb
index 646aa88d80..49b2e945c3 100644
--- a/activerecord/test/cases/adapter_test.rb
+++ b/activerecord/test/cases/adapter_test.rb
@@ -141,16 +141,4 @@ class AdapterTest < ActiveRecord::TestCase
end
end
end
-
- def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
- sql_inject = "1 select * from schema"
- assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject))
- assert_no_match(/schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
- end
-
- def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
- sql_inject = "1, 7 procedure help()"
- assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject))
- assert_no_match(/procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7))
- end
end
diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
index 90d8b0d923..b5c938b14a 100644
--- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
+++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb
@@ -105,9 +105,9 @@ class MysqlReservedWordTest < ActiveRecord::TestCase
assert_nothing_raised { x.save }
x.order = 'y'
assert_nothing_raised { x.save }
- assert_nothing_raised { y = Group.find_by_order('y') }
- assert_nothing_raised { y = Group.find(1) }
- x = Group.find(1)
+ assert_nothing_raised { Group.find_by_order('y') }
+ assert_nothing_raised { Group.find(1) }
+ Group.find(1)
end
# has_one association with reserved-word table name
diff --git a/activerecord/test/cases/associations/association_proxy_test.rb b/activerecord/test/cases/associations/association_proxy_test.rb
new file mode 100644
index 0000000000..55d8da4c4e
--- /dev/null
+++ b/activerecord/test/cases/associations/association_proxy_test.rb
@@ -0,0 +1,52 @@
+require "cases/helper"
+
+module ActiveRecord
+ module Associations
+ class AsssociationProxyTest < ActiveRecord::TestCase
+ class FakeOwner
+ attr_accessor :new_record
+ alias :new_record? :new_record
+
+ def initialize
+ @new_record = false
+ end
+ end
+
+ class FakeReflection < Struct.new(:options, :klass)
+ def initialize options = {}, klass = nil
+ super
+ end
+
+ def check_validity!
+ true
+ end
+ end
+
+ class FakeTarget
+ end
+
+ class FakeTargetProxy < AssociationProxy
+ def association_scope
+ true
+ end
+
+ def find_target
+ FakeTarget.new
+ end
+ end
+
+ def test_method_missing_error
+ reflection = FakeReflection.new({}, Object.new)
+ owner = FakeOwner.new
+ proxy = FakeTargetProxy.new(owner, reflection)
+
+ exception = assert_raises(NoMethodError) do
+ proxy.omg
+ end
+
+ assert_match('omg', exception.message)
+ assert_match(FakeTarget.name, exception.message)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index cdde9a80d3..01073bca3d 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -78,27 +78,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key)
- assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key")
+ assert citibank_result.association_cache.key?(:firm_with_primary_key)
end
def test_eager_loading_with_primary_key_as_symbol
Firm.create("name" => "Apple")
Client.create("name" => "Citibank", :firm_name => "Apple")
citibank_result = Client.find(:first, :conditions => {:name => "Citibank"}, :include => :firm_with_primary_key_symbols)
- assert_not_nil citibank_result.instance_variable_get("@firm_with_primary_key_symbols")
- end
-
- def test_no_unexpected_aliasing
- first_firm = companies(:first_firm)
- another_firm = companies(:another_firm)
-
- citibank = Account.create("credit_limit" => 10)
- citibank.firm = first_firm
- original_proxy = citibank.firm
- citibank.firm = another_firm
-
- assert_equal first_firm.object_id, original_proxy.target.object_id
- assert_equal another_firm.object_id, citibank.firm.target.object_id
+ assert citibank_result.association_cache.key?(:firm_with_primary_key_symbols)
end
def test_creating_the_belonging_object
@@ -133,6 +120,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal apple.name, client.firm_name
end
+ def test_create!
+ client = Client.create!(:name => "Jimmy")
+ account = client.create_account!(:credit_limit => 10)
+ assert_equal account, client.account
+ assert account.persisted?
+ client.save
+ client.reload
+ assert_equal account, client.account
+ end
+
+ def test_failing_create!
+ client = Client.create!(:name => "Jimmy")
+ assert_raise(ActiveRecord::RecordInvalid) { client.create_account! }
+ assert_not_nil client.account
+ assert client.account.new_record?
+ end
+
def test_natural_assignment_to_nil
client = Client.find(3)
client.firm = nil
@@ -159,6 +163,15 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
end
+ def test_with_polymorphic_and_condition
+ sponsor = Sponsor.create
+ member = Member.create :name => "Bert"
+ sponsor.sponsorable = member
+
+ assert_equal member, sponsor.sponsorable
+ assert_nil sponsor.sponsorable_with_conditions
+ end
+
def test_with_select
assert_equal Company.find(2).firm_with_select.attributes.size, 1
assert_equal Company.find(2, :include => :firm_with_select ).firm_with_select.attributes.size, 1
@@ -175,17 +188,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
end
- def test_belongs_to_with_primary_key_counter
- debate = Topic.create("title" => "debate")
- assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
-
- trash = debate.replies_with_primary_key.create("title" => "blah!", "content" => "world around!")
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply created"
-
- trash.destroy
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count"), "First reply deleted"
- end
-
def test_belongs_to_counter_with_assigning_nil
p = Post.find(1)
c = Comment.find(1)
@@ -198,16 +200,23 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal 1, Post.find(p.id).comments.size
end
- def test_belongs_to_with_primary_key_counter_with_assigning_nil
- debate = Topic.create("title" => "debate")
- reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
+ def test_belongs_to_with_primary_key_counter
+ debate = Topic.create("title" => "debate")
+ debate2 = Topic.create("title" => "debate2")
+ reply = Reply.create("title" => "blah!", "content" => "world around!", "parent_title" => "debate")
- assert_equal debate.title, reply.parent_title
- assert_equal 1, Topic.find(debate.id).send(:read_attribute, "replies_count")
+ assert_equal 1, debate.reload.replies_count
+ assert_equal 0, debate2.reload.replies_count
+
+ reply.topic_with_primary_key = debate2
+
+ assert_equal 0, debate.reload.replies_count
+ assert_equal 1, debate2.reload.replies_count
reply.topic_with_primary_key = nil
- assert_equal 0, Topic.find(debate.id).send(:read_attribute, "replies_count")
+ assert_equal 0, debate.reload.replies_count
+ assert_equal 0, debate2.reload.replies_count
end
def test_belongs_to_counter_with_reassigning
@@ -311,13 +320,21 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
end
- def test_forgetting_the_load_when_foreign_key_enters_late
- c = Client.new
- assert_nil c.firm_with_basic_id
+ def test_setting_foreign_key_after_nil_target_loaded
+ client = Client.new
+ client.firm_with_basic_id
+ client.firm_id = 1
- c.firm_id = 1
- # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first
- assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id
+ assert_equal companies(:first_firm), client.firm_with_basic_id
+ end
+
+ def test_polymorphic_setting_foreign_key_after_nil_target_loaded
+ sponsor = Sponsor.new
+ sponsor.sponsorable
+ sponsor.sponsorable_id = 1
+ sponsor.sponsorable_type = "Member"
+
+ assert_equal members(:groucho), sponsor.sponsorable
end
def test_field_name_same_as_foreign_key
@@ -419,6 +436,18 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert_nil sponsor.sponsorable_id
end
+ def test_assignment_updates_foreign_id_field_for_new_and_saved_records
+ client = Client.new
+ saved_firm = Firm.create :name => "Saved"
+ new_firm = Firm.new
+
+ client.firm = saved_firm
+ assert_equal saved_firm.id, client.client_of
+
+ client.firm = new_firm
+ assert_nil client.client_of
+ end
+
def test_polymorphic_assignment_with_primary_key_updates_foreign_id_field_for_new_and_saved_records
essay = Essay.new
saved_writer = Author.create(:name => "David")
@@ -537,4 +566,67 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
assert proxy.stale_target?
assert_equal companies(:first_firm), sponsor.sponsorable
end
+
+ def test_reloading_association_with_key_change
+ client = companies(:second_client)
+ firm = client.firm # note this is a proxy object
+
+ client.firm = companies(:another_firm)
+ assert_equal companies(:another_firm), firm.reload
+
+ client.client_of = companies(:first_firm).id
+ assert_equal companies(:first_firm), firm.reload
+ end
+
+ def test_polymorphic_counter_cache
+ tagging = taggings(:welcome_general)
+ post = posts(:welcome)
+ comment = comments(:greetings)
+
+ assert_difference 'post.reload.taggings_count', -1 do
+ assert_difference 'comment.reload.taggings_count', +1 do
+ tagging.taggable = comment
+ end
+ end
+ end
+
+ def test_polymorphic_with_custom_foreign_type
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ groucho = members(:groucho)
+ other = members(:some_other_guy)
+
+ assert_equal groucho, sponsor.sponsorable
+ assert_equal groucho, sponsor.thing
+
+ sponsor.thing = other
+
+ assert_equal other, sponsor.sponsorable
+ assert_equal other, sponsor.thing
+
+ sponsor.sponsorable = groucho
+
+ assert_equal groucho, sponsor.sponsorable
+ assert_equal groucho, sponsor.thing
+ end
+
+ def test_build_with_conditions
+ client = companies(:second_client)
+ firm = client.build_bob_firm
+
+ assert_equal "Bob", firm.name
+ end
+
+ def test_create_with_conditions
+ client = companies(:second_client)
+ firm = client.create_bob_firm
+
+ assert_equal "Bob", firm.name
+ end
+
+ def test_create_bang_with_conditions
+ client = companies(:second_client)
+ firm = client.create_bob_firm!
+
+ assert_equal "Bob", firm.name
+ end
end
diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb
index 6a30e2905b..2d0d4541b4 100644
--- a/activerecord/test/cases/associations/callbacks_test.rb
+++ b/activerecord/test/cases/associations/callbacks_test.rb
@@ -3,6 +3,7 @@ require 'models/post'
require 'models/author'
require 'models/project'
require 'models/developer'
+require 'models/company'
class AssociationCallbacksTest < ActiveRecord::TestCase
fixtures :posts, :authors, :projects, :developers
@@ -81,6 +82,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase
assert_equal callback_log, jack.post_log
end
+ def test_has_many_callbacks_for_destroy_on_parent
+ firm = Firm.create! :name => "Firm"
+ client = firm.clients.create! :name => "Client"
+ firm.destroy
+
+ assert_equal ["before_remove#{client.id}", "after_remove#{client.id}"], firm.log
+ end
+
def test_has_and_belongs_to_many_add_callback
david = developers(:david)
ar = projects(:active_record)
diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
index c7671a8c22..8957586189 100644
--- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb
+++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -4,6 +4,7 @@ require 'models/author'
require 'models/comment'
require 'models/category'
require 'models/categorization'
+require 'models/tagging'
require 'active_support/core_ext/array/random_access'
module Remembered
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index ad7eb6e4e6..e11f1009dc 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -21,12 +21,13 @@ require 'models/member'
require 'models/membership'
require 'models/club'
require 'models/categorization'
+require 'models/sponsor'
class EagerAssociationTest < ActiveRecord::TestCase
fixtures :posts, :comments, :authors, :author_addresses, :categories, :categories_posts,
:companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
:owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books,
- :developers, :projects, :developers_projects, :members, :memberships, :clubs
+ :developers, :projects, :developers_projects, :members, :memberships, :clubs, :sponsors
def setup
# preheat table existence caches
@@ -211,6 +212,15 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
+ def test_finding_with_includes_on_null_belongs_to_polymorphic_association
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ sponsor.update_attributes!(:sponsorable => nil)
+ sponsor = assert_queries(1) { Sponsor.find(sponsor.id, :include => :sponsorable) }
+ assert_no_queries do
+ assert_equal nil, sponsor.sponsorable
+ end
+ end
+
def test_loading_from_an_association
posts = authors(:david).posts.find(:all, :include => :comments, :order => "posts.id")
assert_equal 2, posts.first.comments.size
@@ -901,11 +911,25 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- def test_preloading_empty_polymorphic_parent
+ def test_preloading_empty_belongs_to
+ c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1)
+
+ client = assert_queries(2) { Client.preload(:firm).find(c.id) }
+ assert_no_queries { assert_nil client.firm }
+ end
+
+ def test_preloading_empty_belongs_to_polymorphic
t = Tagging.create!(:taggable_type => 'Post', :taggable_id => Post.maximum(:id) + 1, :tag => tags(:general))
- assert_queries(2) { @tagging = Tagging.preload(:taggable).find(t.id) }
- assert_no_queries { assert ! @tagging.taggable }
+ tagging = assert_queries(2) { Tagging.preload(:taggable).find(t.id) }
+ assert_no_queries { assert_nil tagging.taggable }
+ end
+
+ def test_preloading_through_empty_belongs_to
+ c = Client.create!(:name => 'Foo', :client_of => Company.maximum(:id) + 1)
+
+ client = assert_queries(2) { Client.preload(:accounts).find(c.id) }
+ assert_no_queries { assert client.accounts.empty? }
end
def test_preloading_has_many_through_with_uniq
@@ -913,4 +937,14 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert_equal 1, mary.unique_categorized_posts.length
assert_equal 1, mary.unique_categorized_post_ids.length
end
+
+ def test_preloading_polymorphic_with_custom_foreign_type
+ sponsor = sponsors(:moustache_club_sponsor_for_groucho)
+ groucho = members(:groucho)
+
+ sponsor = assert_queries(2) {
+ Sponsor.includes(:thing).where(:id => sponsor.id).first
+ }
+ assert_no_queries { assert_equal groucho, sponsor.thing }
+ end
end
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 705550216c..30730c7094 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -101,38 +101,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 't1', record[1]
end
- def test_should_record_timestamp_for_join_table
- setup_data_for_habtm_case
-
- con = ActiveRecord::Base.connection
- sql = 'select * from countries_treaties'
- record = con.select_rows(sql).last
- assert_not_nil record[2]
- assert_not_nil record[3]
- if current_adapter?(:Mysql2Adapter, :OracleAdapter)
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2].to_s(:db)
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3].to_s(:db)
- else
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[2]
- assert_match %r{\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}}, record[3]
- end
- end
-
- def test_should_record_timestamp_for_join_table_only_if_timestamp_should_be_recorded
- begin
- Treaty.record_timestamps = false
- setup_data_for_habtm_case
-
- con = ActiveRecord::Base.connection
- sql = 'select * from countries_treaties'
- record = con.select_rows(sql).last
- assert_nil record[2]
- assert_nil record[3]
- ensure
- Treaty.record_timestamps = true
- end
- end
-
def test_has_and_belongs_to_many
david = Developer.find(1)
@@ -218,34 +186,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_equal 2, aredridel.projects(true).size
end
- def test_adding_uses_default_values_on_join_table
- ac = projects(:action_controller)
- assert !developers(:jamis).projects.include?(ac)
- developers(:jamis).projects << ac
-
- assert developers(:jamis, :reload).projects.include?(ac)
- project = developers(:jamis).projects.detect { |p| p == ac }
- assert_equal 1, project.access_level.to_i
- end
-
- def test_habtm_attribute_access_and_respond_to
- project = developers(:jamis).projects[0]
- assert project.has_attribute?("name")
- assert project.has_attribute?("joined_on")
- assert project.has_attribute?("access_level")
- assert project.respond_to?("name")
- assert project.respond_to?("name=")
- assert project.respond_to?("name?")
- assert project.respond_to?("joined_on")
- # given that the 'join attribute' won't be persisted, I don't
- # think we should define the mutators
- #assert project.respond_to?("joined_on=")
- assert project.respond_to?("joined_on?")
- assert project.respond_to?("access_level")
- #assert project.respond_to?("access_level=")
- assert project.respond_to?("access_level?")
- end
-
def test_habtm_adding_before_save
no_of_devels = Developer.count
no_of_projects = Project.count
@@ -430,10 +370,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert DeveloperWithBeforeDestroyRaise.connection.select_all("SELECT * FROM developers_projects WHERE developer_id = 1").empty?
end
- def test_additional_columns_from_join_table
- assert_date_from_db Date.new(2004, 10, 10), Developer.find(1).projects.first.joined_on.to_date
- end
-
def test_destroying
david = Developer.find(1)
active_record = Project.find(1)
@@ -675,25 +611,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
assert_respond_to categories(:technology).select_testing_posts.find(:first), :correctness_marker
end
- def test_updating_attributes_on_rich_associations
- david = projects(:action_controller).developers.first
- david.name = "DHH"
- assert_raise(ActiveRecord::ReadOnlyRecord) { david.save! }
- end
-
- def test_updating_attributes_on_rich_associations_with_limited_find_from_reflection
- david = projects(:action_controller).selected_developers.first
- david.name = "DHH"
- assert_nothing_raised { david.save! }
- end
-
-
- def test_updating_attributes_on_rich_associations_with_limited_find
- david = projects(:action_controller).developers.find(:all, :select => "developers.*").first
- david.name = "DHH"
- assert david.save!
- end
-
def test_join_table_alias
assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL').size
end
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 2b7ad3642a..1ce91d7211 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -975,6 +975,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert !firm.clients.include?(:first_client)
end
+ def test_replace_failure
+ firm = companies(:first_firm)
+ account = Account.new
+ orig_accounts = firm.accounts.to_a
+
+ assert !account.valid?
+ assert !orig_accounts.empty?
+ assert_raise ActiveRecord::RecordNotSaved do
+ firm.accounts = [account]
+ end
+ assert_equal orig_accounts, firm.accounts
+ end
+
def test_get_ids
assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
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 e98d178ff5..7235631b5a 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -32,6 +32,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
Reader.create :person_id => 0, :post_id => 0
end
+ def test_include?
+ person = Person.new
+ post = Post.new
+ person.posts << post
+ assert person.posts.include?(post)
+ end
+
def test_associate_existing
assert_queries(2) { posts(:thinking); people(:david) }
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 64449df8f5..d9b6694dd8 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -2,9 +2,12 @@ require "cases/helper"
require 'models/developer'
require 'models/project'
require 'models/company'
+require 'models/ship'
+require 'models/pirate'
class HasOneAssociationsTest < ActiveRecord::TestCase
- fixtures :accounts, :companies, :developers, :projects, :developers_projects
+ self.use_transactional_fixtures = false unless supports_savepoints?
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects, :ships, :pirates
def setup
Account.destroyed_account_ids.clear
@@ -91,18 +94,18 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
def test_nullification_on_association_change
firm = companies(:rails_core)
old_account_id = firm.account.id
- firm.account = Account.new
+ firm.account = Account.new(:credit_limit => 5)
# account is dependent with nullify, therefore its firm_id should be nil
assert_nil Account.find(old_account_id).firm_id
end
def test_association_change_calls_delete
- companies(:first_firm).deletable_account = Account.new
+ companies(:first_firm).deletable_account = Account.new(:credit_limit => 5)
assert_equal [], Account.destroyed_account_ids[companies(:first_firm).id]
end
def test_association_change_calls_destroy
- companies(:first_firm).account = Account.new
+ companies(:first_firm).account = Account.new(:credit_limit => 5)
assert_equal [companies(:first_firm).id], Account.destroyed_account_ids[companies(:first_firm).id]
end
@@ -116,35 +119,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal company.account, account
end
- def test_assignment_without_replacement
- apple = Firm.create("name" => "Apple")
- citibank = Account.create("credit_limit" => 10)
- apple.account = citibank
- assert_equal apple.id, citibank.firm_id
-
- hsbc = apple.build_account({ :credit_limit => 20}, false)
- assert_equal apple.id, hsbc.firm_id
- hsbc.save
- assert_equal apple.id, citibank.firm_id
-
- nykredit = apple.create_account({ :credit_limit => 30}, false)
- assert_equal apple.id, nykredit.firm_id
- assert_equal apple.id, citibank.firm_id
- assert_equal apple.id, hsbc.firm_id
- end
-
- def test_assignment_without_replacement_on_create
- apple = Firm.create("name" => "Apple")
- citibank = Account.create("credit_limit" => 10)
- apple.account = citibank
- assert_equal apple.id, citibank.firm_id
-
- hsbc = apple.create_account({:credit_limit => 10}, false)
- assert_equal apple.id, hsbc.firm_id
- hsbc.save
- assert_equal apple.id, citibank.firm_id
- end
-
def test_dependence
num_accounts = Account.count
@@ -193,18 +167,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
- def test_build_association_twice_without_saving_affects_nothing
- count_of_account = Account.count
- firm = Firm.find(:first)
- firm.build_account("credit_limit" => 1000)
- firm.build_account("credit_limit" => 2000)
+ def test_create_association
+ firm = Firm.create(:name => "GlobalMegaCorp")
+ account = firm.create_account(:credit_limit => 1000)
+ assert_equal account, firm.reload.account
+ end
- assert_equal count_of_account, Account.count
+ def test_create_association_with_bang
+ firm = Firm.create(:name => "GlobalMegaCorp")
+ account = firm.create_account!(:credit_limit => 1000)
+ assert_equal account, firm.reload.account
end
- def test_create_association
+ def test_create_association_with_bang_failing
firm = Firm.create(:name => "GlobalMegaCorp")
- account = firm.create_account(:credit_limit => 1000)
+ assert_raise ActiveRecord::RecordInvalid do
+ firm.create_account!
+ end
+ account = firm.account
+ assert_not_nil account
+ account.credit_limit = 5
+ account.save
assert_equal account, firm.reload.account
end
@@ -218,17 +201,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
assert_equal account, firm.account
end
- def test_failing_build_association
- firm = Firm.new("name" => "GlobalMegaCorp")
- firm.save
-
- firm.account = account = Account.new
- assert_equal account, firm.account
- assert !account.save
- assert_equal account, firm.account
- assert_equal ["can't be empty"], account.errors["credit_limit"]
- end
-
def test_create
firm = Firm.new("name" => "GlobalMegaCorp")
firm.save
@@ -256,14 +228,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
firm.destroy
end
- def test_finding_with_interpolated_condition
- firm = Firm.find(:first)
- superior = firm.clients.create(:name => 'SuperiorCo')
- superior.rating = 10
- superior.save
- assert_equal 10, firm.clients_with_interpolated_conditions.first.rating
- end
-
def test_assignment_before_child_saved
firm = Firm.find(1)
firm.account = a = Account.new("credit_limit" => 1000)
@@ -330,4 +294,51 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
new_account = companies(:first_firm).build_account(:firm_name => 'Account')
assert_equal new_account.firm_name, "Account"
end
+
+ def test_creation_failure_without_dependent_option
+ pirate = pirates(:blackbeard)
+ orig_ship = pirate.ship.target
+
+ assert_equal ships(:black_pearl), orig_ship
+ new_ship = pirate.create_ship
+ assert_not_equal ships(:black_pearl), new_ship
+ assert_equal new_ship, pirate.ship
+ assert new_ship.new_record?
+ assert_nil orig_ship.pirate_id
+ assert !orig_ship.changed? # check it was saved
+ end
+
+ def test_creation_failure_with_dependent_option
+ pirate = pirates(:blackbeard).becomes(DestructivePirate)
+ orig_ship = pirate.dependent_ship.target
+
+ new_ship = pirate.create_dependent_ship
+ assert new_ship.new_record?
+ assert orig_ship.destroyed?
+ end
+
+ def test_replacement_failure_due_to_existing_record_should_raise_error
+ pirate = pirates(:blackbeard)
+ pirate.ship.name = nil
+
+ assert !pirate.ship.valid?
+ assert_raise(ActiveRecord::RecordNotSaved) do
+ pirate.ship = ships(:interceptor)
+ end
+ assert_equal ships(:black_pearl), pirate.ship
+ assert_equal pirate.id, pirate.ship.pirate_id
+ end
+
+ def test_replacement_failure_due_to_new_record_should_raise_error
+ pirate = pirates(:blackbeard)
+ new_ship = Ship.new
+
+ assert_raise(ActiveRecord::RecordNotSaved) do
+ pirate.ship = new_ship
+ end
+ assert_equal ships(:black_pearl), pirate.ship
+ assert_equal pirate.id, pirate.ship.pirate_id
+ assert_equal pirate.id, ships(:black_pearl).reload.pirate_id
+ assert_nil new_ship.pirate_id
+ 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 7d55d909a7..91d3025468 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -197,7 +197,7 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
MemberDetail.find(:all, :include => :member_type)
end
@new_detail = @member_details[0]
- assert @new_detail.loaded_member_type?
+ assert @new_detail.send(:association_proxy, :member_type).loaded?
assert_not_nil assert_no_queries { @new_detail.member_type }
end
@@ -266,4 +266,26 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
assert proxy.stale_target?
assert_equal dashboards(:second), minivan.dashboard
end
+
+ def test_has_one_through_belongs_to_setting_belongs_to_foreign_key_after_nil_target_loaded
+ minivan = Minivan.new
+
+ minivan.dashboard
+ proxy = minivan.send(:association_instance_get, :dashboard)
+
+ minivan.speedometer_id = speedometers(:second).id
+
+ assert proxy.stale_target?
+ assert_equal dashboards(:second), minivan.dashboard
+ end
+
+ def test_assigning_has_one_through_belongs_to_with_new_record_owner
+ minivan = Minivan.new
+ dashboard = dashboards(:cool_first)
+
+ minivan.dashboard = dashboard
+
+ assert_equal dashboard, minivan.dashboard
+ assert_equal dashboard, minivan.speedometer.dashboard
+ end
end
diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb
index 0491b2d1fa..e9a57a00a0 100644
--- a/activerecord/test/cases/associations/inverse_associations_test.rb
+++ b/activerecord/test/cases/associations/inverse_associations_test.rb
@@ -114,7 +114,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_built_child
- m = men(:gordon)
+ m = Man.find(:first)
f = m.build_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -125,7 +125,7 @@ class InverseHasOneTests < ActiveRecord::TestCase
end
def test_parent_instance_should_be_shared_with_newly_created_child
- m = men(:gordon)
+ m = Man.find(:first)
f = m.create_face(:description => 'haunted')
assert_not_nil f.man
assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
@@ -146,39 +146,6 @@ class InverseHasOneTests < ActiveRecord::TestCase
assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_newly_built_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.build_face({:description => 'haunted'}, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.create_face({:description => 'haunted'}, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
- def test_parent_instance_should_be_shared_with_newly_created_child_via_bang_method_when_we_dont_replace_existing
- m = Man.find(:first)
- f = m.face.create!({:description => 'haunted'}, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
def test_parent_instance_should_be_shared_with_replaced_via_accessor_child
m = Man.find(:first)
f = Face.new(:description => 'haunted')
@@ -203,18 +170,6 @@ class InverseHasOneTests < ActiveRecord::TestCase
assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_replaced_via_method_child_when_we_dont_replace_existing
- m = Man.find(:first)
- f = Face.new(:description => 'haunted')
- m.face.replace(f, false)
- assert_not_nil f.man
- assert_equal m.name, f.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to parent instance"
- f.man.name = 'Mungo'
- assert_equal m.name, f.man.name, "Name of man should be the same after changes to replaced-child-owned instance"
- end
-
def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error
assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.find(:first).dirty_face }
end
@@ -257,17 +212,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
end
end
- def test_parent_instance_should_be_shared_with_newly_built_child
- m = men(:gordon)
- i = m.interests.build(:topic => 'Industrial Revolution Re-enactment')
- assert_not_nil i.man
- assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
- i.man.name = 'Mungo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
- end
-
def test_parent_instance_should_be_shared_with_newly_block_style_built_child
m = Man.find(:first)
i = m.interests.build {|ii| ii.topic = 'Industrial Revolution Re-enactment'}
@@ -280,17 +224,6 @@ class InverseHasManyTests < ActiveRecord::TestCase
assert_equal m.name, i.man.name, "Name of man should be the same after changes to just-built-child-owned instance"
end
- def test_parent_instance_should_be_shared_with_newly_created_child
- m = men(:gordon)
- i = m.interests.create(:topic => 'Industrial Revolution Re-enactment')
- assert_not_nil i.man
- assert_equal m.name, i.man.name, "Name of man should be the same before changes to parent instance"
- m.name = 'Bongo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to parent instance"
- i.man.name = 'Mungo'
- assert_equal m.name, i.man.name, "Name of man should be the same after changes to newly-created-child-owned instance"
- end
-
def test_parent_instance_should_be_shared_with_newly_created_via_bang_method_child
m = Man.find(:first)
i = m.interests.create!(:topic => 'Industrial Revolution Re-enactment')
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 263c90097f..c50fcd3f33 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -13,7 +13,8 @@ require 'models/book'
require 'models/citation'
class AssociationsJoinModelTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
+
fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books,
# Reload edges table from fixtures as otherwise repeated test was failing
:edges
@@ -85,13 +86,6 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
end
end
- def test_polymorphic_has_many_going_through_join_model_with_disabled_include
- assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
- assert_queries 1 do
- tag.tagging
- end
- end
-
def test_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins
assert_equal tags(:general), tag = posts(:welcome).tags.add_joins_and_select.first
tag.author_id
@@ -529,7 +523,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_has_many_through_collection_size_uses_counter_cache_if_it_exists
author = authors(:david)
- author.stubs(:read_attribute).with('comments_count').returns(100)
+ author.stubs(:_read_attribute).with('comments_count').returns(100)
assert_equal 100, author.comments.size
assert !author.comments.loaded?
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index e3cbae4a84..a29e4349d6 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -247,6 +247,12 @@ class AttributeMethodsTest < ActiveRecord::TestCase
# puts ""
end
+ def test_read_overridden_attribute
+ topic = Topic.new(:title => 'a')
+ def topic.title() 'b' end
+ assert_equal 'a', topic[:title]
+ end
+
def test_query_attribute_string
[nil, "", " "].each do |value|
assert_equal false, Topic.new(:author_name => value).author_name?
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 27aee400f9..11c0c5b0ef 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -90,7 +90,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm = Firm.find(:first)
assert firm.valid?
- firm.account = Account.new
+ firm.build_account
assert !firm.account.valid?
assert !firm.valid?
@@ -102,7 +102,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas
firm = Firm.find(:first)
assert firm.valid?
- firm.unvalidated_account = Account.new
+ firm.build_unvalidated_account
assert !firm.unvalidated_account.valid?
assert firm.valid?
@@ -340,6 +340,13 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
tags(:misc).create_tagging(:taggable => posts(:thinking))
assert_equal num_tagging + 1, Tagging.count
end
+
+ def test_build_and_then_save_parent_should_not_reload_target
+ client = Client.find(:first)
+ apple = client.build_firm(:name => "Apple")
+ client.save!
+ assert_no_queries { assert_equal apple, client.firm }
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
@@ -565,7 +572,7 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase
end
class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -790,7 +797,7 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -910,7 +917,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@ship = Ship.create(:name => 'Nights Dirty Lightning')
@@ -1157,7 +1164,7 @@ module AutosaveAssociationOnACollectionAssociationTests
end
class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@association_name = :birds
@@ -1171,7 +1178,7 @@ class TestAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCase
end
class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@association_name = :parrots
@@ -1186,7 +1193,7 @@ class TestAutosaveAssociationOnAHasAndBelongsToManyAssociation < ActiveRecord::T
end
class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -1202,7 +1209,7 @@ class TestAutosaveAssociationValidationsOnAHasManyAssociation < ActiveRecord::Te
end
class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -1223,7 +1230,7 @@ class TestAutosaveAssociationValidationsOnAHasOneAssociation < ActiveRecord::Tes
end
class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -1243,7 +1250,7 @@ class TestAutosaveAssociationValidationsOnABelongsToAssociation < ActiveRecord::
end
class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create(:catchphrase => "Don' botharrr talkin' like one, savvy?")
@@ -1265,7 +1272,7 @@ class TestAutosaveAssociationValidationsOnAHABTMAssociation < ActiveRecord::Test
end
class TestAutosaveAssociationValidationMethodsGeneration < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.new
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 594f3b80c6..a58d5dec81 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1503,4 +1503,11 @@ class BasicsTest < ActiveRecord::TestCase
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
+
+ def test_marshal_round_trip
+ expected = posts(:welcome)
+ actual = Marshal.load(Marshal.dump(expected))
+
+ assert_equal expected.attributes, actual.attributes
+ end
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index dcc49e12ca..6f65fb96d1 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -7,6 +7,7 @@ class EachTest < ActiveRecord::TestCase
def setup
@posts = Post.order("id asc")
@total = Post.count
+ Post.count('id') # preheat arel's table cache
end
def test_each_should_excecute_one_query_per_batch
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 5cb8485b4b..644c9cb528 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -23,6 +23,11 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal 53.0, value
end
+ def test_should_return_decimal_average_of_integer_field
+ value = Account.average(:id)
+ assert_equal 3.5, value
+ end
+
def test_should_return_nil_as_average
assert_nil NumericData.average(:bank_balance)
end
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index f9bbc5299b..2cc993b6ed 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -26,6 +26,15 @@ def current_adapter?(*types)
end
end
+def in_memory_db?
+ current_adapter?(:SQLiteAdapter) &&
+ ActiveRecord::Base.connection_pool.spec.config[:database] == ":memory:"
+end
+
+def supports_savepoints?
+ ActiveRecord::Base.connection.supports_savepoints?
+end
+
def with_env_tz(new_tz = 'US/Eastern')
old_tz, ENV['TZ'] = ENV['TZ'], new_tz
yield
@@ -100,11 +109,11 @@ class ActiveSupport::TestCase
end
end
-# silence verbose schema loading
-original_stdout = $stdout
-$stdout = StringIO.new
+def load_schema
+ # silence verbose schema loading
+ original_stdout = $stdout
+ $stdout = StringIO.new
-begin
adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
adapter_specific_schema_file = SCHEMA_ROOT + "/#{adapter_name}_specific_schema.rb"
@@ -117,6 +126,8 @@ ensure
$stdout = original_stdout
end
+load_schema
+
class << Time
unless method_defined? :now_before_time_travel
alias_method :now_before_time_travel, :now
@@ -133,4 +144,3 @@ class << Time
@now = nil
end
end
-
diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 9fac9373eb..680d4ca5dd 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -203,7 +203,7 @@ class InheritanceTest < ActiveRecord::TestCase
def test_eager_load_belongs_to_something_inherited
account = Account.find(1, :include => :firm)
- assert_not_nil account.instance_variable_get("@firm"), "nil proves eager load failed"
+ assert account.association_cache.key?(:firm), "nil proves eager load failed"
end
def test_eager_load_belongs_to_primary_key_quoting
diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb
index b8c3ffb9cb..0558deb71b 100644
--- a/activerecord/test/cases/lifecycle_test.rb
+++ b/activerecord/test/cases/lifecycle_test.rb
@@ -101,9 +101,10 @@ class LifecycleTest < ActiveRecord::TestCase
fixtures :topics, :developers, :minimalistics
def test_before_destroy
- original_count = Topic.count
- (topic_to_be_destroyed = Topic.find(1)).destroy
- assert_equal original_count - (1 + topic_to_be_destroyed.replies.size), Topic.count
+ topic = Topic.find(1)
+ assert_difference 'Topic.count', -(1 + topic.replies.size) do
+ topic.destroy
+ end
end
def test_auto_observer
diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb
index f9678cb0c5..2a72838d06 100644
--- a/activerecord/test/cases/locking_test.rb
+++ b/activerecord/test/cases/locking_test.rb
@@ -19,11 +19,6 @@ end
class OptimisticLockingTest < ActiveRecord::TestCase
fixtures :people, :legacy_things, :references
- # need to disable transactional fixtures, because otherwise the sqlite3
- # adapter (at least) chokes when we try and change the schema in the middle
- # of a test (see test_increment_counter_*).
- self.use_transactional_fixtures = false
-
def test_lock_existing
p1 = Person.find(1)
p2 = Person.find(1)
@@ -152,6 +147,33 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_equal "unchangeable name", p.first_name
end
+ def test_quote_table_name
+ ref = references(:michael_magician)
+ ref.favourite = !ref.favourite
+ assert ref.save
+ end
+
+ # Useful for partial updates, don't only update the lock_version if there
+ # is nothing else being updated.
+ def test_update_without_attributes_does_not_only_update_lock_version
+ assert_nothing_raised do
+ p1 = Person.create!(:first_name => 'anika')
+ lock_version = p1.lock_version
+ p1.save
+ p1.reload
+ assert_equal lock_version, p1.lock_version
+ end
+ end
+end
+
+class OptimisticLockingWithSchemaChangeTest < ActiveRecord::TestCase
+ fixtures :people, :legacy_things, :references
+
+ # need to disable transactional fixtures, because otherwise the sqlite3
+ # adapter (at least) chokes when we try and change the schema in the middle
+ # of a test (see test_increment_counter_*).
+ self.use_transactional_fixtures = false
+
{ :lock_version => Person, :custom_lock_version => LegacyThing }.each do |name, model|
define_method("test_increment_counter_updates_#{name}") do
counter_test model, 1 do |id|
@@ -198,24 +220,6 @@ class OptimisticLockingTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::RecordNotFound) { LegacyThing.find(t.id) }
end
- def test_quote_table_name
- ref = references(:michael_magician)
- ref.favourite = !ref.favourite
- assert ref.save
- end
-
- # Useful for partial updates, don't only update the lock_version if there
- # is nothing else being updated.
- def test_update_without_attributes_does_not_only_update_lock_version
- assert_nothing_raised do
- p1 = Person.create!(:first_name => 'anika')
- lock_version = p1.lock_version
- p1.save
- p1.reload
- assert_equal lock_version, p1.lock_version
- end
- end
-
private
def add_counter_column_to(model, col='test_count')
@@ -252,7 +256,7 @@ end
# TODO: The Sybase, and OpenBase adapters currently have no support for pessimistic locking
-unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) || in_memory_db?
class PessimisticLockingTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
fixtures :people, :readers
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 1a65045ded..a5a9965c3a 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -14,7 +14,7 @@ if ActiveRecord::Base.connection.supports_migrations?
class Reminder < ActiveRecord::Base; end
class ActiveRecord::Migration
- class <<self
+ class << self
attr_accessor :message_count
end
@@ -2083,4 +2083,3 @@ if ActiveRecord::Base.connection.supports_migrations?
end
end
end
-
diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb
index 14870cb0e2..a2041af16a 100644
--- a/activerecord/test/cases/modules_test.rb
+++ b/activerecord/test/cases/modules_test.rb
@@ -49,7 +49,6 @@ class ModulesTest < ActiveRecord::TestCase
def test_find_account_and_include_company
account = MyApplication::Billing::Account.find(1, :include => :firm)
- assert_kind_of MyApplication::Business::Firm, account.instance_variable_get('@firm')
assert_kind_of MyApplication::Business::Firm, account.firm
end
diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb
index 7d9b1104cd..e1f938be84 100644
--- a/activerecord/test/cases/nested_attributes_test.rb
+++ b/activerecord/test/cases/nested_attributes_test.rb
@@ -411,7 +411,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase
def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id
@pirate.stubs(:id).returns('ABC1X')
- @ship.stubs(:pirate_id).returns(@pirate.id) # prevents pirate from being reloaded due to non-matching foreign key
@ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' }
assert_equal 'Arr', @ship.pirate.catchphrase
@@ -860,7 +859,7 @@ class TestNestedAttributesWithNonStandardPrimaryKeys < ActiveRecord::TestCase
end
class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@pirate = Pirate.create!(:catchphrase => "My baby takes tha mornin' train!")
@@ -900,7 +899,7 @@ class TestHasOneAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRe
end
class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@ship = Ship.create!(:name => "The good ship Dollypop")
diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb
index de5fa140ba..6269437b14 100644
--- a/activerecord/test/cases/pooled_connections_test.rb
+++ b/activerecord/test/cases/pooled_connections_test.rb
@@ -137,4 +137,4 @@ class PooledConnectionsTest < ActiveRecord::TestCase
def add_record(name)
ActiveRecord::Base.connection_pool.with_connection { Project.create! :name => name }
end
-end unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
+end unless current_adapter?(:FrontBase) || in_memory_db?
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index 33916c4e46..53aefc7b58 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -63,7 +63,7 @@ class QueryCacheTest < ActiveRecord::TestCase
# Oracle adapter returns count() as Fixnum or Float
if current_adapter?(:OracleAdapter)
assert_kind_of Numeric, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
- elsif current_adapter?(:SQLite3Adapter) && SQLite3::Version::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
+ elsif current_adapter?(:SQLite3Adapter) && SQLite3::VERSION > '1.2.5' || current_adapter?(:Mysql2Adapter) || current_adapter?(:MysqlAdapter)
# Future versions of the sqlite3 adapter will return numeric
assert_instance_of Fixnum,
Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
diff --git a/activerecord/test/cases/readonly_test.rb b/activerecord/test/cases/readonly_test.rb
index a1eb96ef09..e21109baae 100644
--- a/activerecord/test/cases/readonly_test.rb
+++ b/activerecord/test/cases/readonly_test.rb
@@ -6,11 +6,6 @@ require 'models/project'
require 'models/reader'
require 'models/person'
-# Dummy class methods to test implicit association scoping.
-def Comment.foo() find :first end
-def Project.foo() find :first end
-
-
class ReadOnlyTest < ActiveRecord::TestCase
fixtures :posts, :comments, :developers, :projects, :developers_projects, :people, :readers
@@ -49,15 +44,6 @@ class ReadOnlyTest < ActiveRecord::TestCase
Developer.joins(', projects').readonly(false).each { |d| assert !d.readonly? }
end
-
- def test_habtm_find_readonly
- dev = Developer.find(1)
- assert !dev.projects.empty?
- assert dev.projects.all?(&:readonly?)
- assert dev.projects.find(:all).all?(&:readonly?)
- assert dev.projects.readonly(true).all?(&:readonly?)
- end
-
def test_has_many_find_readonly
post = Post.find(1)
assert !post.comments.empty?
@@ -114,7 +100,13 @@ class ReadOnlyTest < ActiveRecord::TestCase
end
def test_association_collection_method_missing_scoping_not_readonly
- assert !Developer.find(1).projects.foo.readonly?
- assert !Post.find(1).comments.foo.readonly?
+ developer = Developer.find(1)
+ project = Post.find(1)
+
+ assert !developer.projects.all_as_method.first.readonly?
+ assert !developer.projects.all_as_scope.first.readonly?
+
+ assert !project.comments.all_as_method.first.readonly?
+ assert !project.comments.all_as_scope.first.readonly?
end
end
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index 901c12b26c..eb580928ba 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -8,6 +8,9 @@ require 'models/ship'
require 'models/pirate'
require 'models/price_estimate'
require 'models/tagging'
+require 'models/author'
+require 'models/post'
+require 'models/sponsor'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
@@ -127,11 +130,11 @@ class ReflectionTest < ActiveRecord::TestCase
def test_belongs_to_inferred_foreign_key_from_assoc_name
Company.belongs_to :foo
- assert_equal "foo_id", Company.reflect_on_association(:foo).primary_key_name
+ assert_equal "foo_id", Company.reflect_on_association(:foo).foreign_key
Company.belongs_to :bar, :class_name => "Xyzzy"
- assert_equal "bar_id", Company.reflect_on_association(:bar).primary_key_name
+ assert_equal "bar_id", Company.reflect_on_association(:bar).foreign_key
Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id"
- assert_equal "xyzzy_id", Company.reflect_on_association(:baz).primary_key_name
+ assert_equal "xyzzy_id", Company.reflect_on_association(:baz).foreign_key
end
def test_association_reflection_in_modules
@@ -178,8 +181,8 @@ class ReflectionTest < ActiveRecord::TestCase
def test_reflection_of_all_associations
# FIXME these assertions bust a lot
- assert_equal 37, Firm.reflect_on_all_associations.size
- assert_equal 27, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 36, Firm.reflect_on_all_associations.size
+ assert_equal 26, Firm.reflect_on_all_associations(:has_many).size
assert_equal 10, Firm.reflect_on_all_associations(:has_one).size
assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
end
@@ -203,6 +206,11 @@ class ReflectionTest < ActiveRecord::TestCase
assert_equal "name", Author.reflect_on_association(:essay).active_record_primary_key.to_s
end
+ def test_foreign_type
+ assert_equal "sponsorable_type", Sponsor.reflect_on_association(:sponsorable).foreign_type.to_s
+ assert_equal "sponsorable_type", Sponsor.reflect_on_association(:thing).foreign_type.to_s
+ end
+
def test_collection_association
assert Pirate.reflect_on_association(:birds).collection?
assert Pirate.reflect_on_association(:parrots).collection?
@@ -240,6 +248,18 @@ class ReflectionTest < ActiveRecord::TestCase
assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true, :validate => false }, Firm).validate?
end
+ def test_foreign_key
+ assert_equal "author_id", Author.reflect_on_association(:posts).foreign_key.to_s
+ assert_equal "category_id", Post.reflect_on_association(:categorizations).foreign_key.to_s
+ end
+
+ def test_primary_key_name
+ assert_deprecated do
+ assert_equal "author_id", Author.reflect_on_association(:posts).primary_key_name.to_s
+ assert_equal "category_id", Post.reflect_on_association(:categorizations).primary_key_name.to_s
+ end
+ end
+
private
def assert_reflection(klass, association, options)
assert reflection = klass.reflect_on_association(association)
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index e8672515fc..1bdf3136d4 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -259,7 +259,8 @@ class HasManyScopingTest< ActiveRecord::TestCase
end
def test_should_default_scope_on_associations_is_overriden_by_association_conditions
- assert_equal [], people(:michael).fixed_bad_references
+ reference = references(:michael_unicyclist).becomes(BadReference)
+ assert_equal [reference], people(:michael).fixed_bad_references
end
def test_should_maintain_default_scope_on_eager_loaded_associations
@@ -485,4 +486,21 @@ class DefaultScopingTest < ActiveRecord::TestCase
posts_offset_limit = Post.offset(2).limit(3)
assert_equal posts_limit_offset, posts_offset_limit
end
+
+ def test_create_with_merge
+ aaron = (PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20) &
+ PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
+ assert_equal 20, aaron.salary
+ assert_equal 'Aaron', aaron.name
+
+ aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
+ create_with(:name => 'Aaron').new
+ assert_equal 20, aaron.salary
+ assert_equal 'Aaron', aaron.name
+ end
+
+ def test_create_with_reset
+ jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
+ assert_equal 'Jamis', jamis.name
+ end
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 20bfafbc5e..5018b16b67 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -27,6 +27,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal van.id, Minivan.where(:minivan_id => van).to_a.first.minivan_id
end
+ def test_do_not_double_quote_string_id_with_array
+ van = Minivan.last
+ assert van
+ assert_equal van, Minivan.where(:minivan_id => [van]).to_a.first
+ end
+
def test_bind_values
relation = Post.scoped
assert_equal [], relation.bind_values
@@ -634,6 +640,14 @@ class RelationTest < ActiveRecord::TestCase
def test_any
posts = Post.scoped
+ # This test was failing when run on its own (as opposed to running the entire suite).
+ # The second line in the assert_queries block was causing visit_Arel_Attributes_Attribute
+ # in Arel::Visitors::ToSql to trigger a SHOW TABLES query. Running that line here causes
+ # the SHOW TABLES result to be cached so we don't have to do it again in the block.
+ #
+ # This is obviously a rubbish fix but it's the best I can come up with for now...
+ posts.where(:id => nil).any?
+
assert_queries(3) do
assert posts.any? # Uses COUNT()
assert ! posts.where(:id => nil).any?
@@ -792,4 +806,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal [rails_author], [rails_author] & relation
assert_equal [rails_author], relation & [rails_author]
end
+
+ def test_removing_limit_with_options
+ assert_not_equal 1, Post.limit(1).all(:limit => nil).count
+ end
+
+ def test_primary_key
+ assert_equal "id", Post.scoped.primary_key
+ end
end
diff --git a/activerecord/test/cases/session_store/session_test.rb b/activerecord/test/cases/session_store/session_test.rb
index 6f1c170a0c..f906bda8c3 100644
--- a/activerecord/test/cases/session_store/session_test.rb
+++ b/activerecord/test/cases/session_store/session_test.rb
@@ -5,7 +5,7 @@ require 'active_record/session_store'
module ActiveRecord
class SessionStore
class SessionTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints? && ActiveRecord::Base.connection.supports_ddl_transactions?
def setup
super
diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb
index 23ad10f3f9..f85fb4e5da 100644
--- a/activerecord/test/cases/unconnected_test.rb
+++ b/activerecord/test/cases/unconnected_test.rb
@@ -4,7 +4,7 @@ class TestRecord < ActiveRecord::Base
end
class TestUnconnectedAdapter < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
+ self.use_transactional_fixtures = false unless supports_savepoints?
def setup
@underlying = ActiveRecord::Base.connection
@@ -14,6 +14,7 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase
def teardown
@underlying = nil
ActiveRecord::Base.establish_connection(@specification)
+ load_schema if in_memory_db?
end
def test_connection_no_longer_established
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 679d67553b..b4f3dd034c 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -6,19 +6,6 @@ require 'models/warehouse_thing'
require 'models/guid'
require 'models/event'
-# The following methods in Topic are used in test_conditional_validation_*
-class Topic
- has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
- has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
-end
-
-class UniqueReply < Reply
- validates_uniqueness_of :content, :scope => 'parent_id'
-end
-
-class SillyUniqueReply < UniqueReply
-end
-
class Wizard < ActiveRecord::Base
self.abstract_class = true
diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb
index 0fc9918744..0b54c309d1 100644
--- a/activerecord/test/cases/yaml_serialization_test.rb
+++ b/activerecord/test/cases/yaml_serialization_test.rb
@@ -17,4 +17,30 @@ class YamlSerializationTest < ActiveRecord::TestCase
t = YAML.load YAML.dump topic
assert_equal topic, t
end
+
+ def test_encode_with_coder
+ topic = Topic.first
+ coder = {}
+ topic.encode_with coder
+ assert_equal({'attributes' => topic.attributes}, coder)
+ end
+
+ begin
+ require 'psych'
+
+ def test_psych_roundtrip
+ topic = Topic.first
+ assert topic
+ t = Psych.load Psych.dump topic
+ assert_equal topic, t
+ end
+
+ def test_psych_roundtrip_new_object
+ topic = Topic.new
+ assert topic
+ t = Psych.load Psych.dump topic
+ assert_equal topic.attributes, t.attributes
+ end
+ rescue LoadError
+ end
end
diff --git a/activerecord/test/connections/native_sqlite3/connection.rb b/activerecord/test/connections/native_sqlite3/connection.rb
index c517c2375e..c2aff5551f 100644
--- a/activerecord/test/connections/native_sqlite3/connection.rb
+++ b/activerecord/test/connections/native_sqlite3/connection.rb
@@ -3,21 +3,12 @@ require_dependency 'models/course'
require 'logger'
ActiveRecord::Base.logger = Logger.new("debug.log")
-class SqliteError < StandardError
-end
-
BASE_DIR = FIXTURES_ROOT
sqlite_test_db = "#{BASE_DIR}/fixture_database.sqlite3"
sqlite_test_db2 = "#{BASE_DIR}/fixture_database_2.sqlite3"
def make_connection(clazz, db_file)
ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => db_file, :timeout => 5000 } }
- unless File.exist?(db_file)
- puts "SQLite3 database not found at #{db_file}. Rebuilding it."
- sqlite_command = %Q{sqlite3 "#{db_file}" "create table a (a integer); drop table a;"}
- puts "Executing '#{sqlite_command}'"
- raise SqliteError.new("Seems that there is no sqlite3 executable available") unless system(sqlite_command)
- end
clazz.establish_connection(clazz.name)
end
diff --git a/activerecord/test/connections/native_sqlite3/in_memory_connection.rb b/activerecord/test/connections/native_sqlite3/in_memory_connection.rb
deleted file mode 100644
index 6aba9719bb..0000000000
--- a/activerecord/test/connections/native_sqlite3/in_memory_connection.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-print "Using native SQLite3\n"
-require_dependency 'models/course'
-require 'logger'
-ActiveRecord::Base.logger = Logger.new("debug.log")
-
-class SqliteError < StandardError
-end
-
-def make_connection(clazz, db_definitions_file)
- clazz.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
- File.read(SCHEMA_ROOT + "/#{db_definitions_file}").split(';').each do |command|
- clazz.connection.execute(command) unless command.strip.empty?
- end
-end
-
-make_connection(ActiveRecord::Base, 'sqlite.sql')
-make_connection(Course, 'sqlite2.sql')
-load(SCHEMA_ROOT + "/schema.rb")
diff --git a/activerecord/test/connections/native_sqlite3_mem/connection.rb b/activerecord/test/connections/native_sqlite3_mem/connection.rb
new file mode 100644
index 0000000000..14e10900d1
--- /dev/null
+++ b/activerecord/test/connections/native_sqlite3_mem/connection.rb
@@ -0,0 +1,19 @@
+# This file connects to an in-memory SQLite3 database, which is a very fast way to run the tests.
+# The downside is that disconnect from the database results in the database effectively being
+# wiped. For this reason, pooled_connections_test.rb is disabled when using an in-memory database.
+
+print "Using native SQLite3 (in memory)\n"
+require_dependency 'models/course'
+require 'logger'
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+class SqliteError < StandardError
+end
+
+def make_connection(clazz)
+ ActiveRecord::Base.configurations = { clazz.name => { :adapter => 'sqlite3', :database => ':memory:' } }
+ clazz.establish_connection(clazz.name)
+end
+
+make_connection(ActiveRecord::Base)
+make_connection(Course)
diff --git a/activerecord/test/fixtures/ships.yml b/activerecord/test/fixtures/ships.yml
index 137055aad1..df914262b3 100644
--- a/activerecord/test/fixtures/ships.yml
+++ b/activerecord/test/fixtures/ships.yml
@@ -1,5 +1,6 @@
black_pearl:
name: "Black Pearl"
+ pirate: blackbeard
interceptor:
id: 2
name: "Interceptor"
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index 88061b2145..a9aa0afced 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -15,6 +15,11 @@ class Comment < ActiveRecord::Base
def self.search_by_type(q)
self.find(:all, :conditions => ["#{QUOTED_TYPE} = ?", q])
end
+
+ def self.all_as_method
+ all
+ end
+ scope :all_as_scope, {}
end
class SpecialComment < Comment
diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb
index ee5f77b613..3e219fbe4a 100644
--- a/activerecord/test/models/company.rb
+++ b/activerecord/test/models/company.rb
@@ -38,7 +38,9 @@ end
class Firm < Company
has_many :clients, :order => "id", :dependent => :destroy, :counter_sql =>
"SELECT COUNT(*) FROM companies WHERE firm_id = 1 " +
- "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )"
+ "AND (#{QUOTED_TYPE} = 'Client' OR #{QUOTED_TYPE} = 'SpecialClient' OR #{QUOTED_TYPE} = 'VerySpecialClient' )",
+ :before_remove => :log_before_remove,
+ :after_remove => :log_after_remove
has_many :unsorted_clients, :class_name => "Client"
has_many :clients_sorted_desc, :class_name => "Client", :order => "id DESC"
has_many :clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id"
@@ -47,7 +49,6 @@ class Firm < Company
has_many :exclusively_dependent_clients_of_firm, :foreign_key => "client_of", :class_name => "Client", :order => "id", :dependent => :delete_all
has_many :limited_clients, :class_name => "Client", :limit => 1
has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id"
- has_many :clients_with_interpolated_conditions, :class_name => "Client", :conditions => 'rating > #{rating}'
has_many :clients_like_ms_with_hash_conditions, :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_many :clients_using_multiline_sql, :class_name => "Client", :finder_sql => '
@@ -88,6 +89,19 @@ class Firm < Company
has_one :unautosaved_account, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
has_many :accounts
has_many :unautosaved_accounts, :foreign_key => "firm_id", :class_name => 'Account', :autosave => false
+
+ def log
+ @log ||= []
+ end
+
+ private
+ def log_before_remove(record)
+ log << "before_remove#{record.id}"
+ end
+
+ def log_after_remove(record)
+ log << "after_remove#{record.id}"
+ end
end
class DependentFirm < Company
@@ -109,6 +123,9 @@ class Client < Company
belongs_to :firm_with_primary_key, :class_name => "Firm", :primary_key => "name", :foreign_key => "firm_name"
belongs_to :firm_with_primary_key_symbols, :class_name => "Firm", :primary_key => :name, :foreign_key => :firm_name
belongs_to :readonly_firm, :class_name => "Firm", :foreign_key => "firm_id", :readonly => true
+ belongs_to :bob_firm, :class_name => "Firm", :foreign_key => "client_of", :conditions => { :name => "Bob" }
+ has_many :accounts, :through => :firm
+ belongs_to :account
# Record destruction so we can test whether firm.clients.clear has
# is calling client.destroy, deleting from the database, or setting
diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb
index e258ccdb6c..777f6b5ba0 100644
--- a/activerecord/test/models/customer.rb
+++ b/activerecord/test/models/customer.rb
@@ -70,4 +70,4 @@ class Fullname
def to_s
"#{first} #{last.upcase}"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index f2c45053e7..b0490f754e 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -78,3 +78,7 @@ class Pirate < ActiveRecord::Base
ship_log << "#{callback}_#{record.class.name.downcase}_#{record.id || '<new>'}"
end
end
+
+class DestructivePirate < Pirate
+ has_one :dependent_ship, :class_name => 'Ship', :foreign_key => :pirate_id, :dependent => :destroy
+end
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index b51f7ff48f..1c95d30d6b 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -51,7 +51,7 @@ class Post < ActiveRecord::Base
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings do
def add_joins_and_select
- find :all, :select => 'tags.*, authors.id as author_id', :include => false,
+ find :all, :select => 'tags.*, authors.id as author_id',
:joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
end
end
diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb
index 416032cb75..8a53a8f803 100644
--- a/activerecord/test/models/project.rb
+++ b/activerecord/test/models/project.rb
@@ -28,6 +28,10 @@ class Project < ActiveRecord::Base
@developers_log = []
end
+ def self.all_as_method
+ all
+ end
+ scope :all_as_scope, {}
end
class SpecialProject < Project
diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb
index 110d540120..6adfe0ae3c 100644
--- a/activerecord/test/models/reply.rb
+++ b/activerecord/test/models/reply.rb
@@ -10,6 +10,13 @@ class Reply < Topic
attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title
end
+class UniqueReply < Reply
+ validates_uniqueness_of :content, :scope => 'parent_id'
+end
+
+class SillyUniqueReply < UniqueReply
+end
+
class WrongReply < Reply
validate :errors_on_empty_content
validate :title_is_wrong_create, :on => :create
diff --git a/activerecord/test/models/sponsor.rb b/activerecord/test/models/sponsor.rb
index 50c2c2d76c..aa4a3638fd 100644
--- a/activerecord/test/models/sponsor.rb
+++ b/activerecord/test/models/sponsor.rb
@@ -1,4 +1,7 @@
class Sponsor < ActiveRecord::Base
belongs_to :sponsor_club, :class_name => "Club", :foreign_key => "club_id"
belongs_to :sponsorable, :polymorphic => true
-end \ No newline at end of file
+ belongs_to :thing, :polymorphic => true, :foreign_type => :sponsorable_type, :foreign_key => :sponsorable_id
+ belongs_to :sponsorable_with_conditions, :polymorphic => true,
+ :foreign_type => 'sponsorable_type', :foreign_key => 'sponsorable_id', :conditions => {:name => 'Ernie'}
+end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index 6496f36f7e..6440dbe8ab 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -45,6 +45,10 @@ class Topic < ActiveRecord::Base
has_many :replies, :dependent => :destroy, :foreign_key => "parent_id"
has_many :replies_with_primary_key, :class_name => "Reply", :dependent => :destroy, :primary_key => "title", :foreign_key => "parent_title"
+
+ has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
+ has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id"
+
serialize :content
before_create :default_written_on
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 3dea7e1492..5f9bb7ee41 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -143,6 +143,7 @@ ActiveRecord::Schema.define do
t.text :body, :null => false
end
t.string :type
+ t.integer :taggings_count, :default => 0
end
create_table :companies, :force => true do |t|
@@ -153,6 +154,7 @@ ActiveRecord::Schema.define do
t.string :name
t.integer :client_of
t.integer :rating, :default => 1
+ t.integer :account_id
end
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
diff --git a/activeresource/Rakefile b/activeresource/Rakefile
index cf01bc1389..42e450da66 100755
--- a/activeresource/Rakefile
+++ b/activeresource/Rakefile
@@ -19,7 +19,7 @@ namespace :test do
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib"
Dir.glob("test/**/*_test.rb").all? do |file|
- system(ruby, '-w', "-Ilib:test:#{activesupport_path}", file)
+ sh(ruby, '-w', "-Ilib:test:#{activesupport_path}", file)
end or raise "Failures"
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 6e284182b2..96ce79e896 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -171,7 +171,11 @@ module ActiveSupport
# end
filter = <<-RUBY_EVAL
unless halted
- result = #{@filter}
+ # This double assignment is to prevent warnings in 1.9.3. I would
+ # remove the `result` variable, but apparently some other
+ # generated code is depending on this variable being set sometimes
+ # and sometimes not.
+ result = result = #{@filter}
halted = (#{chain.config[:terminator]})
end
RUBY_EVAL
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index f7f03f4d95..3720dbb8b8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -18,6 +18,10 @@ class BigDecimal
end
end
+ def to_d
+ self
+ end
+
DEFAULT_STRING_FORMAT = 'F'
def to_formatted_s(format = DEFAULT_STRING_FORMAT)
_original_to_s(format)
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 3e5d1a2a42..46e9daaa8f 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -2,49 +2,35 @@ require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
class Class #:nodoc:
- # Rubinius
- if defined?(Class.__subclasses__)
- alias :subclasses :__subclasses__
+ begin
+ ObjectSpace.each_object(Class.new) {}
def descendants
descendants = []
- __subclasses__.each do |k|
- descendants << k
- descendants.concat k.descendants
+ ObjectSpace.each_object(class << self; self; end) do |k|
+ descendants.unshift k unless k == self
end
descendants
end
- else # MRI
- begin
- ObjectSpace.each_object(Class.new) {}
-
- def descendants
- descendants = []
- ObjectSpace.each_object(class << self; self; end) do |k|
- descendants.unshift k unless k == self
- end
- descendants
- end
- rescue StandardError # JRuby
- def descendants
- descendants = []
- ObjectSpace.each_object(Class) do |k|
- descendants.unshift k if k < self
- end
- descendants.uniq!
- descendants
+ rescue StandardError # JRuby
+ def descendants
+ descendants = []
+ ObjectSpace.each_object(Class) do |k|
+ descendants.unshift k if k < self
end
+ descendants.uniq!
+ descendants
end
+ end
- # Returns an array with the direct children of +self+.
- #
- # Integer.subclasses # => [Bignum, Fixnum]
- def subclasses
- subclasses, chain = [], descendants
- chain.each do |k|
- subclasses << k unless chain.any? { |c| c > k }
- end
- subclasses
+ # Returns an array with the direct children of +self+.
+ #
+ # Integer.subclasses # => [Bignum, Fixnum]
+ def subclasses
+ subclasses, chain = [], descendants
+ chain.each do |k|
+ subclasses << k unless chain.any? { |c| c > k }
end
+ subclasses
end
end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 092f936961..06d868a3b0 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -1,5 +1,5 @@
require 'date'
-require 'active_support/inflector'
+require 'active_support/inflector/methods'
require 'active_support/core_ext/date/zones'
class Date
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 8e267c76c4..029b8c41b4 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -1,4 +1,4 @@
-require 'active_support/inflector'
+require 'active_support/inflector/methods'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/values/time_zone'
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 4e8ec5a3a8..3005fef44c 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -26,10 +26,22 @@ class Hash
#
# * If +value+ is a callable object it must expect one or two arguments. Depending
# on the arity, the callable is invoked with the +options+ hash as first argument
- # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. Its
- # return value becomes a new node.
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
+ # callable can add nodes by using <tt>options[:builder]</tt>.
+ #
+ # "foo".to_xml(lambda { |options, key| options[:builder].b(key) })
+ # # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
+ #
+ # class Foo
+ # def to_xml(options)
+ # options[:builder].bar "fooing!"
+ # end
+ # end
+ #
+ # {:foo => Foo.new}.to_xml(:skip_instruct => true)
+ # # => "<hash><bar>fooing!</bar></hash>"
#
# * Otherwise, a node with +key+ as tag is created with a string representation of
# +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index d4ae3131ec..49ac18d245 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -1,4 +1,4 @@
-require 'active_support/inflector'
+require 'active_support/inflector/methods'
require 'active_support/core_ext/time/publicize_conversion_methods'
require 'active_support/values/time_zone'
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index e3e63ce316..ce0775a690 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -9,7 +9,7 @@ module ActiveSupport
# The version the deprecated behavior will be removed, by default.
attr_accessor :deprecation_horizon
end
- self.deprecation_horizon = '3.0'
+ self.deprecation_horizon = '3.1'
# By default, warnings are not silenced and debugging is off.
self.silenced = false
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 970536a594..a65fcafb44 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -1,4 +1,4 @@
-require 'active_support/inflector'
+require 'active_support/inflector/methods'
module ActiveSupport
module Deprecation
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index c7723d139b..ced08b8783 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -2,7 +2,7 @@ module ActiveSupport
module Deprecation
class << self
attr_accessor :silenced
-
+
# Outputs a deprecation warning to the output configured by <tt>ActiveSupport::Deprecation.behavior</tt>
#
# Example:
diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb
index 4cb9d01077..2e389b5c12 100644
--- a/activesupport/lib/active_support/json/backends/yaml.rb
+++ b/activesupport/lib/active_support/json/backends/yaml.rb
@@ -7,13 +7,20 @@ module ActiveSupport
ParseError = ::StandardError
extend self
+ EXCEPTIONS = [::ArgumentError] # :nodoc:
+ begin
+ require 'psych'
+ EXCEPTIONS << Psych::SyntaxError
+ rescue LoadError
+ end
+
# Parses a JSON string or IO and converts it into an object
def decode(json)
if json.respond_to?(:read)
json = json.read
end
YAML.load(convert_json_to_yaml(json))
- rescue ArgumentError
+ rescue *EXCEPTIONS
raise ParseError, "Invalid JSON string"
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index c8cac52027..b2ea196003 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -153,6 +153,12 @@ class Object
end
end
+class Struct
+ def as_json(options = nil) #:nodoc:
+ Hash[members.zip(values)]
+ end
+end
+
class TrueClass
AS_JSON = ActiveSupport::JSON::Variable.new('true').freeze
def as_json(options = nil) AS_JSON end #:nodoc:
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 8e157d3af4..5b8c342f4f 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -20,9 +20,17 @@ module ActiveSupport
"!tag:yaml.org,2002:omap"
end
+ def encode_with(coder)
+ coder.represent_seq '!omap', map { |k,v| { k => v } }
+ end
+
def to_yaml(opts = {})
+ if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
+ return super
+ end
+
YAML.quick_emit(self, opts) do |out|
- out.seq(taguri, to_yaml_style) do |seq|
+ out.seq(taguri) do |seq|
each do |k, v|
seq.add(k => v)
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 6a7da8266c..8137f8e17e 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -138,11 +138,7 @@ module ActiveSupport
end
def to_yaml(options = {})
- if options.kind_of?(YAML::Emitter)
- utc.to_yaml(options)
- else
- time.to_yaml(options).gsub('Z', formatted_offset(true, 'Z'))
- end
+ utc.to_yaml(options)
end
def httpdate
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 7fdcb11465..16570c6aea 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -1,5 +1,6 @@
require 'libxml'
require 'active_support/core_ext/object/blank'
+require 'stringio'
# = XmlMini LibXML implementation
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
index fe2c1b9349..2536b1f33e 100644
--- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb
+++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb
@@ -1,5 +1,6 @@
require 'libxml'
require 'active_support/core_ext/object/blank'
+require 'stringio'
# = XmlMini LibXML implementation using a SAX-based parser
module ActiveSupport
@@ -82,4 +83,4 @@ module ActiveSupport
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb
index e03a744257..04ec9e8ab8 100644
--- a/activesupport/lib/active_support/xml_mini/nokogiri.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb
@@ -5,6 +5,7 @@ rescue LoadError => e
raise e
end
require 'active_support/core_ext/object/blank'
+require 'stringio'
# = XmlMini Nokogiri implementation
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
index 25afbfcd1c..93fd3dfe57 100644
--- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb
+++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb
@@ -5,6 +5,7 @@ rescue LoadError => e
raise e
end
require 'active_support/core_ext/object/blank'
+require 'stringio'
# = XmlMini Nokogiri implementation using a SAX-based parser
module ActiveSupport
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 36692af1d3..a13ad10118 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/object/blank'
+require 'stringio'
# = XmlMini ReXML implementation
module ActiveSupport
diff --git a/activesupport/test/core_ext/bigdecimal.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 9faad9146f..d592973d7a 100644
--- a/activesupport/test/core_ext/bigdecimal.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -1,10 +1,17 @@
require 'abstract_unit'
+require 'bigdecimal'
+require 'active_support/core_ext/big_decimal'
class BigDecimalTest < Test::Unit::TestCase
def test_to_yaml
assert_equal("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml)
- assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
- assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml)
+ assert_equal("--- .Inf\n", BigDecimal.new('Infinity').to_yaml)
+ assert_equal("--- .NaN\n", BigDecimal.new('NaN').to_yaml)
assert_equal("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml)
end
-end \ No newline at end of file
+
+ def test_to_d
+ bd = BigDecimal.new '10'
+ assert_equal bd, bd.to_d
+ end
+end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 74223dd7f2..a0479d45ac 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -4,6 +4,7 @@ require 'bigdecimal'
require 'active_support/core_ext/string/access'
require 'active_support/ordered_hash'
require 'active_support/core_ext/object/conversions'
+require 'active_support/inflections'
class HashExtTest < Test::Unit::TestCase
class IndifferentHash < HashWithIndifferentAccess
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 5579c27215..2b86da67fa 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -106,7 +106,7 @@ class TimeWithZoneTest < Test::Unit::TestCase
end
def test_to_yaml
- assert_equal "--- 1999-12-31 19:00:00 -05:00\n", @twz.to_yaml
+ assert_equal "--- 2000-01-01 00:00:00 Z\n", @twz.to_yaml
end
def test_ruby_to_yaml
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index e0494de6e4..d5fcbf15b7 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'abstract_unit'
+require 'active_support/core_ext/string/inflections'
require 'active_support/json'
class TestJSONEncoding < Test::Unit::TestCase
@@ -214,6 +215,36 @@ class TestJSONEncoding < Test::Unit::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ def test_struct_encoding
+ Struct.new('UserNameAndEmail', :name, :email)
+ Struct.new('UserNameAndDate', :name, :date)
+ Struct.new('Custom', :name, :sub)
+ user_email = Struct::UserNameAndEmail.new 'David', 'sample@example.com'
+ user_birthday = Struct::UserNameAndDate.new 'David', Date.new(2010, 01, 01)
+ custom = Struct::Custom.new 'David', user_birthday
+
+
+ json_strings = ""
+ json_string_and_date = ""
+ json_custom = ""
+
+ assert_nothing_raised do
+ json_strings = user_email.to_json
+ json_string_and_date = user_birthday.to_json
+ json_custom = custom.to_json
+ end
+
+ assert_equal({"name" => "David",
+ "sub" => {
+ "name" => "David",
+ "date" => "2010/01/01" }}, JSON.parse(json_custom))
+
+ assert_equal({"name" => "David", "email" => "sample@example.com"},
+ JSON.parse(json_strings))
+
+ assert_equal({"name" => "David", "date" => "2010/01/01"},
+ JSON.parse(json_string_and_date))
+ end
protected
diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb
index 72088854fc..09203465c3 100644
--- a/activesupport/test/ordered_hash_test.rb
+++ b/activesupport/test/ordered_hash_test.rb
@@ -239,14 +239,14 @@ class OrderedHashTest < Test::Unit::TestCase
def test_each_after_yaml_serialization
values = []
- @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash))
+ @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
@deserialized_ordered_hash.each {|key, value| values << value}
assert_equal @values, values
end
def test_order_after_yaml_serialization
- @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash))
+ @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
assert_equal @keys, @deserialized_ordered_hash.keys
assert_equal @values, @deserialized_ordered_hash.values
@@ -255,12 +255,34 @@ class OrderedHashTest < Test::Unit::TestCase
def test_order_after_yaml_serialization_with_nested_arrays
@ordered_hash[:array] = %w(a b c)
- @deserialized_ordered_hash = YAML::load(YAML::dump(@ordered_hash))
+ @deserialized_ordered_hash = YAML.load(YAML.dump(@ordered_hash))
assert_equal @ordered_hash.keys, @deserialized_ordered_hash.keys
assert_equal @ordered_hash.values, @deserialized_ordered_hash.values
end
+ begin
+ require 'psych'
+
+ def test_psych_serialize
+ @deserialized_ordered_hash = Psych.load(Psych.dump(@ordered_hash))
+
+ values = @deserialized_ordered_hash.map { |_, value| value }
+ assert_equal @values, values
+ end
+
+ def test_psych_serialize_tag
+ yaml = Psych.dump(@ordered_hash)
+ assert_match '!omap', yaml
+ end
+ rescue LoadError
+ end
+
+ def test_has_yaml_tag
+ @ordered_hash[:array] = %w(a b c)
+ assert_match '!omap', YAML.dump(@ordered_hash)
+ end
+
def test_update_sets_keys
@updated_ordered_hash = ActiveSupport::OrderedHash.new
@updated_ordered_hash.update(:name => "Bob")
diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/test_xml_mini.rb
index 309fa234bf..6dbcd1f40b 100644
--- a/activesupport/test/test_xml_mini.rb
+++ b/activesupport/test/test_xml_mini.rb
@@ -1,61 +1,100 @@
require 'abstract_unit'
require 'active_support/xml_mini'
+require 'active_support/builder'
-class XmlMiniTest < Test::Unit::TestCase
- def test_rename_key_dasherizes_by_default
- assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
- end
+module XmlMiniTest
+ class RenameKeyTest < Test::Unit::TestCase
+ def test_rename_key_dasherizes_by_default
+ assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key")
+ end
- def test_rename_key_does_nothing_with_dasherize_true
- assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true)
- end
+ def test_rename_key_does_nothing_with_dasherize_true
+ assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true)
+ end
- def test_rename_key_does_nothing_with_dasherize_false
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
- end
+ def test_rename_key_does_nothing_with_dasherize_false
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false)
+ end
- def test_rename_key_camelizes_with_camelize_false
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false)
- end
+ def test_rename_key_camelizes_with_camelize_false
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false)
+ end
- def test_rename_key_camelizes_with_camelize_nil
- assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil)
- end
+ def test_rename_key_camelizes_with_camelize_nil
+ assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil)
+ end
- def test_rename_key_camelizes_with_camelize_true
- assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
- end
+ def test_rename_key_camelizes_with_camelize_true
+ assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true)
+ end
- def test_rename_key_lower_camelizes_with_camelize_lower
- assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower)
- end
+ def test_rename_key_lower_camelizes_with_camelize_lower
+ assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower)
+ end
- def test_rename_key_lower_camelizes_with_camelize_upper
- assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper)
- end
+ def test_rename_key_lower_camelizes_with_camelize_upper
+ assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper)
+ end
- def test_rename_key_does_not_dasherize_leading_underscores
- assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id")
- end
+ def test_rename_key_does_not_dasherize_leading_underscores
+ assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id")
+ end
- def test_rename_key_with_leading_underscore_dasherizes_interior_underscores
- assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key")
- end
+ def test_rename_key_with_leading_underscore_dasherizes_interior_underscores
+ assert_equal "_my-key", ActiveSupport::XmlMini.rename_key("_my_key")
+ end
- def test_rename_key_does_not_dasherize_trailing_underscores
- assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_")
- end
+ def test_rename_key_does_not_dasherize_trailing_underscores
+ assert_equal "id_", ActiveSupport::XmlMini.rename_key("id_")
+ end
- def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores
- assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_")
- end
+ def test_rename_key_with_trailing_underscore_dasherizes_interior_underscores
+ assert_equal "my-key_", ActiveSupport::XmlMini.rename_key("my_key_")
+ end
- def test_rename_key_does_not_dasherize_multiple_leading_underscores
- assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id")
- end
+ def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ assert_equal "__id", ActiveSupport::XmlMini.rename_key("__id")
+ end
- def test_rename_key_does_not_dasherize_multiple_leading_underscores
- assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
+ def test_rename_key_does_not_dasherize_multiple_leading_underscores
+ assert_equal "id__", ActiveSupport::XmlMini.rename_key("id__")
+ end
end
+ class ToTagTest < ActiveSupport::TestCase
+ def assert_xml(xml)
+ assert_equal xml, @options[:builder].target!
+ end
+
+ setup do
+ @xml = ActiveSupport::XmlMini
+ @options = {:skip_instruct => true, :builder => Builder::XmlMarkup.new}
+ end
+
+ test "#to_tag accepts a callable object and passes options with the builder" do
+ @xml.to_tag(:some_tag, lambda {|o| o[:builder].br }, @options)
+ assert_xml "<br/>"
+ end
+
+ test "#to_tag accepts a callable object and passes options and tag name" do
+ @xml.to_tag(:tag, lambda {|o, t| o[:builder].b(t) }, @options)
+ assert_xml "<b>tag</b>"
+ end
+
+ test "#to_tag accepts an object responding to #to_xml and passes the options, where :root is key" do
+ obj = Object.new
+ obj.instance_eval do
+ def to_xml(options) options[:builder].yo(options[:root].to_s) end
+ end
+
+ @xml.to_tag(:tag, obj, @options)
+ assert_xml "<yo>tag</yo>"
+ end
+
+ test "#to_tag accepts arbitrary objects responding to #to_str" do
+ @xml.to_tag(:b, "Howdy", @options)
+ assert_xml "<b>Howdy</b>"
+ end
+ # TODO: test the remaining functions hidden in #to_tag.
+ end
end
diff --git a/ci/ci_build.rb b/ci/ci_build.rb
index fd884b3b2e..964e2d4eb8 100755
--- a/ci/ci_build.rb
+++ b/ci/ci_build.rb
@@ -35,7 +35,7 @@ cd "#{root_dir}/activesupport" do
puts "[CruiseControl] Building Active Support"
puts
build_results[:activesupport] = rake 'test'
- # build_results[:activesupport_isolated] = rake 'test:isolated'
+ build_results[:activesupport_isolated] = rake 'test:isolated'
end
system "sudo rm -R #{root_dir}/railties/tmp"
@@ -51,7 +51,7 @@ cd "#{root_dir}/actionpack" do
puts "[CruiseControl] Building Action Pack"
puts
build_results[:actionpack] = rake 'test'
- # build_results[:actionpack_isolated] = rake 'test:isolated'
+ build_results[:actionpack_isolated] = rake 'test:isolated'
end
cd "#{root_dir}/actionmailer" do
@@ -59,7 +59,7 @@ cd "#{root_dir}/actionmailer" do
puts "[CruiseControl] Building Action Mailer"
puts
build_results[:actionmailer] = rake 'test'
- # build_results[:actionmailer_isolated] = rake 'test:isolated'
+ build_results[:actionmailer_isolated] = rake 'test:isolated'
end
cd "#{root_dir}/activemodel" do
@@ -67,7 +67,7 @@ cd "#{root_dir}/activemodel" do
puts "[CruiseControl] Building Active Model"
puts
build_results[:activemodel] = rake 'test'
- # build_results[:activemodel_isolated] = rake 'test:isolated'
+ build_results[:activemodel_isolated] = rake 'test:isolated'
end
rm_f "#{root_dir}/activeresource/debug.log"
@@ -76,7 +76,7 @@ cd "#{root_dir}/activeresource" do
puts "[CruiseControl] Building Active Resource"
puts
build_results[:activeresource] = rake 'test'
- # build_results[:activeresource_isolated] = rake 'test:isolated'
+ build_results[:activeresource_isolated] = rake 'test:isolated'
end
rm_f "#{root_dir}/activerecord/debug.log"
@@ -85,7 +85,7 @@ cd "#{root_dir}/activerecord" do
puts "[CruiseControl] Building Active Record with MySQL"
puts
build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'mysql:test'
- # build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test'
+ build_results[:activerecord_mysql_isolated] = rake 'mysql:rebuild_databases', 'mysql:isolated_test'
end
cd "#{root_dir}/activerecord" do
@@ -93,7 +93,7 @@ cd "#{root_dir}/activerecord" do
puts "[CruiseControl] Building Active Record with MySQL2"
puts
build_results[:activerecord_mysql2] = rake 'mysql:rebuild_databases', 'mysql2:test'
- # build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test'
+ build_results[:activerecord_mysql2_isolated] = rake 'mysql:rebuild_databases', 'mysql2:isolated_test'
end
cd "#{root_dir}/activerecord" do
@@ -101,7 +101,7 @@ cd "#{root_dir}/activerecord" do
puts "[CruiseControl] Building Active Record with PostgreSQL"
puts
build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'postgresql:test'
- # build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test'
+ build_results[:activerecord_postgresql8_isolated] = rake 'postgresql:rebuild_databases', 'postgresql:isolated_test'
end
cd "#{root_dir}/activerecord" do
@@ -109,7 +109,7 @@ cd "#{root_dir}/activerecord" do
puts "[CruiseControl] Building Active Record with SQLite 3"
puts
build_results[:activerecord_sqlite3] = rake 'sqlite3:test'
- # build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test'
+ build_results[:activerecord_sqlite3_isolated] = rake 'sqlite3:isolated_test'
end
diff --git a/railties/Rakefile b/railties/Rakefile
index 1b469543cc..26fa0bf6a5 100755
--- a/railties/Rakefile
+++ b/railties/Rakefile
@@ -18,7 +18,7 @@ namespace :test do
Dir["test/#{dir}/*_test.rb"].each do |file|
next true if file.include?("fixtures")
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
- system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file)
+ sh(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file)
end
end
end
diff --git a/railties/guides/rails_guides.rb b/railties/guides/rails_guides.rb
index dfbb06cc76..feb5fe3937 100644
--- a/railties/guides/rails_guides.rb
+++ b/railties/guides/rails_guides.rb
@@ -24,14 +24,14 @@ rescue LoadError
end
begin
- gem 'RedCloth', '>= 4.1.1'
require 'redcloth'
rescue Gem::LoadError
+ # This can happen if doc:guides is executed in an application.
$stderr.puts('Generating guides requires RedCloth 4.1.1+.')
$stderr.puts(<<ERROR) if bundler?
Please add
- gem 'RedCloth', '>= 4.1.1'
+ gem 'RedCloth', '~> 4.2'
to the Gemfile, run
diff --git a/railties/guides/source/contribute.textile b/railties/guides/source/contribute.textile
index 3d4607de1d..8d19d78324 100644
--- a/railties/guides/source/contribute.textile
+++ b/railties/guides/source/contribute.textile
@@ -13,8 +13,8 @@ h3. How to Contribute?
* Assets are stored in the +railties/guides/assets+ directory.
* Sample format : "Active Record Associations":http://github.com/lifo/docrails/blob/3e56a3832415476fdd1cb963980d0ae390ac1ed3/railties/guides/source/association_basics.textile.
* Sample output : "Active Record Associations":association_basics.html.
-* You can build the Guides during testing by running +rake generate_guides+ in the +railties+ directory.
-* You're encouraged to validate XHTML for the generated guides before commiting your changes by running +rake validate_guides+ in the +railties+ directory.
+* You can build the Guides during testing by running +bundle exec rake generate_guides+ in the +railties+ directory.
+* You're encouraged to validate XHTML for the generated guides before commiting your changes by running +bundle exec rake validate_guides+ in the +railties+ directory.
* Edge guides "can be consulted online":http://edgeguides.rubyonrails.org/. That website is generated periodically from docrails.
h3. What to Contribute?
diff --git a/railties/guides/source/ruby_on_rails_guides_guidelines.textile b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
index 07f86edfb7..6576758856 100644
--- a/railties/guides/source/ruby_on_rails_guides_guidelines.textile
+++ b/railties/guides/source/ruby_on_rails_guides_guidelines.textile
@@ -45,7 +45,7 @@ h3. HTML Generation
To generate all the guides just cd into the +railties+ directory and execute
<plain>
-rake generate_guides
+bundle exec rake generate_guides
</plain>
You'll need the gems erubis, i18n, and RedCloth.
@@ -53,7 +53,7 @@ You'll need the gems erubis, i18n, and RedCloth.
To process +my_guide.textile+ and nothing else use the +ONLY+ environment variable:
<plain>
-rake generate_guides ONLY=my_guide
+bundle exec rake generate_guides ONLY=my_guide
</plain>
Although by default guides that have not been modified are not processed, so +ONLY+ is rarely needed in practice.
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 8cd496781b..c74bcbedf2 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -6,25 +6,28 @@ module Rails
class Configuration < ::Rails::Engine::Configuration
attr_accessor :allow_concurrency, :asset_host, :cache_classes, :cache_store,
:encoding, :consider_all_requests_local, :dependency_loading,
- :filter_parameters, :helpers_paths, :log_level, :logger,
+ :filter_parameters, :helpers_paths, :logger,
:preload_frameworks, :reload_plugins,
:secret_token, :serve_static_assets, :session_options,
:time_zone, :whiny_nils
+ attr_writer :log_level
+
def initialize(*)
super
self.encoding = "utf-8"
- @allow_concurrency = false
+ @allow_concurrency = false
@consider_all_requests_local = false
- @filter_parameters = []
- @helpers_paths = []
- @dependency_loading = true
- @serve_static_assets = true
- @session_store = :cookie_store
- @session_options = {}
- @time_zone = "UTC"
- @middleware = app_middleware
- @generators = app_generators
+ @filter_parameters = []
+ @helpers_paths = []
+ @dependency_loading = true
+ @serve_static_assets = true
+ @session_store = :cookie_store
+ @session_options = {}
+ @time_zone = "UTC"
+ @log_level = nil
+ @middleware = app_middleware
+ @generators = app_generators
end
def compiled_asset_path
diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb
index 66c4088a68..29e693dfb0 100644
--- a/railties/lib/rails/generators.rb
+++ b/railties/lib/rails/generators.rb
@@ -155,7 +155,7 @@ module Rails
# commands.
def self.invoke(namespace, args=ARGV, config={})
names = namespace.to_s.split(':')
- if klass = find_by_namespace(names.pop, names.shift)
+ if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
args << "--help" if args.empty? && klass.arguments.any? { |a| a.required? }
klass.start(args, config)
else
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 7766050632..ab7ed4eb9e 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -112,12 +112,7 @@ module Rails
end
def database_gemfile_entry
- entry = ""
- unless options[:skip_active_record]
- entry = "gem '#{gem_for_database}'"
- entry << ", :require => '#{require_for_database}'" if require_for_database
- end
- entry
+ options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
end
def rails_gemfile_entry
@@ -150,19 +145,12 @@ gem 'rails', '#{Rails::VERSION::STRING}'
case options[:database]
when "oracle" then "ruby-oci8"
when "postgresql" then "pg"
- when "sqlite3" then "sqlite3-ruby"
when "frontbase" then "ruby-frontbase"
when "mysql" then "mysql2"
else options[:database]
end
end
- def require_for_database
- case options[:database]
- when "sqlite3" then "sqlite3"
- end
- end
-
def bundle_if_dev_or_edge
bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
run "#{bundle_command} install" if dev_or_edge?
diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb
index f97f3db588..131eb6ff6f 100644
--- a/railties/lib/rails/generators/base.rb
+++ b/railties/lib/rails/generators/base.rb
@@ -274,7 +274,7 @@ module Rails
# Use Rails default banner.
#
def self.banner
- "rails generate #{generator_name} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]"
+ "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ')
end
# Sets the base_name taking into account the current class namespace.
diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile
index 86b9e8f40c..7d5a865b80 100644
--- a/railties/lib/rails/generators/rails/app/templates/Gemfile
+++ b/railties/lib/rails/generators/rails/app/templates/Gemfile
@@ -17,7 +17,7 @@ source 'http://rubygems.org'
# Bundle the extra gems:
# gem 'bj'
# gem 'nokogiri'
-# gem 'sqlite3-ruby', :require => 'sqlite3'
+# gem 'sqlite3'
# gem 'aws-s3', :require => 'aws/s3'
# Bundle gems for the local environment. Make sure to
@@ -26,3 +26,6 @@ source 'http://rubygems.org'
# group :development, :test do
# gem 'webrat'
# end
+
+# Needed for guides generation
+# gem "RedCloth", "~> 4.2"
diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
index 025d62a8d8..90d87cc295 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml
@@ -1,5 +1,5 @@
# SQLite version 3.x
-# gem install sqlite3-ruby (not necessary on OS X Leopard)
+# gem install sqlite3
development:
adapter: sqlite3
database: db/development.sqlite3
diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
index 9461589ff5..8b1aed974f 100644
--- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -16,7 +16,7 @@ module Rails
end
def readme
- copy_file "README.rdoc"
+ template "README.rdoc"
end
def gemfile
@@ -44,7 +44,7 @@ module Rails
end
def config
- template "config/routes.rb" if mountable?
+ template "config/routes.rb" if full?
end
def test
diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb
index 42ddf380d8..8e158d5831 100644
--- a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb
+++ b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb
@@ -1,3 +1,6 @@
+<% if mountable? -%>
<%= camelized %>::Engine.routes.draw do
-
+<% else -%>
+Rails.application.routes.draw do
+<% end -%>
end
diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb
index 8570fc7b3f..d15887f561 100644
--- a/railties/lib/rails/paths.rb
+++ b/railties/lib/rails/paths.rb
@@ -134,10 +134,10 @@ module Rails
@root = root
@glob = options[:glob]
- autoload_once! if options[:autoload_once]
- eager_load! if options[:eager_load]
- autoload! if options[:autoload]
- load_path! if options[:load_path]
+ options[:autoload_once] ? autoload_once! : skip_autoload_once!
+ options[:eager_load] ? eager_load! : skip_eager_load!
+ options[:autoload] ? autoload! : skip_autoload!
+ options[:load_path] ? load_path! : skip_load_path!
end
def children
diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb
index b3dc1f894c..32acc66f10 100644
--- a/railties/lib/rails/rack/logger.rb
+++ b/railties/lib/rails/rack/logger.rb
@@ -21,7 +21,7 @@ module Rails
request = ActionDispatch::Request.new(env)
path = request.fullpath
- info "\n\nStarted #{env["REQUEST_METHOD"]} \"#{path}\" " \
+ info "\n\nStarted #{request.request_method} \"#{path}\" " \
"for #{request.ip} at #{Time.now.to_default_s}"
end
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 23cd2378c7..822a6bf032 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -63,5 +63,24 @@ module ApplicationTests
RUBY
assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` }
end
+
+ def test_model_and_migration_generator_with_change_syntax
+ Dir.chdir(app_path) do
+ `rails generate model user username:string password:string`
+ `rails generate migration add_email_to_users email:string`
+ end
+
+ output = Dir.chdir(app_path){ `rake db:migrate` }
+ assert_match /create_table\(:users\)/, output
+ assert_match /CreateUsers: migrated/, output
+ assert_match /add_column\(:users, :email, :string\)/, output
+ assert_match /AddEmailToUsers: migrated/, output
+
+ output = Dir.chdir(app_path){ `rake db:rollback STEP=2` }
+ assert_match /drop_table\("users"\)/, output
+ assert_match /CreateUsers: reverted/, output
+ assert_match /remove_column\("users", :email\)/, output
+ assert_match /AddEmailToUsers: reverted/, output
+ end
end
end
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 0fe5cdc4a8..28b20d4206 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -106,7 +106,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_config_database_is_added_by_default
run_generator
assert_file "config/database.yml", /sqlite3/
- assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/
+ assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
end
def test_config_another_database
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index 288ec30460..6eecfc8e2e 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -34,15 +34,10 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
run_generator [migration, "title:string", "body:text"]
assert_migration "db/migrate/#{migration}.rb" do |content|
- assert_method :up, content do |up|
+ assert_method :change, content do |up|
assert_match /add_column :posts, :title, :string/, up
assert_match /add_column :posts, :body, :text/, up
end
-
- assert_method :down, content do |down|
- assert_match /remove_column :posts, :title/, down
- assert_match /remove_column :posts, :body/, down
- end
end
end
diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb
index 552b7eb30a..b86859666e 100644
--- a/railties/test/generators/model_generator_test.rb
+++ b/railties/test/generators/model_generator_test.rb
@@ -99,15 +99,11 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["product", "name:string", "supplier_id:integer"]
assert_migration "db/migrate/create_products.rb" do |m|
- assert_method :up, m do |up|
+ assert_method :change, m do |up|
assert_match /create_table :products/, up
assert_match /t\.string :name/, up
assert_match /t\.integer :supplier_id/, up
end
-
- assert_method :down, m do |down|
- assert_match /drop_table :products/, down
- end
end
end
@@ -141,7 +137,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase
run_generator ["account", "--no-timestamps"]
assert_migration "db/migrate/create_accounts.rb" do |m|
- assert_method :up, m do |up|
+ assert_method :change, m do |up|
assert_no_match /t.timestamps/, up
end
end
diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb
index 2a9e8046b8..2a8b337144 100644
--- a/railties/test/generators/plugin_new_generator_test.rb
+++ b/railties/test/generators/plugin_new_generator_test.rb
@@ -38,8 +38,10 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
assert_file "things-43/lib/things-43.rb", /module Things43/
end
- def test_generating_test_files
+ def test_generating_without_options
run_generator
+ assert_file "README.rdoc", /Bukkits/
+ assert_no_file "config/routes.rb"
assert_file "test/test_helper.rb"
assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/
end
@@ -66,7 +68,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_database_entry_is_assed_by_default_in_full_mode
run_generator([destination_root, "--full"])
assert_file "test/dummy/config/database.yml", /sqlite/
- assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/
+ assert_file "Gemfile", /^gem\s+["']sqlite3["']$/
end
def test_config_another_database
@@ -151,6 +153,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase
def test_creating_engine_in_full_mode
run_generator [destination_root, "--full"]
+ assert_file "config/routes.rb", /Rails.application.routes.draw do/
assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/
assert_file "lib/bukkits.rb", /require "bukkits\/engine"/
end
diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb
index 346c9ceb9d..99c9d790eb 100644
--- a/railties/test/generators_test.rb
+++ b/railties/test/generators_test.rb
@@ -94,6 +94,14 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match /Rails 2\.x generator/, output
end
+ def test_invoke_with_nested_namespaces
+ model_generator = mock('ModelGenerator') do
+ expects(:start).with(["Account"], {})
+ end
+ Rails::Generators.expects(:find_by_namespace).with('namespace', 'my:awesome').returns(model_generator)
+ Rails::Generators.invoke 'my:awesome:namespace', ["Account"]
+ end
+
def test_rails_generators_help_with_builtin_information
output = capture(:stdout){ Rails::Generators.help }
assert_match /Rails:/, output
@@ -120,6 +128,16 @@ class GeneratorsTest < Rails::Generators::TestCase
assert_match /^ active_record:fixjour$/, output
end
+ def test_default_banner_should_show_generator_namespace
+ klass = Rails::Generators.find_by_namespace(:foobar)
+ assert_match /^rails generate foobar:foobar/, klass.banner
+ end
+
+ def test_default_banner_should_not_show_rails_generator_namespace
+ klass = Rails::Generators.find_by_namespace(:model)
+ assert_match /^rails generate model/, klass.banner
+ end
+
def test_no_color_sets_proper_shell
Rails::Generators.no_color!
assert_equal Thor::Shell::Basic, Thor::Base.shell
diff --git a/railties/test/railties/shared_tests.rb b/railties/test/railties/shared_tests.rb
index 31ba2eaca2..3eb79d57c8 100644
--- a/railties/test/railties/shared_tests.rb
+++ b/railties/test/railties/shared_tests.rb
@@ -294,7 +294,7 @@ YAML
boot_rails
- assert_equal %W(
+ expected_locales = %W(
#{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml
#{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml
#{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml
@@ -302,7 +302,13 @@ YAML
#{@plugin.path}/config/locales/en.yml
#{app_path}/config/locales/en.yml
#{app_path}/app/locales/en.yml
- ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) }
+ ).map { |path| File.expand_path(path) }
+
+ actual_locales = I18n.load_path.map { |path|
+ File.expand_path(path)
+ } & expected_locales # remove locales external to Rails
+
+ assert_equal expected_locales, actual_locales
assert_equal "2", I18n.t(:foo)
assert_equal "1", I18n.t(:bar)