\r\r"
+ end
+
+ def nested_multipart(recipient)
+ recipients recipient
+ subject "nested multipart"
+ from "test@example.com"
+ content_type "multipart/mixed"
+ part :content_type => "multipart/alternative", :content_disposition => "inline", :headers => { "foo" => "bar" } do |p|
+ p.part :content_type => "text/plain", :body => "test text\nline #2"
+ p.part :content_type => "text/html", :body => "test HTML \nline #2"
+ end
+ attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
+ end
+
+ def nested_multipart_with_body(recipient)
+ recipients recipient
+ subject "nested multipart with body"
+ from "test@example.com"
+ content_type "multipart/mixed"
+ part :content_type => "multipart/alternative", :content_disposition => "inline", :body => "Nothing to see here." do |p|
+ p.part :content_type => "text/html", :body => "test HTML "
+ end
+ end
+
+ def attachment_with_custom_header(recipient)
+ recipients recipient
+ subject "custom header in attachment"
+ from "test@example.com"
+ content_type "multipart/related"
+ part :content_type => "text/html", :body => 'yo'
+ attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '' }
+ end
+
+ def unnamed_attachment(recipient)
+ recipients recipient
+ subject "nested multipart"
+ from "test@example.com"
+ content_type "multipart/mixed"
+ part :content_type => "text/plain", :body => "hullo"
+ attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
+ end
+
+ def headers_with_nonalpha_chars(recipient)
+ recipients recipient
+ subject "nonalpha chars"
+ from "One: Two "
+ cc "Three: Four "
+ bcc "Five: Six "
+ body "testing"
+ end
+
+ def custom_content_type_attributes
+ recipients "no.one@nowhere.test"
+ subject "custom content types"
+ from "some.one@somewhere.test"
+ content_type "text/plain; format=flowed"
+ body "testing"
+ end
+
+ def return_path
+ recipients "no.one@nowhere.test"
+ subject "return path test"
+ from "some.one@somewhere.test"
+ body "testing"
+ headers "return-path" => "another@somewhere.test"
+ end
+
+ class < charset }
+ end
+ mail
+ end
+
+ # Replacing logger work around for mocha bug. Should be fixed in mocha 0.3.3
+ def setup
+ set_delivery_method :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.raise_delivery_errors = true
+ ActionMailer::Base.deliveries = []
+
+ @original_logger = TestMailer.logger
+ @recipient = 'test@localhost'
+ end
+
+ def teardown
+ TestMailer.logger = @original_logger
+ restore_delivery_method
+ end
+
+ def test_nested_parts
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
+ assert_equal 2,created.parts.size
+ assert_equal 2,created.parts.first.parts.size
+
+ assert_equal "multipart/mixed", created.content_type
+ assert_equal "multipart/alternative", created.parts.first.content_type
+ assert_equal "bar", created.parts.first.header['foo'].to_s
+ assert_equal "text/plain", created.parts.first.parts.first.content_type
+ assert_equal "text/html", created.parts.first.parts[1].content_type
+ assert_equal "application/octet-stream", created.parts[1].content_type
+ end
+
+ def test_nested_parts_with_body
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_nested_multipart_with_body(@recipient)}
+ assert_equal 1,created.parts.size
+ assert_equal 2,created.parts.first.parts.size
+
+ assert_equal "multipart/mixed", created.content_type
+ assert_equal "multipart/alternative", created.parts.first.content_type
+ assert_equal "Nothing to see here.", created.parts.first.parts.first.body
+ assert_equal "text/plain", created.parts.first.parts.first.content_type
+ assert_equal "text/html", created.parts.first.parts[1].content_type
+ end
+
+ def test_attachment_with_custom_header
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
+ assert_equal "", created.parts[1].header['content-id'].to_s
+ end
+
+ def test_signed_up
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Signed up] Welcome #{@recipient}"
+ expected.body = "Hello there, \n\nMr. #{@recipient}"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_custom_template
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Signed up] Welcome #{@recipient}"
+ expected.body = "Hello there, \n\nMr. #{@recipient}"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+ end
+
+ def test_custom_templating_extension
+ #
+ # N.b., custom_templating_extension.text.plain.haml is expected to be in fixtures/test_mailer directory
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Signed up] Welcome #{@recipient}"
+ expected.body = "Hello there, \n\nMr. #{@recipient}"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ # Stub the render method so no alternative renderers need be present.
+ ActionView::Base.any_instance.stubs(:render).returns("Hello there, \n\nMr. #{@recipient}")
+
+ # If the template is not registered, there should be no parts.
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
+ assert_not_nil created
+ assert_equal 0, created.parts.length
+
+ ActionMailer::Base.register_template_extension('haml')
+
+ # Now that the template is registered, there should be one part. The text/plain part.
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) }
+ assert_not_nil created
+ assert_equal 2, created.parts.length
+ assert_equal 'text/plain', created.parts[0].content_type
+ assert_equal 'text/html', created.parts[1].content_type
+ end
+
+ def test_cancelled_account
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Cancelled] Goodbye #{@recipient}"
+ expected.body = "Goodbye, Mr. #{@recipient}"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_cc_bcc
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "testing bcc/cc"
+ expected.body = "Nothing to see here."
+ expected.from = "system@loudthinking.com"
+ expected.cc = "nobody@loudthinking.com"
+ expected.bcc = "root@loudthinking.com"
+ expected.date = Time.local 2004, 12, 12
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_cc_bcc @recipient
+ end
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_cc_bcc @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_reply_to
+ expected = new_mail
+
+ expected.to = @recipient
+ expected.subject = "testing reply_to"
+ expected.body = "Nothing to see here."
+ expected.from = "system@loudthinking.com"
+ expected.reply_to = "atraver@gmail.com"
+ expected.date = Time.local 2008, 5, 23
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_different_reply_to @recipient
+ end
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_different_reply_to @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_iso_charset
+ expected = new_mail( "iso-8859-1" )
+ expected.to = @recipient
+ expected.subject = encode "testing isø charsets", "iso-8859-1"
+ expected.body = "Nothing to see here."
+ expected.from = "system@loudthinking.com"
+ expected.cc = "nobody@loudthinking.com"
+ expected.bcc = "root@loudthinking.com"
+ expected.date = Time.local 2004, 12, 12
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_iso_charset @recipient
+ end
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_iso_charset @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_unencoded_subject
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "testing unencoded subject"
+ expected.body = "Nothing to see here."
+ expected.from = "system@loudthinking.com"
+ expected.cc = "nobody@loudthinking.com"
+ expected.bcc = "root@loudthinking.com"
+ expected.date = Time.local 2004, 12, 12
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_unencoded_subject @recipient
+ end
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_unencoded_subject @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_instances_are_nil
+ assert_nil ActionMailer::Base.new
+ assert_nil TestMailer.new
+ end
+
+ def test_deliveries_array
+ assert_not_nil ActionMailer::Base.deliveries
+ assert_equal 0, ActionMailer::Base.deliveries.size
+ TestMailer.deliver_signed_up(@recipient)
+ assert_equal 1, ActionMailer::Base.deliveries.size
+ assert_not_nil ActionMailer::Base.deliveries.first
+ end
+
+ def test_perform_deliveries_flag
+ ActionMailer::Base.perform_deliveries = false
+ TestMailer.deliver_signed_up(@recipient)
+ assert_equal 0, ActionMailer::Base.deliveries.size
+ ActionMailer::Base.perform_deliveries = true
+ TestMailer.deliver_signed_up(@recipient)
+ assert_equal 1, ActionMailer::Base.deliveries.size
+ end
+
+ def test_doesnt_raise_errors_when_raise_delivery_errors_is_false
+ ActionMailer::Base.raise_delivery_errors = false
+ TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception)
+ assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
+ end
+
+ def test_performs_delivery_via_sendmail
+ sm = mock()
+ sm.expects(:print).with(anything)
+ sm.expects(:flush)
+ IO.expects(:popen).once.with('/usr/sbin/sendmail -i -t', 'w+').yields(sm)
+ ActionMailer::Base.delivery_method = :sendmail
+ TestMailer.deliver_signed_up(@recipient)
+ end
+
+ def test_delivery_logs_sent_mail
+ mail = TestMailer.create_signed_up(@recipient)
+ logger = mock()
+ logger.expects(:info).with("Sent mail to #{@recipient}")
+ logger.expects(:debug).with("\n#{mail.encoded}")
+ TestMailer.logger = logger
+ TestMailer.deliver_signed_up(@recipient)
+ end
+
+ def test_unquote_quoted_printable_subject
+ msg = <"
+
+ expected = new_mail "iso-8859-1"
+ expected.to = quote_address_if_necessary @recipient, "iso-8859-1"
+ expected.subject = "testing extended headers"
+ expected.body = "Nothing to see here."
+ expected.from = quote_address_if_necessary "Grytøyr ", "iso-8859-1"
+ expected.cc = quote_address_if_necessary "Grytøyr ", "iso-8859-1"
+ expected.bcc = quote_address_if_necessary "Grytøyr ", "iso-8859-1"
+ expected.date = Time.local 2004, 12, 12
+
+ created = nil
+ assert_nothing_raised do
+ created = TestMailer.create_extended_headers @recipient
+ end
+
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised do
+ TestMailer.deliver_extended_headers @recipient
+ end
+
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+
+ def test_utf8_body_is_not_quoted
+ @recipient = "Foo áëô îü "
+ expected = new_mail "utf-8"
+ expected.to = quote_address_if_necessary @recipient, "utf-8"
+ expected.subject = "testing utf-8 body"
+ expected.body = "åœö blah"
+ expected.from = quote_address_if_necessary @recipient, "utf-8"
+ expected.cc = quote_address_if_necessary @recipient, "utf-8"
+ expected.bcc = quote_address_if_necessary @recipient, "utf-8"
+ expected.date = Time.local 2004, 12, 12
+
+ created = TestMailer.create_utf8_body @recipient
+ assert_match(/åœö blah/, created.encoded)
+ end
+
+ def test_multiple_utf8_recipients
+ @recipient = ["\"Foo áëô îü\" ", "\"Example Recipient\" "]
+ expected = new_mail "utf-8"
+ expected.to = quote_address_if_necessary @recipient, "utf-8"
+ expected.subject = "testing utf-8 body"
+ expected.body = "åœö blah"
+ expected.from = quote_address_if_necessary @recipient.first, "utf-8"
+ expected.cc = quote_address_if_necessary @recipient, "utf-8"
+ expected.bcc = quote_address_if_necessary @recipient, "utf-8"
+ expected.date = Time.local 2004, 12, 12
+
+ created = TestMailer.create_utf8_body @recipient
+ assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= \r/, created.encoded)
+ assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= , Example Recipient _Google}, mail.body
+ end
+
+ def test_various_newlines
+ mail = TestMailer.create_various_newlines(@recipient)
+ assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
+ "line #5\n\nline#6\n\nline #7", mail.body)
+ end
+
+ def test_various_newlines_multipart
+ mail = TestMailer.create_various_newlines_multipart(@recipient)
+ assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
+ assert_equal "
line #1
\n
line #2
\n
line #3
\n
line #4
\n\n", mail.parts[1].body
+ end
+
+ def test_headers_removed_on_smtp_delivery
+ ActionMailer::Base.delivery_method = :smtp
+ TestMailer.deliver_cc_bcc(@recipient)
+ assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
+ assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
+ assert MockSMTP.deliveries[0][2].include?(@recipient)
+ assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
+ assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
+ assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
+ end
+
+ def test_recursive_multipart_processing
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
+ mail = TMail::Mail.parse(fixture)
+ assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
+ end
+
+ def test_decode_encoded_attachment_filename
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
+ mail = TMail::Mail.parse(fixture)
+ attachment = mail.attachments.last
+
+ expected = "01 Quien Te Dij\212at. Pitbull.mp3"
+ expected.force_encoding(Encoding::ASCII_8BIT) if expected.respond_to?(:force_encoding)
+
+ assert_equal expected, attachment.original_filename
+ end
+
+ def test_wrong_mail_header
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
+ assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
+ end
+
+ def test_decode_message_with_unknown_charset
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
+ mail = TMail::Mail.parse(fixture)
+ assert_nothing_raised { mail.body }
+ end
+
+ def test_empty_header_values_omitted
+ result = TestMailer.create_unnamed_attachment(@recipient).encoded
+ assert_match %r{Content-Type: application/octet-stream[^;]}, result
+ assert_match %r{Content-Disposition: attachment[^;]}, result
+ end
+
+ def test_headers_with_nonalpha_chars
+ mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
+ assert !mail.from_addrs.empty?
+ assert !mail.cc_addrs.empty?
+ assert !mail.bcc_addrs.empty?
+ assert_match(/:/, mail.from_addrs.to_s)
+ assert_match(/:/, mail.cc_addrs.to_s)
+ assert_match(/:/, mail.bcc_addrs.to_s)
+ end
+
+ def test_deliver_with_mail_object
+ mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
+ assert_nothing_raised { TestMailer.deliver(mail) }
+ assert_equal 1, TestMailer.deliveries.length
+ end
+
+ def test_multipart_with_template_path_with_dots
+ mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
+ assert_equal 2, mail.parts.length
+ end
+
+ def test_custom_content_type_attributes
+ mail = TestMailer.create_custom_content_type_attributes
+ assert_match %r{format=flowed}, mail['content-type'].to_s
+ assert_match %r{charset=utf-8}, mail['content-type'].to_s
+ end
+
+ def test_return_path_with_create
+ mail = TestMailer.create_return_path
+ assert_equal "", mail['return-path'].to_s
+ end
+
+ def test_return_path_with_deliver
+ ActionMailer::Base.delivery_method = :smtp
+ TestMailer.deliver_return_path
+ assert_match %r{^Return-Path: }, MockSMTP.deliveries[0][0]
+ end
+end
+
+end # uses_mocha
+
+class InheritableTemplateRootTest < Test::Unit::TestCase
+ def test_attr
+ expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
+ assert_equal expected, FunkyPathMailer.template_root
+
+ sub = Class.new(FunkyPathMailer)
+ sub.template_root = 'test/path'
+
+ assert_equal 'test/path', sub.template_root
+ assert_equal expected, FunkyPathMailer.template_root
+ end
+end
+
+class MethodNamingTest < Test::Unit::TestCase
+ class TestMailer < ActionMailer::Base
+ def send
+ body 'foo'
+ end
+ end
+
+ def setup
+ set_delivery_method :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+ end
+
+ def teardown
+ restore_delivery_method
+ end
+
+ def test_send_method
+ assert_nothing_raised do
+ assert_emails 1 do
+ TestMailer.deliver_send
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionmailer/test/quoting_test.rb b/vendor/rails/actionmailer/test/quoting_test.rb
new file mode 100644
index 0000000..13a859a
--- /dev/null
+++ b/vendor/rails/actionmailer/test/quoting_test.rb
@@ -0,0 +1,98 @@
+# encoding: utf-8
+require 'abstract_unit'
+require 'tmail'
+require 'tempfile'
+
+class QuotingTest < Test::Unit::TestCase
+ # Move some tests from TMAIL here
+ def test_unquote_quoted_printable
+ a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
+ assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
+ end
+
+ def test_unquote_base64
+ a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
+ assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
+ end
+
+ def test_unquote_without_charset
+ a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
+ assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
+ end
+
+ def test_unqoute_multiple
+ a ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?="
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
+ assert_equal "Re: [12] #137: Inkonsistente verwendung von \"Hinzuf\303\274gen\"", b
+ end
+
+ def test_unqoute_in_the_middle
+ a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?="
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
+ assert_equal "Re: Photos Brosch\303\274re Rand", b
+ end
+
+ def test_unqoute_iso
+ a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?="
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1')
+ expected = "Brosch\374re Rand"
+ expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding)
+ assert_equal expected, b
+ end
+
+ def test_quote_multibyte_chars
+ original = "\303\246 \303\270 and \303\245"
+ original.force_encoding('ASCII-8BIT') if original.respond_to?(:force_encoding)
+
+ result = execute_in_sandbox(<<-CODE)
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
+ $KCODE = 'u'
+ require 'jcode'
+ require 'action_mailer/quoting'
+ include ActionMailer::Quoting
+ quoted_printable(#{original.inspect}, "UTF-8")
+ CODE
+
+ unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
+ assert_equal unquoted, original
+ end
+
+
+ # test an email that has been created using \r\n newlines, instead of
+ # \n newlines.
+ def test_email_quoted_with_0d0a
+ mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a"))
+ assert_match %r{Elapsed time}, mail.body
+ end
+
+ def test_email_with_partially_quoted_subject
+ mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
+ assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject
+ end
+
+ private
+ # This whole thing *could* be much simpler, but I don't think Tempfile,
+ # popen and others exist on all platforms (like Windows).
+ def execute_in_sandbox(code)
+ test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
+ res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
+
+ File.open(test_name, "w+") do |file|
+ file.write(<<-CODE)
+ block = Proc.new do
+ #{code}
+ end
+ puts block.call
+ CODE
+ end
+
+ system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
+ File.read(res_name).chomp
+ ensure
+ File.delete(test_name) rescue nil
+ File.delete(res_name) rescue nil
+ end
+end
diff --git a/vendor/rails/actionmailer/test/test_helper_test.rb b/vendor/rails/actionmailer/test/test_helper_test.rb
new file mode 100644
index 0000000..f8913e5
--- /dev/null
+++ b/vendor/rails/actionmailer/test/test_helper_test.rb
@@ -0,0 +1,129 @@
+require 'abstract_unit'
+
+class TestHelperMailer < ActionMailer::Base
+ def test
+ recipients "test@example.com"
+ from "tester@example.com"
+ body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
+ end
+end
+
+class TestHelperMailerTest < ActionMailer::TestCase
+ def test_setup_sets_right_action_mailer_options
+ assert_equal :test, ActionMailer::Base.delivery_method
+ assert ActionMailer::Base.perform_deliveries
+ assert_equal [], ActionMailer::Base.deliveries
+ end
+
+ def test_setup_creates_the_expected_mailer
+ assert @expected.is_a?(TMail::Mail)
+ assert_equal "1.0", @expected.mime_version
+ assert_equal "text/plain", @expected.content_type
+ end
+
+ def test_mailer_class_is_correctly_inferred
+ assert_equal TestHelperMailer, self.class.mailer_class
+ end
+
+ def test_determine_default_mailer_raises_correct_error
+ assert_raises(ActionMailer::NonInferrableMailerError) do
+ self.class.determine_default_mailer("NotAMailerTest")
+ end
+ end
+
+ def test_charset_is_utf_8
+ assert_equal "utf-8", charset
+ end
+
+ def test_encode
+ assert_equal "=?utf-8?Q?=0aasdf=0a?=", encode("\nasdf\n")
+ end
+
+ def test_assert_emails
+ assert_nothing_raised do
+ assert_emails 1 do
+ TestHelperMailer.deliver_test
+ end
+ end
+ end
+
+ def test_repeated_assert_emails_calls
+ assert_nothing_raised do
+ assert_emails 1 do
+ TestHelperMailer.deliver_test
+ end
+ end
+
+ assert_nothing_raised do
+ assert_emails 2 do
+ TestHelperMailer.deliver_test
+ TestHelperMailer.deliver_test
+ end
+ end
+ end
+
+ def test_assert_emails_with_no_block
+ assert_nothing_raised do
+ TestHelperMailer.deliver_test
+ assert_emails 1
+ end
+
+ assert_nothing_raised do
+ TestHelperMailer.deliver_test
+ TestHelperMailer.deliver_test
+ assert_emails 3
+ end
+ end
+
+ def test_assert_no_emails
+ assert_nothing_raised do
+ assert_no_emails do
+ TestHelperMailer.create_test
+ end
+ end
+ end
+
+ def test_assert_emails_too_few_sent
+ error = assert_raises Test::Unit::AssertionFailedError do
+ assert_emails 2 do
+ TestHelperMailer.deliver_test
+ end
+ end
+
+ assert_match /2 .* but 1/, error.message
+ end
+
+ def test_assert_emails_too_many_sent
+ error = assert_raises Test::Unit::AssertionFailedError do
+ assert_emails 1 do
+ TestHelperMailer.deliver_test
+ TestHelperMailer.deliver_test
+ end
+ end
+
+ assert_match /1 .* but 2/, error.message
+ end
+
+ def test_assert_no_emails_failure
+ error = assert_raises Test::Unit::AssertionFailedError do
+ assert_no_emails do
+ TestHelperMailer.deliver_test
+ end
+ end
+
+ assert_match /0 .* but 1/, error.message
+ end
+end
+
+class AnotherTestHelperMailerTest < ActionMailer::TestCase
+ tests TestHelperMailer
+
+ def setup
+ @test_var = "a value"
+ end
+
+ def test_setup_shouldnt_conflict_with_mailer_setup
+ assert @expected.is_a?(TMail::Mail)
+ assert_equal 'a value', @test_var
+ end
+end
diff --git a/vendor/rails/actionmailer/test/tmail_test.rb b/vendor/rails/actionmailer/test/tmail_test.rb
new file mode 100644
index 0000000..718990e
--- /dev/null
+++ b/vendor/rails/actionmailer/test/tmail_test.rb
@@ -0,0 +1,22 @@
+require 'abstract_unit'
+
+class TMailMailTest < Test::Unit::TestCase
+ def test_body
+ m = TMail::Mail.new
+ expected = 'something_with_underscores'
+ m.encoding = 'quoted-printable'
+ quoted_body = [expected].pack('*M')
+ m.body = quoted_body
+ assert_equal "something_with_underscores=\n", m.quoted_body
+ assert_equal expected, m.body
+ end
+
+ def test_nested_attachments_are_recognized_correctly
+ fixture = File.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_nested_attachment")
+ mail = TMail::Mail.parse(fixture)
+ assert_equal 2, mail.attachments.length
+ assert_equal "image/png", mail.attachments.first.content_type
+ assert_equal 1902, mail.attachments.first.length
+ assert_equal "application/pkcs7-signature", mail.attachments.last.content_type
+ end
+end
diff --git a/vendor/rails/actionmailer/test/url_test.rb b/vendor/rails/actionmailer/test/url_test.rb
new file mode 100644
index 0000000..71286bd
--- /dev/null
+++ b/vendor/rails/actionmailer/test/url_test.rb
@@ -0,0 +1,76 @@
+require 'abstract_unit'
+
+class TestMailer < ActionMailer::Base
+
+ default_url_options[:host] = 'www.basecamphq.com'
+
+ def signed_up_with_url(recipient)
+ @recipients = recipient
+ @subject = "[Signed up] Welcome #{recipient}"
+ @from = "system@loudthinking.com"
+ @sent_on = Time.local(2004, 12, 12)
+
+ @body["recipient"] = recipient
+ @body["welcome_url"] = url_for :host => "example.com", :controller => "welcome", :action => "greeting"
+ end
+
+ class < charset }
+ end
+ mail
+ end
+
+ def setup
+ set_delivery_method :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+
+ @recipient = 'test@localhost'
+ end
+
+ def teardown
+ restore_delivery_method
+ end
+
+ def test_signed_up_with_url
+ ActionController::Routing::Routes.draw do |map|
+ map.connect ':controller/:action/:id'
+ map.welcome 'welcome', :controller=>"foo", :action=>"bar"
+ end
+
+ expected = new_mail
+ expected.to = @recipient
+ expected.subject = "[Signed up] Welcome #{@recipient}"
+ expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n"
+ expected.from = "system@loudthinking.com"
+ expected.date = Time.local(2004, 12, 12)
+
+ created = nil
+ assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
+ assert_not_nil created
+ assert_equal expected.encoded, created.encoded
+
+ assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) }
+ assert_not_nil ActionMailer::Base.deliveries.first
+ assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
+ end
+end
diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG
new file mode 100644
index 0000000..cb684a9
--- /dev/null
+++ b/vendor/rails/actionpack/CHANGELOG
@@ -0,0 +1,4798 @@
+*2.1.0 (May 31st, 2008)*
+
+* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
+
+* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
+
+* Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136]
+
+* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [rick]
+* InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing]
+
+* select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing]
+
+* Fixed that TextHelper#text_field would corrypt when raw HTML was used as the value (mchenryc, Kevin Glowacz) [#80]
+
+* Added ActionController::TestCase#rescue_action_in_public! to control whether the action under test should use the regular rescue_action path instead of simply raising the exception inline (great for error testing) [DHH]
+
+* Reduce number of instance variables being copied from controller to view. [Pratik]
+
+* select_datetime and select_time default to Time.zone.now when config.time_zone is set [Geoff Buesing]
+
+* datetime_select defaults to Time.zone.now when config.time_zone is set [Geoff Buesing]
+
+* Remove ActionController::Base#view_controller_internals flag. [Pratik]
+
+* Add conditional options to caches_page method. [Paul Horsfall]
+
+* Move missing template logic to ActionView. [Pratik]
+
+* Introduce ActionView::InlineTemplate class. [Pratik]
+
+* Automatically parse posted JSON content for Mime::JSON requests. [rick]
+
+ POST /posts
+ {"post": {"title": "Breaking News"}}
+
+ def create
+ @post = Post.create params[:post]
+ # ...
+ end
+
+* add json_escape ERB util to escape html entities in json strings that are output in HTML pages. [rick]
+
+* Provide a helper proxy to access helper methods from outside views. Closes #10839 [Josh Peek]
+ e.g. ApplicationController.helpers.simple_format(text)
+
+* Improve documentation. [Xavier Noria, leethal, jerome]
+
+* Ensure RJS redirect_to doesn't html-escapes string argument. Closes #8546 [josh, eventualbuddha, Pratik]
+
+* Support render :partial => collection of heterogeneous elements. #11491 [Zach Dennis]
+
+* Avoid remote_ip spoofing. [Brian Candler]
+
+* Added support for regexp flags like ignoring case in the :requirements part of routes declarations #11421 [NeilW]
+
+* Fixed that ActionController::Base#read_multipart would fail if boundary was exactly 10240 bytes #10886 [ariejan]
+
+* Fixed HTML::Tokenizer (used in sanitize helper) didn't handle unclosed CDATA tags #10071 [esad, packagethief]
+
+* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
+
+* Fixed that FormHelper#radio_button would produce invalid ids #11298 [harlancrystal]
+
+* Added :confirm option to submit_tag #11415 [miloops]
+
+* Fixed NumberHelper#number_with_precision to properly round in a way that works equally on Mac, Windows, Linux (closes #11409, #8275, #10090, #8027) [zhangyuanyi]
+
+* Allow the #simple_format text_helper to take an html_options hash for each paragraph. #2448 [Francois Beausoleil, thechrisoshow]
+
+* Fix regression from filter refactoring where re-adding a skipped filter resulted in it being called twice. [rick]
+
+* Refactor filters to use Active Support callbacks. #11235 [Josh Peek]
+
+* Fixed that polymorphic routes would modify the input array #11363 [thomas.lee]
+
+* Added :format option to NumberHelper#number_to_currency to enable better localization support #11149 [lylo]
+
+* Fixed that TextHelper#excerpt would include one character too many #11268 [Irfy]
+
+* Fix more obscure nested parameter hash parsing bug. #10797 [thomas.lee]
+
+* Added ActionView::Helpers::register_javascript/stylesheet_expansion to make it easier for plugin developers to inject multiple assets. #10350 [lotswholetime]
+
+* Fix nested parameter hash parsing bug. #10797 [thomas.lee]
+
+* Allow using named routes in ActionController::TestCase before any request has been made. Closes #11273 [alloy]
+
+* Fixed that sweepers defined by cache_sweeper will be added regardless of the perform_caching setting. Instead, control whether the sweeper should be run with the perform_caching setting. This makes testing easier when you want to turn perform_caching on/off [DHH]
+
+* Make MimeResponds::Responder#any work without explicit types. Closes #11140 [jaw6]
+
+* Better error message for type conflicts when parsing params. Closes #7962 [spicycode, matt]
+
+* Remove unused ActionController::Base.template_class. Closes #10787 [Pratik]
+
+* Moved template handlers related code from ActionView::Base to ActionView::Template. [Pratik]
+
+* Tests for div_for and content_tag_for helpers. Closes #11223 [thechrisoshow]
+
+* Allow file uploads in Integration Tests. Closes #11091 [RubyRedRick]
+
+* Refactor partial rendering into a PartialTemplate class. [Pratik]
+
+* Added that requests with JavaScript as the priority mime type in the accept header and no format extension in the parameters will be treated as though their format was :js when it comes to determining which template to render. This makes it possible for JS requests to automatically render action.js.rjs files without an explicit respond_to block [DHH]
+
+* Tests for distance_of_time_in_words with TimeWithZone instances. Closes #10914 [ernesto.jimenez]
+
+* Remove support for multivalued (e.g., '&'-delimited) cookies. [Jamis Buck]
+
+* Fix problem with render :partial collections, records, and locals. #11057 [lotswholetime]
+
+* Added support for naming concrete classes in sweeper declarations [DHH]
+
+* Remove ERB trim variables from trace template in case ActionView::Base.erb_trim_mode is changed in the application. #10098 [tpope, kampers]
+
+* Fix typo in form_helper documentation. #10650 [xaviershay, kampers]
+
+* Fix bug with setting Request#format= after the getter has cached the value. #10889 [cch1]
+
+* Correct inconsistencies in RequestForgeryProtection docs. #11032 [mislav]
+
+* Introduce a Template class to ActionView. #11024 [lifofifo]
+
+* Introduce the :index option for form_for and fields_for to simplify multi-model forms (see http://railscasts.com/episodes/75). #9883 [rmm5t]
+
+* Introduce map.resources :cards, :as => 'tarjetas' to use a custom resource name in the URL: cards_path == '/tarjetas'. #10578 [blj]
+
+* TestSession supports indifferent access. #7372 [tamc, Arsen7, mhackett, julik, jean.helou]
+
+* Make assert_routing aware of the HTTP method used. #8039 [mpalmer]
+ e.g. assert_routing({ :method => 'put', :path => '/product/321' }, { :controller => "product", :action => "update", :id => "321" })
+
+* Make map.root accept a single symbol as an argument to declare an alias. #10818 [bscofield]
+
+ e.g. map.dashboard '/dashboard', :controller=>'dashboard'
+ map.root :dashboard
+
+* Handle corner case with image_tag when passed 'messed up' image names. #9018 [duncanbeevers, mpalmer]
+
+* Add label_tag helper for generating elements. #10802 [DefV]
+
+* Introduce TemplateFinder to handle view paths and lookups. #10800 [Pratik Naik]
+
+* Performance: optimize route recognition. Large speedup for apps with many resource routes. #10835 [oleganza]
+
+* Make render :partial recognise form builders and use the _form partial. #10814 [djanowski]
+
+* Allow users to declare other namespaces when using the atom feed helpers. #10304 [david.calavera]
+
+* Introduce send_file :x_sendfile => true to send an X-Sendfile response header. [Jeremy Kemper]
+
+* Fixed ActionView::Helpers::ActiveRecordHelper::form for when protect_from_forgery is used #10739 [jeremyevans]
+
+* Provide nicer access to HTTP Headers. Instead of request.env["HTTP_REFERRER"] you can now use request.headers["Referrer"]. [Koz]
+
+* UrlWriter respects relative_url_root. #10748 [Cheah Chu Yeow]
+
+* The asset_host block takes the controller request as an optional second argument. Example: use a single asset host for SSL requests. #10549 [Cheah Chu Yeow, Peter B, Tom Taylor]
+
+* Support render :text => nil. #6684 [tjennings, PotatoSalad, Cheah Chu Yeow]
+
+* assert_response failures include the exception message. #10688 [Seth Rasmussen]
+
+* All fragment cache keys are now by default prefixed with the "views/" namespace [DHH]
+
+* Moved the caching stores from ActionController::Caching::Fragments::* to ActiveSupport::Cache::*. If you're explicitly referring to a store, like ActionController::Caching::Fragments::MemoryStore, you need to update that reference with ActiveSupport::Cache::MemoryStore [DHH]
+
+* Deprecated ActionController::Base.fragment_cache_store for ActionController::Base.cache_store [DHH]
+
+* Made fragment caching in views work for rjs and builder as well #6642 [zsombor]
+
+* Fixed rendering of partials with layout when done from site layout #9209 [antramm]
+
+* Fix atom_feed_helper to comply with the atom spec. Closes #10672 [xaviershay]
+
+ * The tags created do not contain a date (http://feedvalidator.org/docs/error/InvalidTAG.html)
+ * IDs are not guaranteed unique
+ * A default self link was not provided, contrary to the documentation
+ * NOTE: This changes tags for existing atom entries, but at least they validate now.
+
+* Correct indentation in tests. Closes #10671 [l.guidi]
+
+* Fix that auto_link looks for ='s in url paths (Amazon urls have them). Closes #10640 [bgreenlee]
+
+* Ensure that test case setup is run even if overridden. #10382 [Josh Peek]
+
+* Fix HTML Sanitizer to allow trailing spaces in CSS style attributes. Closes #10566 [wesley.moxam]
+
+* Add :default option to time_zone_select. #10590 [Matt Aimonetti]
+
+
+*2.0.2* (December 16th, 2007)
+
+* Added delete_via_redirect and put_via_redirect to integration testing #10497 [philodespotos]
+
+* Allow headers['Accept'] to be set by hand when calling xml_http_request #10461 [BMorearty]
+
+* Added OPTIONS to list of default accepted HTTP methods #10449 [holoway]
+
+* Added option to pass proc to ActionController::Base.asset_host for maximum configurability #10521 [chuyeow]. Example:
+
+ ActionController::Base.asset_host = Proc.new { |source|
+ if source.starts_with?('/images')
+ "http://images.example.com"
+ else
+ "http://assets.example.com"
+ end
+ }
+
+* Fixed that ActionView#file_exists? would be incorrect if @first_render is set #10569 [dbussink]
+
+* Added that Array#to_param calls to_param on all it's elements #10473 [brandon]
+
+* Ensure asset cache directories are automatically created. #10337 [Josh Peek, Cheah Chu Yeow]
+
+* render :xml and :json preserve custom content types. #10388 [jmettraux, Cheah Chu Yeow]
+
+* Refactor Action View template handlers. #10437, #10455 [Josh Peek]
+
+* Fix DoubleRenderError message and leave out mention of returning false from filters. Closes #10380 [Frederick Cheung]
+
+* Clean up some cruft around ActionController::Base#head. Closes #10417 [ssoroka]
+
+
+*2.0.1* (December 7th, 2007)
+
+* Fixed send_file/binary_content for testing #8044 [tolsen]
+
+* When a NonInferrableControllerError is raised, make the proposed fix clearer in the error message. Closes #10199 [danger]
+
+* Update Prototype to 1.6.0.1. [sam]
+
+* Update script.aculo.us to 1.8.0.1. [madrobby]
+
+* Add 'disabled' attribute to
+ ]]>
+
+
+
+
+ Test 2
+ ]]>
+
+
+
+
+EOF
+ assert_select "channel item description" do
+ # Test element regardless of wrapper.
+ assert_select_encoded do
+ assert_select "p", :count=>2, :text=>/Test/
+ end
+ # Test through encoded wrapper.
+ assert_select_encoded do
+ assert_select "encoded p", :count=>2, :text=>/Test/
+ end
+ # Use :root instead (recommended)
+ assert_select_encoded do
+ assert_select ":root p", :count=>2, :text=>/Test/
+ end
+ # Test individually.
+ assert_select "description" do |elements|
+ assert_select_encoded elements[0] do
+ assert_select "p", "Test 1"
+ end
+ assert_select_encoded elements[1] do
+ assert_select "p", "Test 2"
+ end
+ end
+ end
+
+ # Test that we only un-encode element itself.
+ assert_select "channel item" do
+ assert_select_encoded do
+ assert_select "p", 0
+ end
+ end
+ end
+
+
+ #
+ # Test assert_select_email
+ #
+
+ def test_assert_select_email
+ assert_raises(AssertionFailedError) { assert_select_email {} }
+ AssertSelectMailer.deliver_test "
foo
bar
"
+ assert_select_email do
+ assert_select "div:root" do
+ assert_select "p:first-child", "foo"
+ assert_select "p:last-child", "bar"
+ end
+ end
+ end
+
+
+ protected
+ def render_html(html)
+ @controller.response_with = html
+ get :html
+ end
+
+ def render_rjs(&block)
+ @controller.response_with &block
+ get :rjs
+ end
+
+ def render_xml(xml)
+ @controller.response_with = xml
+ get :xml
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/base_test.rb b/vendor/rails/actionpack/test/controller/base_test.rb
new file mode 100644
index 0000000..b287175
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/base_test.rb
@@ -0,0 +1,183 @@
+require 'abstract_unit'
+require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late
+
+# Provide some controller to run the tests on.
+module Submodule
+ class ContainedEmptyController < ActionController::Base
+ end
+ class ContainedNonEmptyController < ActionController::Base
+ def public_action
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ raise "Noooo!"
+ end
+
+ def another_hidden_action
+ end
+ hide_action :another_hidden_action
+ end
+ class SubclassedController < ContainedNonEmptyController
+ hide_action :public_action # Hiding it here should not affect the superclass.
+ end
+end
+class EmptyController < ActionController::Base
+end
+class NonEmptyController < ActionController::Base
+ def public_action
+ end
+
+ hide_action :hidden_action
+ def hidden_action
+ end
+end
+
+class MethodMissingController < ActionController::Base
+
+ hide_action :shouldnt_be_called
+ def shouldnt_be_called
+ raise "NO WAY!"
+ end
+
+protected
+
+ def method_missing(selector)
+ render :text => selector.to_s
+ end
+
+end
+
+class DefaultUrlOptionsController < ActionController::Base
+ def default_url_options_action
+ end
+
+ def default_url_options(options = nil)
+ { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' }
+ end
+end
+
+class ControllerClassTests < Test::Unit::TestCase
+ def test_controller_path
+ assert_equal 'empty', EmptyController.controller_path
+ assert_equal EmptyController.controller_path, EmptyController.new.controller_path
+ assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path
+ assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path
+ end
+ def test_controller_name
+ assert_equal 'empty', EmptyController.controller_name
+ assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name
+ end
+end
+
+class ControllerInstanceTests < Test::Unit::TestCase
+ def setup
+ @empty = EmptyController.new
+ @contained = Submodule::ContainedEmptyController.new
+ @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new]
+
+ @non_empty_controllers = [NonEmptyController.new,
+ Submodule::ContainedNonEmptyController.new]
+ end
+
+ def test_action_methods
+ @empty_controllers.each do |c|
+ hide_mocha_methods_from_controller(c)
+ assert_equal Set.new, c.send!(:action_methods), "#{c.controller_path} should be empty!"
+ end
+ @non_empty_controllers.each do |c|
+ hide_mocha_methods_from_controller(c)
+ assert_equal Set.new(%w(public_action)), c.send!(:action_methods), "#{c.controller_path} should not be empty!"
+ end
+ end
+
+ protected
+ # Mocha adds some public instance methods to Object that would be
+ # considered actions, so explicitly hide_action them.
+ def hide_mocha_methods_from_controller(controller)
+ mocha_methods = [
+ :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object,
+ :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher,
+ ]
+ controller.class.send!(:hide_action, *mocha_methods)
+ end
+end
+
+
+class PerformActionTest < Test::Unit::TestCase
+ def use_controller(controller_class)
+ @controller = controller_class.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_get_on_priv_should_show_selector
+ use_controller MethodMissingController
+ get :shouldnt_be_called
+ assert_response :success
+ assert_equal 'shouldnt_be_called', @response.body
+ end
+
+ def test_method_missing_is_not_an_action_name
+ use_controller MethodMissingController
+ assert ! @controller.send!(:action_methods).include?('method_missing')
+
+ get :method_missing
+ assert_response :success
+ assert_equal 'method_missing', @response.body
+ end
+
+ def test_get_on_hidden_should_fail
+ use_controller NonEmptyController
+ get :hidden_action
+ assert_response 404
+
+ get :another_hidden_action
+ assert_response 404
+ end
+end
+
+class DefaultUrlOptionsTest < Test::Unit::TestCase
+ def setup
+ @controller = DefaultUrlOptionsController.new
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = 'www.example.com'
+ end
+
+ def test_default_url_options_are_used_if_set
+ ActionController::Routing::Routes.draw do |map|
+ map.default_url_options 'default_url_options', :controller => 'default_url_options'
+ map.connect ':controller/:action/:id'
+ end
+
+ get :default_url_options_action # Make a dummy request so that the controller is initialized properly.
+
+ assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options')
+ assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url)
+ ensure
+ ActionController::Routing::Routes.load!
+ end
+end
+
+class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase
+ def test_named_routes_still_work
+ ActionController::Routing::Routes.draw do |map|
+ map.resources :things
+ end
+ EmptyController.send :include, ActionController::UrlWriter
+
+ assert_equal '/things', EmptyController.new.send(:things_path)
+ ensure
+ ActionController::Routing::Routes.load!
+ end
+end
\ No newline at end of file
diff --git a/vendor/rails/actionpack/test/controller/benchmark_test.rb b/vendor/rails/actionpack/test/controller/benchmark_test.rb
new file mode 100644
index 0000000..608ea5f
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/benchmark_test.rb
@@ -0,0 +1,32 @@
+require 'abstract_unit'
+
+# Provide some static controllers.
+class BenchmarkedController < ActionController::Base
+ def public_action
+ render :nothing => true
+ end
+
+ def rescue_action(e)
+ raise e
+ end
+end
+
+class BenchmarkTest < Test::Unit::TestCase
+ class MockLogger
+ def method_missing(*args)
+ end
+ end
+
+ def setup
+ @controller = BenchmarkedController.new
+ # benchmark doesn't do anything unless a logger is set
+ @controller.logger = MockLogger.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ @request.host = "test.actioncontroller.i"
+ end
+
+ def test_with_http_1_0_request
+ @request.host = nil
+ assert_nothing_raised { get :public_action }
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/caching_test.rb b/vendor/rails/actionpack/test/controller/caching_test.rb
new file mode 100644
index 0000000..f9b6b87
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/caching_test.rb
@@ -0,0 +1,610 @@
+require 'fileutils'
+require 'abstract_unit'
+
+CACHE_DIR = 'test_cache'
+# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
+FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
+ActionController::Base.page_cache_directory = FILE_STORE_PATH
+ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
+ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/' ]
+
+class PageCachingTestController < ActionController::Base
+ caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
+ caches_page :found, :not_found
+
+ def ok
+ head :ok
+ end
+
+ def no_content
+ head :no_content
+ end
+
+ def found
+ redirect_to :action => 'ok'
+ end
+
+ def not_found
+ head :not_found
+ end
+
+ def custom_path
+ render :text => "Super soaker"
+ cache_page("Super soaker", "/index.html")
+ end
+
+ def expire_custom_path
+ expire_page("/index.html")
+ head :ok
+ end
+
+ def trailing_slash
+ render :text => "Sneak attack"
+ end
+end
+
+class PageCachingTest < Test::Unit::TestCase
+ def setup
+ ActionController::Base.perform_caching = true
+
+ ActionController::Routing::Routes.draw do |map|
+ map.main '', :controller => 'posts'
+ map.resources :posts
+ map.connect ':controller/:action/:id'
+ end
+
+ @request = ActionController::TestRequest.new
+ @request.host = 'hostname.com'
+
+ @response = ActionController::TestResponse.new
+ @controller = PageCachingTestController.new
+
+ @params = {:controller => 'posts', :action => 'index', :only_path => true, :skip_relative_url_root => true}
+ @rewriter = ActionController::UrlRewriter.new(@request, @params)
+
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+ FileUtils.mkdir_p(FILE_STORE_PATH)
+ end
+
+ def teardown
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+
+ ActionController::Base.perform_caching = false
+ end
+
+ def test_page_caching_resources_saves_to_correct_path_with_extension_even_if_default_route
+ @params[:format] = 'rss'
+ assert_equal '/posts.rss', @rewriter.rewrite(@params)
+ @params[:format] = nil
+ assert_equal '/', @rewriter.rewrite(@params)
+ end
+
+ def test_should_cache_get_with_ok_status
+ get :ok
+ assert_response :ok
+ assert_page_cached :ok, "get with ok status should have been cached"
+ end
+
+ def test_should_cache_with_custom_path
+ get :custom_path
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+
+ def test_should_expire_cache_with_custom_path
+ get :custom_path
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+
+ get :expire_custom_path
+ assert !File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+
+ def test_should_cache_without_trailing_slash_on_url
+ @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash'
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
+ end
+
+ def test_should_cache_with_trailing_slash_on_url
+ @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash/'
+ assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html")
+ end
+
+ uses_mocha("should_cache_ok_at_custom_path") do
+ def test_should_cache_ok_at_custom_path
+ @request.expects(:path).returns("/index.html")
+ get :ok
+ assert_response :ok
+ assert File.exist?("#{FILE_STORE_PATH}/index.html")
+ end
+ end
+
+ [:ok, :no_content, :found, :not_found].each do |status|
+ [:get, :post, :put, :delete].each do |method|
+ unless method == :get and status == :ok
+ define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do
+ @request.env['REQUEST_METHOD'] = method.to_s.upcase
+ process status
+ assert_response status
+ assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached"
+ end
+ end
+ end
+ end
+
+ def test_page_caching_conditional_options
+ @request.env['HTTP_ACCEPT'] = 'application/json'
+ get :ok
+ assert_page_not_cached :ok
+ end
+
+ private
+ def assert_page_cached(action, message = "#{action} should have been cached")
+ assert page_cached?(action), message
+ end
+
+ def assert_page_not_cached(action, message = "#{action} shouldn't have been cached")
+ assert !page_cached?(action), message
+ end
+
+ def page_cached?(action)
+ File.exist? "#{FILE_STORE_PATH}/page_caching_test/#{action}.html"
+ end
+end
+
+
+class ActionCachingTestController < ActionController::Base
+ caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }
+ caches_action :show, :cache_path => 'http://test.host/custom/show'
+ caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
+ caches_action :with_layout
+
+ layout 'talk_from_action.erb'
+
+ def index
+ @cache_this = MockTime.now.to_f.to_s
+ render :text => @cache_this
+ end
+
+ def redirected
+ redirect_to :action => 'index'
+ end
+
+ def forbidden
+ render :text => "Forbidden"
+ headers["Status"] = "403 Forbidden"
+ end
+
+ def with_layout
+ @cache_this = MockTime.now.to_f.to_s
+ render :text => @cache_this, :layout => true
+ end
+
+ alias_method :show, :index
+ alias_method :edit, :index
+ alias_method :destroy, :index
+
+ def expire
+ expire_action :controller => 'action_caching_test', :action => 'index'
+ render :nothing => true
+ end
+end
+
+class MockTime < Time
+ # Let Time spicy to assure that Time.now != Time.now
+ def to_f
+ super+rand
+ end
+end
+
+class ActionCachingMockController
+ attr_accessor :mock_url_for
+ attr_accessor :mock_path
+
+ def initialize
+ yield self if block_given?
+ end
+
+ def url_for(*args)
+ @mock_url_for
+ end
+
+ def request
+ mocked_path = @mock_path
+ Object.new.instance_eval(<<-EVAL)
+ def path; '#{@mock_path}' end
+ self
+ EVAL
+ end
+end
+
+class ActionCacheTest < Test::Unit::TestCase
+ def setup
+ reset!
+ FileUtils.mkdir_p(FILE_STORE_PATH)
+ @path_class = ActionController::Caching::Actions::ActionCachePath
+ @mock_controller = ActionCachingMockController.new
+ end
+
+ def teardown
+ FileUtils.rm_rf(File.dirname(FILE_STORE_PATH))
+ end
+
+ def test_simple_action_cache
+ get :index
+ cached_time = content_to_cache
+ assert_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test')
+ reset!
+
+ get :index
+ assert_equal cached_time, @response.body
+ end
+
+ def test_simple_action_not_cached
+ get :destroy
+ cached_time = content_to_cache
+ assert_equal cached_time, @response.body
+ assert !fragment_exist?('hostname.com/action_caching_test/destroy')
+ reset!
+
+ get :destroy
+ assert_not_equal cached_time, @response.body
+ end
+
+ def test_action_cache_with_layout
+ get :with_layout
+ cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ assert fragment_exist?('hostname.com/action_caching_test/with_layout')
+ reset!
+
+ get :with_layout
+ assert_not_equal cached_time, @response.body
+
+ assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout')
+ end
+
+ def test_action_cache_conditional_options
+ @request.env['HTTP_ACCEPT'] = 'application/json'
+ get :index
+ assert !fragment_exist?('hostname.com/action_caching_test')
+ end
+
+ def test_action_cache_with_custom_cache_path
+ get :show
+ cached_time = content_to_cache
+ assert_equal cached_time, @response.body
+ assert fragment_exist?('test.host/custom/show')
+ reset!
+
+ get :show
+ assert_equal cached_time, @response.body
+ end
+
+ def test_action_cache_with_custom_cache_path_in_block
+ get :edit
+ assert fragment_exist?('test.host/edit')
+ reset!
+
+ get :edit, :id => 1
+ assert fragment_exist?('test.host/1;edit')
+ end
+
+ def test_cache_expiration
+ get :index
+ cached_time = content_to_cache
+ reset!
+
+ get :index
+ assert_equal cached_time, @response.body
+ reset!
+
+ get :expire
+ reset!
+
+ get :index
+ new_cached_time = content_to_cache
+ assert_not_equal cached_time, @response.body
+ reset!
+
+ get :index
+ assert_response :success
+ assert_equal new_cached_time, @response.body
+ end
+
+ def test_cache_is_scoped_by_subdomain
+ @request.host = 'jamis.hostname.com'
+ get :index
+ jamis_cache = content_to_cache
+
+ reset!
+
+ @request.host = 'david.hostname.com'
+ get :index
+ david_cache = content_to_cache
+ assert_not_equal jamis_cache, @response.body
+
+ reset!
+
+ @request.host = 'jamis.hostname.com'
+ get :index
+ assert_equal jamis_cache, @response.body
+
+ reset!
+
+ @request.host = 'david.hostname.com'
+ get :index
+ assert_equal david_cache, @response.body
+ end
+
+ def test_redirect_is_not_cached
+ get :redirected
+ assert_response :redirect
+ reset!
+
+ get :redirected
+ assert_response :redirect
+ end
+
+ def test_forbidden_is_not_cached
+ get :forbidden
+ assert_response :forbidden
+ reset!
+
+ get :forbidden
+ assert_response :forbidden
+ end
+
+ def test_xml_version_of_resource_is_treated_as_different_cache
+ @mock_controller.mock_url_for = 'http://example.org/posts/'
+ @mock_controller.mock_path = '/posts/index.xml'
+ path_object = @path_class.new(@mock_controller, {})
+ assert_equal 'xml', path_object.extension
+ assert_equal 'example.org/posts/index.xml', path_object.path
+ end
+
+ def test_correct_content_type_is_returned_for_cache_hit
+ # run it twice to cache it the first time
+ get :index, :id => 'content-type.xml'
+ get :index, :id => 'content-type.xml'
+ assert_equal 'application/xml', @response.content_type
+ end
+
+ def test_empty_path_is_normalized
+ @mock_controller.mock_url_for = 'http://example.org/'
+ @mock_controller.mock_path = '/'
+
+ assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {})
+ end
+
+ def test_file_extensions
+ get :index, :id => 'kitten.jpg'
+ get :index, :id => 'kitten.jpg'
+
+ assert_response :success
+ end
+
+ private
+ def content_to_cache
+ assigns(:cache_this)
+ end
+
+ def reset!
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = ActionCachingTestController.new
+ @request.host = 'hostname.com'
+ end
+
+ def fragment_exist?(path)
+ @controller.fragment_exist?(path)
+ end
+
+ def read_fragment(path)
+ @controller.read_fragment(path)
+ end
+end
+
+class FragmentCachingTestController < ActionController::Base
+ def some_action; end;
+end
+
+class FragmentCachingTest < Test::Unit::TestCase
+ def setup
+ ActionController::Base.perform_caching = true
+ @store = ActiveSupport::Cache::MemoryStore.new
+ ActionController::Base.cache_store = @store
+ @controller = FragmentCachingTestController.new
+ @params = {:controller => 'posts', :action => 'index'}
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller.params = @params
+ @controller.request = @request
+ @controller.response = @response
+ @controller.send(:initialize_current_url)
+ end
+
+ def test_fragment_cache_key
+ assert_equal 'views/what a key', @controller.fragment_cache_key('what a key')
+ assert_equal( "views/test.host/fragment_caching_test/some_action",
+ @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action'))
+ end
+
+ def test_read_fragment__with_caching_enabled
+ @store.write('views/name', 'value')
+ assert_equal 'value', @controller.read_fragment('name')
+ end
+
+ def test_read_fragment__with_caching_disabled
+ ActionController::Base.perform_caching = false
+ @store.write('views/name', 'value')
+ assert_nil @controller.read_fragment('name')
+ end
+
+ def test_fragment_exist__with_caching_enabled
+ @store.write('views/name', 'value')
+ assert @controller.fragment_exist?('name')
+ assert !@controller.fragment_exist?('other_name')
+ end
+
+ def test_fragment_exist__with_caching_disabled
+ ActionController::Base.perform_caching = false
+ @store.write('views/name', 'value')
+ assert !@controller.fragment_exist?('name')
+ assert !@controller.fragment_exist?('other_name')
+ end
+
+ def test_write_fragment__with_caching_enabled
+ assert_nil @store.read('views/name')
+ assert_equal 'value', @controller.write_fragment('name', 'value')
+ assert_equal 'value', @store.read('views/name')
+ end
+
+ def test_write_fragment__with_caching_disabled
+ assert_nil @store.read('views/name')
+ ActionController::Base.perform_caching = false
+ assert_equal nil, @controller.write_fragment('name', 'value')
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment__with_simple_key
+ @store.write('views/name', 'value')
+ @controller.expire_fragment 'name'
+ assert_nil @store.read('views/name')
+ end
+
+ def test_expire_fragment__with__regexp
+ @store.write('views/name', 'value')
+ @store.write('views/another_name', 'another_value')
+ @store.write('views/primalgrasp', 'will not expire ;-)')
+
+ @controller.expire_fragment /name/
+
+ assert_nil @store.read('views/name')
+ assert_nil @store.read('views/another_name')
+ assert_equal 'will not expire ;-)', @store.read('views/primalgrasp')
+ end
+
+ def test_fragment_for__with_disabled_caching
+ ActionController::Base.perform_caching = false
+
+ @store.write('views/expensive', 'fragment content')
+ fragment_computed = false
+
+ buffer = 'generated till now -> '
+ @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer }
+
+ assert fragment_computed
+ assert_equal 'generated till now -> ', buffer
+ end
+
+ def test_fragment_for
+ @store.write('views/expensive', 'fragment content')
+ fragment_computed = false
+
+ buffer = 'generated till now -> '
+ @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer}
+
+ assert !fragment_computed
+ assert_equal 'generated till now -> fragment content', buffer
+ end
+
+ def test_cache_erb_fragment
+ @store.write('views/expensive', 'fragment content')
+ _erbout = 'generated till now -> '
+
+ assert_equal( 'generated till now -> fragment content',
+ ActionView::TemplateHandlers::ERB.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
+ end
+
+ def test_cache_rxml_fragment
+ @store.write('views/expensive', 'fragment content')
+ xml = 'generated till now -> '
+ class << xml; def target!; to_s; end; end
+
+ assert_equal( 'generated till now -> fragment content',
+ ActionView::TemplateHandlers::Builder.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
+ end
+
+ def test_cache_rjs_fragment
+ @store.write('views/expensive', 'fragment content')
+ page = 'generated till now -> '
+
+ assert_equal( 'generated till now -> fragment content',
+ ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
+ end
+
+ def test_cache_rjs_fragment_debug_mode_does_not_interfere
+ @store.write('views/expensive', 'fragment content')
+ page = 'generated till now -> '
+
+ begin
+ debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true
+ assert_equal( 'generated till now -> fragment content',
+ ActionView::TemplateHandlers::RJS.new(@controller).cache_fragment(Proc.new{ }, 'expensive'))
+ assert ActionView::Base.debug_rjs
+ ensure
+ ActionView::Base.debug_rjs = debug_mode
+ end
+ end
+end
+
+
+class FunctionalCachingController < ActionController::Base
+ def fragment_cached
+ end
+
+ def html_fragment_cached_with_partial
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ def js_fragment_cached_with_partial
+ respond_to do |format|
+ format.js
+ end
+ end
+
+
+ def rescue_action(e)
+ raise e
+ end
+end
+
+FunctionalCachingController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class FunctionalFragmentCachingTest < Test::Unit::TestCase
+ def setup
+ ActionController::Base.perform_caching = true
+ @store = ActiveSupport::Cache::MemoryStore.new
+ ActionController::Base.cache_store = @store
+ @controller = FunctionalCachingController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+ def test_fragment_caching
+ get :fragment_cached
+ assert_response :success
+ expected_body = <<-CACHED
+Hello
+This bit's fragment cached
+CACHED
+ assert_equal expected_body, @response.body
+
+ assert_equal "This bit's fragment cached", @store.read('views/test.host/functional_caching/fragment_cached')
+ end
+
+ def test_fragment_caching_in_partials
+ get :html_fragment_cached_with_partial
+ assert_response :success
+ assert_match /Fragment caching in a partial/, @response.body
+ assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial')
+ end
+
+ def test_fragment_caching_in_rjs_partials
+ xhr :get, :js_fragment_cached_with_partial
+ assert_response :success
+ assert_match /Fragment caching in a partial/, @response.body
+ assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial')
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/capture_test.rb b/vendor/rails/actionpack/test/controller/capture_test.rb
new file mode 100644
index 0000000..aaafea3
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/capture_test.rb
@@ -0,0 +1,89 @@
+require 'abstract_unit'
+
+class CaptureController < ActionController::Base
+ def self.controller_name; "test"; end
+ def self.controller_path; "test"; end
+
+ def content_for
+ render :layout => "talk_from_action"
+ end
+
+ def content_for_with_parameter
+ render :layout => "talk_from_action"
+ end
+
+ def content_for_concatenated
+ render :layout => "talk_from_action"
+ end
+
+ def erb_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def block_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def non_erb_block_content_for
+ render :layout => "talk_from_action"
+ end
+
+ def rescue_action(e) raise end
+end
+
+CaptureController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class CaptureTest < Test::Unit::TestCase
+ def setup
+ @controller = CaptureController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_simple_capture
+ get :capturing
+ assert_equal "Dreamy days", @response.body.strip
+ end
+
+ def test_content_for
+ get :content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_should_concatentate_content_for
+ get :content_for_concatenated
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_erb_content_for
+ get :erb_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_should_set_content_for_with_parameter
+ get :content_for_with_parameter
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_block_content_for
+ get :block_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ def test_non_erb_block_content_for
+ get :non_erb_block_content_for
+ assert_equal expected_content_for_output, @response.body
+ end
+
+ private
+ def expected_content_for_output
+ "Putting stuff in the title!\n\nGreat stuff!"
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/cgi_test.rb b/vendor/rails/actionpack/test/controller/cgi_test.rb
new file mode 100644
index 0000000..87f72fd
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/cgi_test.rb
@@ -0,0 +1,116 @@
+require 'abstract_unit'
+require 'action_controller/cgi_process'
+
+class BaseCgiTest < Test::Unit::TestCase
+ def setup
+ @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
+ # some Nokia phone browsers omit the space after the semicolon separator.
+ # some developers have grown accustomed to using comma in cookie values.
+ @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
+ @fake_cgi = Struct.new(:env_table).new(@request_hash)
+ @request = ActionController::CgiRequest.new(@fake_cgi)
+ end
+
+ def default_test; end
+end
+
+
+class CgiRequestTest < BaseCgiTest
+ def test_proxy_request
+ assert_equal 'glu.ttono.us', @request.host_with_port
+ end
+
+ def test_http_host
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
+ assert_equal "rubyonrails.org:8080", @request.host_with_port
+
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", @request.host
+ end
+
+ def test_http_host_with_default_port_overrides_server_port
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "rubyonrails.org"
+ assert_equal "rubyonrails.org", @request.host_with_port
+ end
+
+ def test_host_with_port_defaults_to_server_name_if_no_host_headers
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash.delete "HTTP_HOST"
+ assert_equal "glu.ttono.us:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_falls_back_to_server_addr_if_necessary
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash.delete "HTTP_HOST"
+ @request_hash.delete "SERVER_NAME"
+ assert_equal "207.7.108.53:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_if_http_standard_port_is_specified
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_with_port_if_https_standard_port_is_specified
+ @request_hash['HTTP_X_FORWARDED_PROTO'] = "https"
+ @request_hash['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_if_ipv6_reference
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_host_if_ipv6_reference_with_port
+ @request_hash.delete "HTTP_X_FORWARDED_HOST"
+ @request_hash['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_cookie_syntax_resilience
+ cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
+ assert_equal ["yes"], cookies["is_admin"], cookies.inspect
+
+ alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
+ assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
+ end
+end
+
+
+class CgiRequestParamsParsingTest < BaseCgiTest
+ def test_doesnt_break_when_content_type_has_charset
+ data = 'flamenco=love'
+ @request.env['CONTENT_LENGTH'] = data.length
+ @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+ @request.env['RAW_POST_DATA'] = data
+ assert_equal({"flamenco"=> "love"}, @request.request_parameters)
+ end
+
+ def test_doesnt_interpret_request_uri_as_query_string_when_missing
+ @request.env['REQUEST_URI'] = 'foo'
+ assert_equal({}, @request.query_parameters)
+ end
+end
+
+
+class CgiRequestNeedsRewoundTest < BaseCgiTest
+ def test_body_should_be_rewound
+ data = 'foo'
+ fake_cgi = Struct.new(:env_table, :query_string, :stdinput).new(@request_hash, '', StringIO.new(data))
+ fake_cgi.env_table['CONTENT_LENGTH'] = data.length
+ fake_cgi.env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+
+ # Read the request body by parsing params.
+ request = ActionController::CgiRequest.new(fake_cgi)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/components_test.rb b/vendor/rails/actionpack/test/controller/components_test.rb
new file mode 100644
index 0000000..82c5548
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/components_test.rb
@@ -0,0 +1,140 @@
+require 'abstract_unit'
+
+class CallerController < ActionController::Base
+ def calling_from_controller
+ render_component(:controller => "callee", :action => "being_called")
+ end
+
+ def calling_from_controller_with_params
+ render_component(:controller => "callee", :action => "being_called", :params => { "name" => "David" })
+ end
+
+ def calling_from_controller_with_different_status_code
+ render_component(:controller => "callee", :action => "blowing_up")
+ end
+
+ def calling_from_template
+ render :inline => "Ring, ring: <%= render_component(:controller => 'callee', :action => 'being_called') %>"
+ end
+
+ def internal_caller
+ render :inline => "Are you there? <%= render_component(:action => 'internal_callee') %>"
+ end
+
+ def internal_callee
+ render :text => "Yes, ma'am"
+ end
+
+ def set_flash
+ render_component(:controller => "callee", :action => "set_flash")
+ end
+
+ def use_flash
+ render_component(:controller => "callee", :action => "use_flash")
+ end
+
+ def calling_redirected
+ render_component(:controller => "callee", :action => "redirected")
+ end
+
+ def calling_redirected_as_string
+ render :inline => "<%= render_component(:controller => 'callee', :action => 'redirected') %>"
+ end
+
+ def rescue_action(e) raise end
+end
+
+class CalleeController < ActionController::Base
+ def being_called
+ render :text => "#{params[:name] || "Lady"} of the House, speaking"
+ end
+
+ def blowing_up
+ render :text => "It's game over, man, just game over, man!", :status => 500
+ end
+
+ def set_flash
+ flash[:notice] = 'My stoney baby'
+ render :text => 'flash is set'
+ end
+
+ def use_flash
+ render :text => flash[:notice] || 'no flash'
+ end
+
+ def redirected
+ redirect_to :controller => "callee", :action => "being_called"
+ end
+
+ def rescue_action(e) raise end
+end
+
+class ComponentsTest < Test::Unit::TestCase
+ def setup
+ @controller = CallerController.new
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_calling_from_controller
+ get :calling_from_controller
+ assert_equal "Lady of the House, speaking", @response.body
+ end
+
+ def test_calling_from_controller_with_params
+ get :calling_from_controller_with_params
+ assert_equal "David of the House, speaking", @response.body
+ end
+
+ def test_calling_from_controller_with_different_status_code
+ get :calling_from_controller_with_different_status_code
+ assert_equal 500, @response.response_code
+ end
+
+ def test_calling_from_template
+ get :calling_from_template
+ assert_equal "Ring, ring: Lady of the House, speaking", @response.body
+ end
+
+ def test_etag_is_set_for_parent_template_when_calling_from_template
+ get :calling_from_template
+ expected_etag = etag_for("Ring, ring: Lady of the House, speaking")
+ assert_equal expected_etag, @response.headers['ETag']
+ end
+
+ def test_internal_calling
+ get :internal_caller
+ assert_equal "Are you there? Yes, ma'am", @response.body
+ end
+
+ def test_flash
+ get :set_flash
+ assert_equal 'My stoney baby', flash[:notice]
+ get :use_flash
+ assert_equal 'My stoney baby', @response.body
+ get :use_flash
+ assert_equal 'no flash', @response.body
+ end
+
+ def test_component_redirect_redirects
+ get :calling_redirected
+
+ assert_redirected_to :action => "being_called"
+ end
+
+ def test_component_multiple_redirect_redirects
+ test_component_redirect_redirects
+ test_internal_calling
+ end
+
+ def test_component_as_string_redirect_renders_redirected_action
+ get :calling_redirected_as_string
+
+ assert_equal "Lady of the House, speaking", @response.body
+ end
+
+ protected
+ def etag_for(text)
+ %("#{Digest::MD5.hexdigest(text)}")
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/content_type_test.rb b/vendor/rails/actionpack/test/controller/content_type_test.rb
new file mode 100644
index 0000000..d262ce8
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/content_type_test.rb
@@ -0,0 +1,139 @@
+require 'abstract_unit'
+
+class ContentTypeController < ActionController::Base
+ def render_content_type_from_body
+ response.content_type = Mime::RSS
+ render :text => "hello world!"
+ end
+
+ def render_defaults
+ render :text => "hello world!"
+ end
+
+ def render_content_type_from_render
+ render :text => "hello world!", :content_type => Mime::RSS
+ end
+
+ def render_charset_from_body
+ response.charset = "utf-16"
+ render :text => "hello world!"
+ end
+
+ def render_default_for_rhtml
+ end
+
+ def render_default_for_rxml
+ end
+
+ def render_default_for_rjs
+ end
+
+ def render_change_for_rxml
+ response.content_type = Mime::HTML
+ render :action => "render_default_for_rxml"
+ end
+
+ def render_default_content_types_for_respond_to
+ respond_to do |format|
+ format.html { render :text => "hello world!" }
+ format.xml { render :action => "render_default_content_types_for_respond_to.rhtml" }
+ format.js { render :text => "hello world!" }
+ format.rss { render :text => "hello world!", :content_type => Mime::XML }
+ end
+ end
+
+ def rescue_action(e) raise end
+end
+
+ContentTypeController.view_paths = [ File.dirname(__FILE__) + "/../fixtures/" ]
+
+class ContentTypeTest < Test::Unit::TestCase
+ def setup
+ @controller = ContentTypeController.new
+
+ # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get
+ # a more accurate simulation of what happens in "real life".
+ @controller.logger = Logger.new(nil)
+
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_render_defaults
+ get :render_defaults
+ assert_equal "utf-8", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+
+ def test_render_changed_charset_default
+ ContentTypeController.default_charset = "utf-16"
+ get :render_defaults
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ ContentTypeController.default_charset = "utf-8"
+ end
+
+ def test_content_type_from_body
+ get :render_content_type_from_body
+ assert_equal "application/rss+xml", @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_content_type_from_render
+ get :render_content_type_from_render
+ assert_equal "application/rss+xml", @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_charset_from_body
+ get :render_charset_from_body
+ assert_equal "utf-16", @response.charset
+ assert_equal Mime::HTML, @response.content_type
+ end
+
+ def test_default_for_rhtml
+ get :render_default_for_rhtml
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_default_for_rxml
+ get :render_default_for_rxml
+ assert_equal Mime::XML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_default_for_rjs
+ xhr :post, :render_default_for_rjs
+ assert_equal Mime::JS, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_change_for_rxml
+ get :render_change_for_rxml
+ assert_equal Mime::HTML, @response.content_type
+ assert_equal "utf-8", @response.charset
+ end
+
+ def test_render_default_content_types_for_respond_to
+ @request.env["HTTP_ACCEPT"] = Mime::HTML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::HTML, @response.content_type
+
+ @request.env["HTTP_ACCEPT"] = Mime::JS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::JS, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_template
+ @request.env["HTTP_ACCEPT"] = Mime::XML.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+
+ def test_render_default_content_types_for_respond_to_with_overwrite
+ @request.env["HTTP_ACCEPT"] = Mime::RSS.to_s
+ get :render_default_content_types_for_respond_to
+ assert_equal Mime::XML, @response.content_type
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
new file mode 100644
index 0000000..e69de29
diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
new file mode 100644
index 0000000..e69de29
diff --git a/vendor/rails/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb b/vendor/rails/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
new file mode 100644
index 0000000..e69de29
diff --git a/vendor/rails/actionpack/test/controller/cookie_test.rb b/vendor/rails/actionpack/test/controller/cookie_test.rb
new file mode 100644
index 0000000..b45fbb1
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/cookie_test.rb
@@ -0,0 +1,146 @@
+require 'abstract_unit'
+
+class CookieTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def authenticate
+ cookies["user_name"] = "david"
+ end
+
+ def authenticate_for_fourteen_days
+ cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ end
+
+ def authenticate_for_fourteen_days_with_symbols
+ cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) }
+ end
+
+ def set_multiple_cookies
+ cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) }
+ cookies["login"] = "XJ-122"
+ end
+
+ def access_frozen_cookies
+ cookies["will"] = "work"
+ end
+
+ def logout
+ cookies.delete("user_name")
+ end
+
+ def delete_cookie_with_path
+ cookies.delete("user_name", :path => '/beaten')
+ render :text => "hello world"
+ end
+
+ def authenticate_with_http_only
+ cookies["user_name"] = { :value => "david", :http_only => true }
+ end
+
+ def rescue_action(e)
+ raise unless ActionView::MissingTemplate # No templates here, and we don't care about the output
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+
+ @controller = TestController.new
+ @request.host = "www.nextangle.com"
+ end
+
+ def test_setting_cookie
+ get :authenticate
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david") ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_for_fourteen_days
+ get :authenticate_for_fourteen_days
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_for_fourteen_days_with_symbols
+ get :authenticate_for_fourteen_days
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "expires" => Time.local(2005, 10, 10)) ], @response.headers["cookie"]
+ end
+
+ def test_setting_cookie_with_http_only
+ get :authenticate_with_http_only
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "david", "http_only" => true) ], @response.headers["cookie"]
+ assert_equal CGI::Cookie::new("name" => "user_name", "value" => "david", "path" => "/", "http_only" => true).to_s, @response.headers["cookie"][0].to_s
+ end
+
+ def test_multiple_cookies
+ get :set_multiple_cookies
+ assert_equal 2, @response.cookies.size
+ end
+
+ def test_setting_test_cookie
+ assert_nothing_raised { get :access_frozen_cookies }
+ end
+
+ def test_expiring_cookie
+ get :logout
+ assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"]
+ assert_equal CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)).value, []
+ end
+
+ def test_cookiejar_accessor
+ @request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10))
+ @controller.request = @request
+ jar = ActionController::CookieJar.new(@controller)
+ assert_equal "david", jar["user_name"]
+ assert_equal nil, jar["something_else"]
+ end
+
+ def test_cookiejar_accessor_with_array_value
+ a = %w{1 2 3}
+ @request.cookies["pages"] = CGI::Cookie.new("name" => "pages", "value" => a, "expires" => Time.local(2025, 10, 10))
+ @controller.request = @request
+ jar = ActionController::CookieJar.new(@controller)
+ assert_equal a, jar["pages"]
+ end
+
+ def test_delete_cookie_with_path
+ get :delete_cookie_with_path
+ assert_equal "/beaten", @response.headers["cookie"].first.path
+ assert_not_equal "/", @response.headers["cookie"].first.path
+ end
+
+ def test_cookie_to_s_simple_values
+ assert_equal 'myname=myvalue; path=', CGI::Cookie.new('myname', 'myvalue').to_s
+ end
+
+ def test_cookie_to_s_hash
+ cookie_str = CGI::Cookie.new(
+ 'name' => 'myname',
+ 'value' => 'myvalue',
+ 'domain' => 'mydomain',
+ 'path' => 'mypath',
+ 'expires' => Time.utc(2007, 10, 20),
+ 'secure' => true,
+ 'http_only' => true).to_s
+ assert_equal 'myname=myvalue; domain=mydomain; path=mypath; expires=Sat, 20 Oct 2007 00:00:00 GMT; secure; HttpOnly', cookie_str
+ end
+
+ def test_cookie_to_s_hash_default_not_secure_not_http_only
+ cookie_str = CGI::Cookie.new(
+ 'name' => 'myname',
+ 'value' => 'myvalue',
+ 'domain' => 'mydomain',
+ 'path' => 'mypath',
+ 'expires' => Time.utc(2007, 10, 20))
+ assert cookie_str !~ /secure/
+ assert cookie_str !~ /HttpOnly/
+ end
+
+ def test_cookies_should_not_be_split_on_ampersand_values
+ cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true')
+ assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies)
+ end
+
+ def test_cookies_should_not_be_split_on_values_with_newlines
+ cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest")
+ assert cookies.size == 1
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/custom_handler_test.rb b/vendor/rails/actionpack/test/controller/custom_handler_test.rb
new file mode 100644
index 0000000..ac484ae
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/custom_handler_test.rb
@@ -0,0 +1,45 @@
+require 'abstract_unit'
+
+class CustomHandler < ActionView::TemplateHandler
+ def initialize( view )
+ @view = view
+ end
+
+ def render( template )
+ [ template.source,
+ template.locals,
+ @view ]
+ end
+end
+
+class CustomHandlerTest < Test::Unit::TestCase
+ def setup
+ ActionView::Template.register_template_handler "foo", CustomHandler
+ ActionView::Template.register_template_handler :foo2, CustomHandler
+ @view = ActionView::Base.new
+ end
+
+ def test_custom_render
+ template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo")
+
+ result = @view.render_template(template)
+ assert_equal(
+ [ "hello <%= one %>", { :one => "two" }, @view ],
+ result )
+ end
+
+ def test_custom_render2
+ template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "foo2")
+ result = @view.render_template(template)
+ assert_equal(
+ [ "hello <%= one %>", { :one => "two" }, @view ],
+ result )
+ end
+
+ def test_unhandled_extension
+ # uses the ERb handler by default if the extension isn't recognized
+ template = ActionView::InlineTemplate.new(@view, "hello <%= one %>", { :one => "two" }, "bar")
+ result = @view.render_template(template)
+ assert_equal "hello two", result
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb b/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
new file mode 100644
index 0000000..8c1a895
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/deprecation/deprecated_base_methods_test.rb
@@ -0,0 +1,37 @@
+require 'abstract_unit'
+
+class DeprecatedBaseMethodsTest < Test::Unit::TestCase
+ class Target < ActionController::Base
+
+ def home_url(greeting)
+ "http://example.com/#{greeting}"
+ end
+
+ def raises_name_error
+ this_method_doesnt_exist
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ Target.view_paths = [ File.dirname(__FILE__) + "/../../fixtures" ]
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = Target.new
+ end
+
+ def test_log_error_silences_deprecation_warnings
+ get :raises_name_error
+ rescue => e
+ assert_not_deprecated { @controller.send :log_error, e }
+ end
+
+ def test_assertion_failed_error_silences_deprecation_warnings
+ get :raises_name_error
+ rescue => e
+ error = Test::Unit::Error.new('testing ur doodz', e)
+ assert_not_deprecated { error.message }
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/dispatcher_test.rb b/vendor/rails/actionpack/test/controller/dispatcher_test.rb
new file mode 100644
index 0000000..eea0813
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/dispatcher_test.rb
@@ -0,0 +1,105 @@
+require 'abstract_unit'
+
+uses_mocha 'dispatcher tests' do
+
+require 'action_controller/dispatcher'
+
+class DispatcherTest < Test::Unit::TestCase
+ Dispatcher = ActionController::Dispatcher
+
+ def setup
+ @output = StringIO.new
+ ENV['REQUEST_METHOD'] = 'GET'
+
+ # Clear callbacks as they are redefined by Dispatcher#define_dispatcher_callbacks
+ Dispatcher.instance_variable_set("@prepare_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Dispatcher.instance_variable_set("@before_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Dispatcher.instance_variable_set("@after_dispatch_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+
+ Dispatcher.stubs(:require_dependency)
+
+ @dispatcher = Dispatcher.new(@output)
+ end
+
+ def teardown
+ ENV.delete 'REQUEST_METHOD'
+ end
+
+ def test_clears_dependencies_after_dispatch_if_in_loading_mode
+ ActionController::Routing::Routes.expects(:reload).once
+ Dependencies.expects(:clear).once
+
+ dispatch(@output, false)
+ end
+
+ def test_leaves_dependencies_after_dispatch_if_not_in_loading_mode
+ ActionController::Routing::Routes.expects(:reload).never
+ Dependencies.expects(:clear).never
+
+ dispatch
+ end
+
+ # Stub out dispatch error logger
+ class << Dispatcher
+ def log_failsafe_exception(status, exception); end
+ end
+
+ def test_failsafe_response
+ CGI.expects(:new).raises('some multipart parsing failure')
+ Dispatcher.expects(:log_failsafe_exception)
+
+ assert_nothing_raised { dispatch }
+
+ assert_equal "Status: 400 Bad Request\r\nContent-Type: text/html\r\n\r\n
400 Bad Request
", @output.string
+ end
+
+ def test_prepare_callbacks
+ a = b = c = nil
+ Dispatcher.to_prepare { |*args| a = b = c = 1 }
+ Dispatcher.to_prepare { |*args| b = c = 2 }
+ Dispatcher.to_prepare { |*args| c = 3 }
+
+ # Ensure to_prepare callbacks are not run when defined
+ assert_nil a || b || c
+
+ # Run callbacks
+ @dispatcher.send :run_callbacks, :prepare_dispatch
+
+ assert_equal 1, a
+ assert_equal 2, b
+ assert_equal 3, c
+
+ # Make sure they are only run once
+ a = b = c = nil
+ @dispatcher.send :dispatch
+ assert_nil a || b || c
+ end
+
+ def test_to_prepare_with_identifier_replaces
+ a = b = nil
+ Dispatcher.to_prepare(:unique_id) { |*args| a = b = 1 }
+ Dispatcher.to_prepare(:unique_id) { |*args| a = 2 }
+
+ @dispatcher.send :run_callbacks, :prepare_dispatch
+ assert_equal 2, a
+ assert_equal nil, b
+ end
+
+ private
+ def dispatch(output = @output, cache_classes = true)
+ controller = mock
+ controller.stubs(:process).returns(controller)
+ controller.stubs(:out).with(output).returns('response')
+
+ ActionController::Routing::Routes.stubs(:recognize).returns(controller)
+
+ Dispatcher.define_dispatcher_callbacks(cache_classes)
+ Dispatcher.dispatch(nil, {}, output)
+ end
+
+ def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
+ assert_equal howmany, klass.subclasses.size, message
+ end
+end
+
+end
diff --git a/vendor/rails/actionpack/test/controller/fake_controllers.rb b/vendor/rails/actionpack/test/controller/fake_controllers.rb
new file mode 100644
index 0000000..75c114c
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/fake_controllers.rb
@@ -0,0 +1,33 @@
+class << Object; alias_method :const_available?, :const_defined?; end
+
+class ContentController < Class.new(ActionController::Base)
+end
+class NotAController
+end
+module Admin
+ class << self; alias_method :const_available?, :const_defined?; end
+ class UserController < Class.new(ActionController::Base); end
+ class NewsFeedController < Class.new(ActionController::Base); end
+end
+
+# For speed test
+class SpeedController < ActionController::Base; end
+class SearchController < SpeedController; end
+class VideosController < SpeedController; end
+class VideoFileController < SpeedController; end
+class VideoSharesController < SpeedController; end
+class VideoAbusesController < SpeedController; end
+class VideoUploadsController < SpeedController; end
+class VideoVisitsController < SpeedController; end
+class UsersController < SpeedController; end
+class SettingsController < SpeedController; end
+class ChannelsController < SpeedController; end
+class ChannelVideosController < SpeedController; end
+class SessionsController < SpeedController; end
+class LostPasswordsController < SpeedController; end
+class PagesController < SpeedController; end
+
+ActionController::Routing::Routes.draw do |map|
+ map.route_one 'route_one', :controller => 'elsewhere', :action => 'flash_me'
+ map.connect ':controller/:action/:id'
+end
diff --git a/vendor/rails/actionpack/test/controller/fake_models.rb b/vendor/rails/actionpack/test/controller/fake_models.rb
new file mode 100644
index 0000000..7420579
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/fake_models.rb
@@ -0,0 +1,11 @@
+class Customer < Struct.new(:name, :id)
+ def to_param
+ id.to_s
+ end
+end
+
+class BadCustomer < Customer
+end
+
+class GoodCustomer < Customer
+end
diff --git a/vendor/rails/actionpack/test/controller/filter_params_test.rb b/vendor/rails/actionpack/test/controller/filter_params_test.rb
new file mode 100644
index 0000000..c4de101
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/filter_params_test.rb
@@ -0,0 +1,49 @@
+require 'abstract_unit'
+
+class FilterParamController < ActionController::Base
+end
+
+class FilterParamTest < Test::Unit::TestCase
+ def setup
+ @controller = FilterParamController.new
+ end
+
+ def test_filter_parameters
+ assert FilterParamController.respond_to?(:filter_parameter_logging)
+ assert !@controller.respond_to?(:filter_parameters)
+
+ FilterParamController.filter_parameter_logging
+ assert @controller.respond_to?(:filter_parameters)
+
+ test_hashes = [[{},{},[]],
+ [{'foo'=>nil},{'foo'=>nil},[]],
+ [{'foo'=>'bar'},{'foo'=>'bar'},[]],
+ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'],
+ [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'],
+ [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
+ [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
+ [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
+ [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
+
+ test_hashes.each do |before_filter, after_filter, filter_words|
+ FilterParamController.filter_parameter_logging(*filter_words)
+ assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
+
+ filter_words.push('blah')
+ FilterParamController.filter_parameter_logging(*filter_words) do |key, value|
+ value.reverse! if key =~ /bargain/
+ end
+
+ before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}}
+ after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}}
+
+ assert_equal after_filter, @controller.send!(:filter_parameters, before_filter)
+ end
+ end
+
+ def test_filter_parameters_is_protected
+ FilterParamController.filter_parameter_logging(:foo)
+ assert !FilterParamController.action_methods.include?('filter_parameters')
+ assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) }
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/filters_test.rb b/vendor/rails/actionpack/test/controller/filters_test.rb
new file mode 100644
index 0000000..3652c48
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/filters_test.rb
@@ -0,0 +1,881 @@
+require 'abstract_unit'
+
+# FIXME: crashes Ruby 1.9
+class FilterTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ before_filter :ensure_login
+ after_filter :clean_up
+
+ def show
+ render :inline => "ran action"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up
+ @ran_after_filter ||= []
+ @ran_after_filter << "clean_up"
+ end
+ end
+
+ class ChangingTheRequirementsController < TestController
+ before_filter :ensure_login, :except => [:go_wild]
+
+ def go_wild
+ render :text => "gobble"
+ end
+ end
+
+ class TestMultipleFiltersController < ActionController::Base
+ before_filter :try_1
+ before_filter :try_2
+ before_filter :try_3
+
+ (1..3).each do |i|
+ define_method "fail_#{i}" do
+ render :text => i.to_s
+ end
+ end
+
+ protected
+ (1..3).each do |i|
+ define_method "try_#{i}" do
+ instance_variable_set :@try, i
+ if action_name == "fail_#{i}"
+ head(404)
+ end
+ end
+ end
+ end
+
+ class RenderingController < ActionController::Base
+ before_filter :render_something_else
+
+ def show
+ @ran_action = true
+ render :inline => "ran action"
+ end
+
+ private
+ def render_something_else
+ render :inline => "something else"
+ end
+ end
+
+ class ConditionalFilterController < ActionController::Base
+ def show
+ render :inline => "ran action"
+ end
+
+ def another_action
+ render :inline => "ran action"
+ end
+
+ def show_without_filter
+ render :inline => "ran action without filter"
+ end
+
+ private
+ def ensure_login
+ @ran_filter ||= []
+ @ran_filter << "ensure_login"
+ end
+
+ def clean_up_tmp
+ @ran_filter ||= []
+ @ran_filter << "clean_up_tmp"
+ end
+
+ def rescue_action(e) raise(e) end
+ end
+
+ class ConditionalCollectionFilterController < ConditionalFilterController
+ before_filter :ensure_login, :except => [ :show_without_filter, :another_action ]
+ end
+
+ class OnlyConditionSymController < ConditionalFilterController
+ before_filter :ensure_login, :only => :show
+ end
+
+ class ExceptConditionSymController < ConditionalFilterController
+ before_filter :ensure_login, :except => :show_without_filter
+ end
+
+ class BeforeAndAfterConditionController < ConditionalFilterController
+ before_filter :ensure_login, :only => :show
+ after_filter :clean_up_tmp, :only => :show
+ end
+
+ class OnlyConditionProcController < ConditionalFilterController
+ before_filter(:only => :show) {|c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class ExceptConditionProcController < ConditionalFilterController
+ before_filter(:except => :show_without_filter) {|c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class ConditionalClassFilter
+ def self.filter(controller) controller.assigns["ran_class_filter"] = true end
+ end
+
+ class OnlyConditionClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter, :only => :show
+ end
+
+ class ExceptConditionClassController < ConditionalFilterController
+ before_filter ConditionalClassFilter, :except => :show_without_filter
+ end
+
+ class AnomolousYetValidConditionController < ConditionalFilterController
+ before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.assigns["ran_proc_filter1"] = true }, :except => :show_without_filter) { |c| c.assigns["ran_proc_filter2"] = true}
+ end
+
+ class ConditionalOptionsFilter < ConditionalFilterController
+ before_filter :ensure_login, :if => Proc.new { |c| true }
+ before_filter :clean_up_tmp, :if => Proc.new { |c| false }
+ end
+
+ class EmptyFilterChainController < TestController
+ self.filter_chain.clear
+ def show
+ @action_executed = true
+ render :text => "yawp!"
+ end
+ end
+
+ class PrependingController < TestController
+ prepend_before_filter :wonderful_life
+ # skip_before_filter :fire_flash
+
+ private
+ def wonderful_life
+ @ran_filter ||= []
+ @ran_filter << "wonderful_life"
+ end
+ end
+
+ class SkippingAndLimitedController < TestController
+ skip_before_filter :ensure_login
+ before_filter :ensure_login, :only => :index
+
+ def index
+ render :text => 'ok'
+ end
+
+ def public
+ end
+ end
+
+ class SkippingAndReorderingController < TestController
+ skip_before_filter :ensure_login
+ before_filter :find_record
+ before_filter :ensure_login
+
+ private
+ def find_record
+ @ran_filter ||= []
+ @ran_filter << "find_record"
+ end
+ end
+
+ class ConditionalSkippingController < TestController
+ skip_before_filter :ensure_login, :only => [ :login ]
+ skip_after_filter :clean_up, :only => [ :login ]
+
+ before_filter :find_user, :only => [ :change_password ]
+
+ def login
+ render :inline => "ran action"
+ end
+
+ def change_password
+ render :inline => "ran action"
+ end
+
+ protected
+ def find_user
+ @ran_filter ||= []
+ @ran_filter << "find_user"
+ end
+ end
+
+ class ConditionalParentOfConditionalSkippingController < ConditionalFilterController
+ before_filter :conditional_in_parent, :only => [:show, :another_action]
+ after_filter :conditional_in_parent, :only => [:show, :another_action]
+
+ private
+
+ def conditional_in_parent
+ @ran_filter ||= []
+ @ran_filter << 'conditional_in_parent'
+ end
+ end
+
+ class ChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_filter :conditional_in_parent, :only => :another_action
+ skip_after_filter :conditional_in_parent, :only => :another_action
+ end
+
+ class AnotherChildOfConditionalParentController < ConditionalParentOfConditionalSkippingController
+ skip_before_filter :conditional_in_parent, :only => :show
+ end
+
+ class ProcController < PrependingController
+ before_filter(proc { |c| c.assigns["ran_proc_filter"] = true })
+ end
+
+ class ImplicitProcController < PrependingController
+ before_filter { |c| c.assigns["ran_proc_filter"] = true }
+ end
+
+ class AuditFilter
+ def self.filter(controller)
+ controller.assigns["was_audited"] = true
+ end
+ end
+
+ class AroundFilter
+ def before(controller)
+ @execution_log = "before"
+ controller.class.execution_log << " before aroundfilter " if controller.respond_to? :execution_log
+ controller.assigns["before_ran"] = true
+ end
+
+ def after(controller)
+ controller.assigns["execution_log"] = @execution_log + " and after"
+ controller.assigns["after_ran"] = true
+ controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log
+ end
+ end
+
+ class AppendedAroundFilter
+ def before(controller)
+ controller.class.execution_log << " before appended aroundfilter "
+ end
+
+ def after(controller)
+ controller.class.execution_log << " after appended aroundfilter "
+ end
+ end
+
+ class AuditController < ActionController::Base
+ before_filter(AuditFilter)
+
+ def show
+ render :text => "hello"
+ end
+ end
+
+ class AroundFilterController < PrependingController
+ around_filter AroundFilter.new
+ end
+
+ class BeforeAfterClassFilterController < PrependingController
+ begin
+ filter = AroundFilter.new
+ before_filter filter
+ after_filter filter
+ end
+ end
+
+ class MixedFilterController < PrependingController
+ cattr_accessor :execution_log
+
+ def initialize
+ @@execution_log = ""
+ end
+
+ before_filter { |c| c.class.execution_log << " before procfilter " }
+ prepend_around_filter AroundFilter.new
+
+ after_filter { |c| c.class.execution_log << " after procfilter " }
+ append_around_filter AppendedAroundFilter.new
+ end
+
+ class MixedSpecializationController < ActionController::Base
+ class OutOfOrder < StandardError; end
+
+ before_filter :first
+ before_filter :second, :only => :foo
+
+ def foo
+ render :text => 'foo'
+ end
+
+ def bar
+ render :text => 'bar'
+ end
+
+ protected
+ def first
+ @first = true
+ end
+
+ def second
+ raise OutOfOrder unless @first
+ end
+ end
+
+ class DynamicDispatchController < ActionController::Base
+ before_filter :choose
+
+ %w(foo bar baz).each do |action|
+ define_method(action) { render :text => action }
+ end
+
+ private
+ def choose
+ self.action_name = params[:choose]
+ end
+ end
+
+ class PrependingBeforeAndAfterController < ActionController::Base
+ prepend_before_filter :before_all
+ prepend_after_filter :after_all
+ before_filter :between_before_all_and_after_all
+
+ def before_all
+ @ran_filter ||= []
+ @ran_filter << 'before_all'
+ end
+
+ def after_all
+ @ran_filter ||= []
+ @ran_filter << 'after_all'
+ end
+
+ def between_before_all_and_after_all
+ @ran_filter ||= []
+ @ran_filter << 'between_before_all_and_after_all'
+ end
+ def show
+ render :text => 'hello'
+ end
+ end
+
+ class ErrorToRescue < Exception; end
+
+ class RescuingAroundFilterWithBlock
+ def filter(controller)
+ begin
+ yield
+ rescue ErrorToRescue => ex
+ controller.send! :render, :text => "I rescued this: #{ex.inspect}"
+ end
+ end
+ end
+
+ class RescuedController < ActionController::Base
+ around_filter RescuingAroundFilterWithBlock.new
+
+ def show
+ raise ErrorToRescue.new("Something made the bad noise.")
+ end
+
+ private
+ def rescue_action(exception)
+ raise exception
+ end
+ end
+
+ class NonYieldingAroundFilterController < ActionController::Base
+
+ before_filter :filter_one
+ around_filter :non_yielding_filter
+ before_filter :filter_two
+ after_filter :filter_three
+
+ def index
+ render :inline => "index"
+ end
+
+ #make sure the controller complains
+ def rescue_action(e); raise e; end
+
+ private
+
+ def filter_one
+ @filters ||= []
+ @filters << "filter_one"
+ end
+
+ def filter_two
+ @filters << "filter_two"
+ end
+
+ def non_yielding_filter
+ @filters << "zomg it didn't yield"
+ @filter_return_value
+ end
+
+ def filter_three
+ @filters << "filter_three"
+ end
+
+ end
+
+ def test_non_yielding_around_filters_not_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_non_yielding_around_filters_returning_false_do_not_raise
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ assert_nothing_raised do
+ test_process(controller, "index")
+ end
+ end
+
+ def test_after_filters_are_not_run_if_around_filter_returns_false
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", false
+ test_process(controller, "index")
+ assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_after_filters_are_not_run_if_around_filter_does_not_yield
+ controller = NonYieldingAroundFilterController.new
+ controller.instance_variable_set "@filter_return_value", true
+ test_process(controller, "index")
+ assert_equal ["filter_one", "zomg it didn't yield"], controller.assigns['filters']
+ end
+
+ def test_empty_filter_chain
+ assert_equal 0, EmptyFilterChainController.filter_chain.size
+ assert test_process(EmptyFilterChainController).template.assigns['action_executed']
+ end
+
+ def test_added_filter_to_inheritance_graph
+ assert_equal [ :ensure_login ], TestController.before_filters
+ end
+
+ def test_base_class_in_isolation
+ assert_equal [ ], ActionController::Base.before_filters
+ end
+
+ def test_prepending_filter
+ assert_equal [ :wonderful_life, :ensure_login ], PrependingController.before_filters
+ end
+
+ def test_running_filters
+ assert_equal %w( wonderful_life ensure_login ), test_process(PrependingController).template.assigns["ran_filter"]
+ end
+
+ def test_running_filters_with_proc
+ assert test_process(ProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_implicit_proc
+ assert test_process(ImplicitProcController).template.assigns["ran_proc_filter"]
+ end
+
+ def test_running_filters_with_class
+ assert test_process(AuditController).template.assigns["was_audited"]
+ end
+
+ def test_running_anomolous_yet_valid_condition_filters
+ response = test_process(AnomolousYetValidConditionController)
+ assert_equal %w( ensure_login ), response.template.assigns["ran_filter"]
+ assert response.template.assigns["ran_class_filter"]
+ assert response.template.assigns["ran_proc_filter1"]
+ assert response.template.assigns["ran_proc_filter2"]
+
+ response = test_process(AnomolousYetValidConditionController, "show_without_filter")
+ assert_equal nil, response.template.assigns["ran_filter"]
+ assert !response.template.assigns["ran_class_filter"]
+ assert !response.template.assigns["ran_proc_filter1"]
+ assert !response.template.assigns["ran_proc_filter2"]
+ end
+
+ def test_running_conditional_options
+ response = test_process(ConditionalOptionsFilter)
+ assert_equal %w( ensure_login ), response.template.assigns["ran_filter"]
+ end
+
+ def test_running_collection_condition_filters
+ assert_equal %w( ensure_login ), test_process(ConditionalCollectionFilterController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(ConditionalCollectionFilterController, "show_without_filter").template.assigns["ran_filter"]
+ assert_equal nil, test_process(ConditionalCollectionFilterController, "another_action").template.assigns["ran_filter"]
+ end
+
+ def test_running_only_condition_filters
+ assert_equal %w( ensure_login ), test_process(OnlyConditionSymController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(OnlyConditionSymController, "show_without_filter").template.assigns["ran_filter"]
+
+ assert test_process(OnlyConditionProcController).template.assigns["ran_proc_filter"]
+ assert !test_process(OnlyConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
+
+ assert test_process(OnlyConditionClassController).template.assigns["ran_class_filter"]
+ assert !test_process(OnlyConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
+ end
+
+ def test_running_except_condition_filters
+ assert_equal %w( ensure_login ), test_process(ExceptConditionSymController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(ExceptConditionSymController, "show_without_filter").template.assigns["ran_filter"]
+
+ assert test_process(ExceptConditionProcController).template.assigns["ran_proc_filter"]
+ assert !test_process(ExceptConditionProcController, "show_without_filter").template.assigns["ran_proc_filter"]
+
+ assert test_process(ExceptConditionClassController).template.assigns["ran_class_filter"]
+ assert !test_process(ExceptConditionClassController, "show_without_filter").template.assigns["ran_class_filter"]
+ end
+
+ def test_running_before_and_after_condition_filters
+ assert_equal %w( ensure_login clean_up_tmp), test_process(BeforeAndAfterConditionController).template.assigns["ran_filter"]
+ assert_equal nil, test_process(BeforeAndAfterConditionController, "show_without_filter").template.assigns["ran_filter"]
+ end
+
+ def test_around_filter
+ controller = test_process(AroundFilterController)
+ assert controller.template.assigns["before_ran"]
+ assert controller.template.assigns["after_ran"]
+ end
+
+ def test_before_after_class_filter
+ controller = test_process(BeforeAfterClassFilterController)
+ assert controller.template.assigns["before_ran"]
+ assert controller.template.assigns["after_ran"]
+ end
+
+ def test_having_properties_in_around_filter
+ controller = test_process(AroundFilterController)
+ assert_equal "before and after", controller.template.assigns["execution_log"]
+ end
+
+ def test_prepending_and_appending_around_filter
+ controller = test_process(MixedFilterController)
+ assert_equal " before aroundfilter before procfilter before appended aroundfilter " +
+ " after appended aroundfilter after aroundfilter after procfilter ",
+ MixedFilterController.execution_log
+ end
+
+ def test_rendering_breaks_filtering_chain
+ response = test_process(RenderingController)
+ assert_equal "something else", response.body
+ assert !response.template.assigns["ran_action"]
+ end
+
+ def test_filters_with_mixed_specialization_run_in_order
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'bar')
+ assert_equal 'bar', response.body
+ end
+
+ assert_nothing_raised do
+ response = test_process(MixedSpecializationController, 'foo')
+ assert_equal 'foo', response.body
+ end
+ end
+
+ def test_dynamic_dispatch
+ %w(foo bar baz).each do |action|
+ request = ActionController::TestRequest.new
+ request.query_parameters[:choose] = action
+ response = DynamicDispatchController.process(request, ActionController::TestResponse.new)
+ assert_equal action, response.body
+ end
+ end
+
+ def test_running_prepended_before_and_after_filter
+ assert_equal 3, PrependingBeforeAndAfterController.filter_chain.length
+ response = test_process(PrependingBeforeAndAfterController)
+ assert_equal %w( before_all between_before_all_and_after_all after_all ), response.template.assigns["ran_filter"]
+ end
+
+ def test_skipping_and_limiting_controller
+ assert_equal %w( ensure_login ), test_process(SkippingAndLimitedController, "index").template.assigns["ran_filter"]
+ assert_nil test_process(SkippingAndLimitedController, "public").template.assigns["ran_filter"]
+ end
+
+ def test_skipping_and_reordering_controller
+ assert_equal %w( find_record ensure_login ), test_process(SkippingAndReorderingController, "index").template.assigns["ran_filter"]
+ end
+
+ def test_conditional_skipping_of_filters
+ assert_nil test_process(ConditionalSkippingController, "login").template.assigns["ran_filter"]
+ assert_equal %w( ensure_login find_user ), test_process(ConditionalSkippingController, "change_password").template.assigns["ran_filter"]
+
+ assert_nil test_process(ConditionalSkippingController, "login").template.controller.instance_variable_get("@ran_after_filter")
+ assert_equal %w( clean_up ), test_process(ConditionalSkippingController, "change_password").template.controller.instance_variable_get("@ran_after_filter")
+ end
+
+ def test_conditional_skipping_of_filters_when_parent_filter_is_also_conditional
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
+ assert_nil test_process(ChildOfConditionalParentController, 'another_action').template.assigns['ran_filter']
+ end
+
+ def test_condition_skipping_of_filters_when_siblings_also_have_conditions
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter'], "1"
+ assert_equal nil, test_process(AnotherChildOfConditionalParentController).template.assigns['ran_filter']
+ assert_equal %w( conditional_in_parent conditional_in_parent ), test_process(ChildOfConditionalParentController).template.assigns['ran_filter']
+ end
+
+ def test_changing_the_requirements
+ assert_equal nil, test_process(ChangingTheRequirementsController, "go_wild").template.assigns['ran_filter']
+ end
+
+ def test_a_rescuing_around_filter
+ response = nil
+ assert_nothing_raised do
+ response = test_process(RescuedController)
+ end
+
+ assert response.success?
+ assert_equal("I rescued this: #", response.body)
+ end
+
+ private
+ def test_process(controller, action = "show")
+ request = ActionController::TestRequest.new
+ request.action = action
+ controller.process(request, ActionController::TestResponse.new)
+ end
+end
+
+
+
+class PostsController < ActionController::Base
+ def rescue_action(e); raise e; end
+
+ module AroundExceptions
+ class Error < StandardError ; end
+ class Before < Error ; end
+ class After < Error ; end
+ end
+ include AroundExceptions
+
+ class DefaultFilter
+ include AroundExceptions
+ end
+
+ module_eval %w(raises_before raises_after raises_both no_raise no_filter).map { |action| "def #{action}; default_action end" }.join("\n")
+
+ private
+ def default_action
+ render :inline => "#{action_name} called"
+ end
+end
+
+class ControllerWithSymbolAsFilter < PostsController
+ around_filter :raise_before, :only => :raises_before
+ around_filter :raise_after, :only => :raises_after
+ around_filter :without_exception, :only => :no_raise
+
+ private
+ def raise_before
+ raise Before
+ yield
+ end
+
+ def raise_after
+ yield
+ raise After
+ end
+
+ def without_exception
+ # Do stuff...
+ 1 + 1
+
+ yield
+
+ # Do stuff...
+ 1 + 1
+ end
+end
+
+class ControllerWithFilterClass < PostsController
+ class YieldingFilter < DefaultFilter
+ def self.filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter, :only => :raises_after
+end
+
+class ControllerWithFilterInstance < PostsController
+ class YieldingFilter < DefaultFilter
+ def filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter.new, :only => :raises_after
+end
+
+class ControllerWithFilterMethod < PostsController
+ class YieldingFilter < DefaultFilter
+ def filter(controller)
+ yield
+ raise After
+ end
+ end
+
+ around_filter YieldingFilter.new.method(:filter), :only => :raises_after
+end
+
+class ControllerWithProcFilter < PostsController
+ around_filter(:only => :no_raise) do |c,b|
+ c.assigns['before'] = true
+ b.call
+ c.assigns['after'] = true
+ end
+end
+
+class ControllerWithNestedFilters < ControllerWithSymbolAsFilter
+ around_filter :raise_before, :raise_after, :without_exception, :only => :raises_both
+end
+
+class ControllerWithAllTypesOfFilters < PostsController
+ before_filter :before
+ around_filter :around
+ after_filter :after
+ around_filter :around_again
+
+ private
+ def before
+ @ran_filter ||= []
+ @ran_filter << 'before'
+ end
+
+ def around
+ @ran_filter << 'around (before yield)'
+ yield
+ @ran_filter << 'around (after yield)'
+ end
+
+ def after
+ @ran_filter << 'after'
+ end
+
+ def around_again
+ @ran_filter << 'around_again (before yield)'
+ yield
+ @ran_filter << 'around_again (after yield)'
+ end
+end
+
+class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters
+ skip_filter :around_again
+ skip_filter :after
+end
+
+class YieldingAroundFiltersTest < Test::Unit::TestCase
+ include PostsController::AroundExceptions
+
+ def test_filters_registering
+ assert_equal 1, ControllerWithFilterMethod.filter_chain.size
+ assert_equal 1, ControllerWithFilterClass.filter_chain.size
+ assert_equal 1, ControllerWithFilterInstance.filter_chain.size
+ assert_equal 3, ControllerWithSymbolAsFilter.filter_chain.size
+ assert_equal 6, ControllerWithNestedFilters.filter_chain.size
+ assert_equal 4, ControllerWithAllTypesOfFilters.filter_chain.size
+ end
+
+ def test_base
+ controller = PostsController
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_nothing_raised { test_process(controller,'raises_before') }
+ assert_nothing_raised { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_filter') }
+ end
+
+ def test_with_symbol
+ controller = ControllerWithSymbolAsFilter
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(Before) { test_process(controller,'raises_before') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ end
+
+ def test_with_class
+ controller = ControllerWithFilterClass
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_instance
+ controller = ControllerWithFilterInstance
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_method
+ controller = ControllerWithFilterMethod
+ assert_nothing_raised { test_process(controller,'no_raise') }
+ assert_raise(After) { test_process(controller,'raises_after') }
+ end
+
+ def test_with_proc
+ controller = test_process(ControllerWithProcFilter,'no_raise')
+ assert controller.template.assigns['before']
+ assert controller.template.assigns['after']
+ end
+
+ def test_nested_filters
+ controller = ControllerWithNestedFilters
+ assert_nothing_raised do
+ begin
+ test_process(controller,'raises_both')
+ rescue Before, After
+ end
+ end
+ assert_raise Before do
+ begin
+ test_process(controller,'raises_both')
+ rescue After
+ end
+ end
+ end
+
+ def test_filter_order_with_all_filter_types
+ controller = test_process(ControllerWithAllTypesOfFilters,'no_raise')
+ assert_equal 'before around (before yield) around_again (before yield) around_again (after yield) around (after yield) after',controller.template.assigns['ran_filter'].join(' ')
+ end
+
+ def test_filter_order_with_skip_filter_method
+ controller = test_process(ControllerWithTwoLessFilters,'no_raise')
+ assert_equal 'before around (before yield) around (after yield)',controller.template.assigns['ran_filter'].join(' ')
+ end
+
+ def test_first_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_1')
+ assert_equal ' ', response.body
+ assert_equal 1, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ def test_second_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_2')
+ assert_equal ' ', response.body
+ assert_equal 2, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ def test_last_filter_in_multiple_before_filter_chain_halts
+ controller = ::FilterTest::TestMultipleFiltersController.new
+ response = test_process(controller, 'fail_3')
+ assert_equal ' ', response.body
+ assert_equal 3, controller.instance_variable_get(:@try)
+ assert controller.instance_variable_get(:@before_filter_chain_aborted)
+ end
+
+ protected
+ def test_process(controller, action = "show")
+ request = ActionController::TestRequest.new
+ request.action = action
+ controller.process(request, ActionController::TestResponse.new)
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/flash_test.rb b/vendor/rails/actionpack/test/controller/flash_test.rb
new file mode 100644
index 0000000..e562531
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/flash_test.rb
@@ -0,0 +1,146 @@
+require 'abstract_unit'
+
+class FlashTest < Test::Unit::TestCase
+ class TestController < ActionController::Base
+ def set_flash
+ flash["that"] = "hello"
+ render :inline => "hello"
+ end
+
+ def set_flash_now
+ flash.now["that"] = "hello"
+ flash.now["foo"] ||= "bar"
+ flash.now["foo"] ||= "err"
+ @flashy = flash.now["that"]
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def attempt_to_use_flash_now
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ render :inline => "hello"
+ end
+
+ def use_flash_and_keep_it
+ @flash_copy = {}.update flash
+ @flashy = flash["that"]
+ flash.keep
+ render :inline => "hello"
+ end
+
+ def use_flash_and_update_it
+ flash.update("this" => "hello again")
+ @flash_copy = {}.update flash
+ render :inline => "hello"
+ end
+
+ def use_flash_after_reset_session
+ flash["that"] = "hello"
+ @flashy_that = flash["that"]
+ reset_session
+ @flashy_that_reset = flash["that"]
+ flash["this"] = "good-bye"
+ @flashy_this = flash["this"]
+ render :inline => "hello"
+ end
+
+ def rescue_action(e)
+ raise unless ActionView::MissingTemplate === e
+ end
+
+ # methods for test_sweep_after_halted_filter_chain
+ before_filter :halt_and_redir, :only => "filter_halting_action"
+
+ def std_action
+ @flash_copy = {}.update(flash)
+ end
+
+ def filter_halting_action
+ @flash_copy = {}.update(flash)
+ end
+
+ def halt_and_redir
+ flash["foo"] = "bar"
+ redirect_to :action => "std_action"
+ @flash_copy = {}.update(flash)
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TestController.new
+ end
+
+ def test_flash
+ get :set_flash
+
+ get :use_flash
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
+ end
+
+ def test_keep_flash
+ get :set_flash
+
+ get :use_flash_and_keep_it
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :use_flash
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"], "On second flash"
+
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On third flash"
+ end
+
+ def test_flash_now
+ get :set_flash_now
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "bar" , @response.template.assigns["flash_copy"]["foo"]
+ assert_equal "hello", @response.template.assigns["flashy"]
+
+ get :attempt_to_use_flash_now
+ assert_nil @response.template.assigns["flash_copy"]["that"]
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ assert_nil @response.template.assigns["flashy"]
+ end
+
+ def test_update_flash
+ get :set_flash
+ get :use_flash_and_update_it
+ assert_equal "hello", @response.template.assigns["flash_copy"]["that"]
+ assert_equal "hello again", @response.template.assigns["flash_copy"]["this"]
+ get :use_flash
+ assert_nil @response.template.assigns["flash_copy"]["that"], "On second flash"
+ assert_equal "hello again", @response.template.assigns["flash_copy"]["this"], "On second flash"
+ end
+
+ def test_flash_after_reset_session
+ get :use_flash_after_reset_session
+ assert_equal "hello", @response.template.assigns["flashy_that"]
+ assert_equal "good-bye", @response.template.assigns["flashy_this"]
+ assert_nil @response.template.assigns["flashy_that_reset"]
+ end
+
+ def test_sweep_after_halted_filter_chain
+ get :std_action
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ get :filter_halting_action
+ assert_equal "bar", @response.template.assigns["flash_copy"]["foo"]
+ get :std_action # follow redirection
+ assert_equal "bar", @response.template.assigns["flash_copy"]["foo"]
+ get :std_action
+ assert_nil @response.template.assigns["flash_copy"]["foo"]
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/header_test.rb b/vendor/rails/actionpack/test/controller/header_test.rb
new file mode 100644
index 0000000..33c14a1
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/header_test.rb
@@ -0,0 +1,14 @@
+require 'abstract_unit'
+
+class HeaderTest < Test::Unit::TestCase
+ def setup
+ @headers = ActionController::Http::Headers.new("HTTP_CONTENT_TYPE"=>"text/plain")
+ end
+
+ def test_content_type_works
+ assert_equal "text/plain", @headers["Content-Type"]
+ assert_equal "text/plain", @headers["content-type"]
+ assert_equal "text/plain", @headers["CONTENT_TYPE"]
+ assert_equal "text/plain", @headers["HTTP_CONTENT_TYPE"]
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/helper_test.rb b/vendor/rails/actionpack/test/controller/helper_test.rb
new file mode 100644
index 0000000..83e3b08
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/helper_test.rb
@@ -0,0 +1,210 @@
+require 'abstract_unit'
+
+ActionController::Helpers::HELPERS_DIR.replace File.dirname(__FILE__) + '/../fixtures/helpers'
+
+class TestController < ActionController::Base
+ attr_accessor :delegate_attr
+ def delegate_method() end
+ def rescue_action(e) raise end
+end
+
+module Fun
+ class GamesController < ActionController::Base
+ def render_hello_world
+ render :inline => "hello: <%= stratego %>"
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ class PdfController < ActionController::Base
+ def test
+ render :inline => "test: <%= foobar %>"
+ end
+
+ def rescue_action(e) raise end
+ end
+end
+
+class ApplicationController < ActionController::Base
+ helper :all
+end
+
+module LocalAbcHelper
+ def a() end
+ def b() end
+ def c() end
+end
+
+class HelperTest < Test::Unit::TestCase
+ def setup
+ # Increment symbol counter.
+ @symbol = (@@counter ||= 'A0').succ!.dup
+
+ # Generate new controller class.
+ controller_class_name = "Helper#{@symbol}Controller"
+ eval("class #{controller_class_name} < TestController; end")
+ @controller_class = self.class.const_get(controller_class_name)
+
+ # Set default test helper.
+ self.test_helper = LocalAbcHelper
+ end
+
+ def test_deprecated_helper
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper TestHelper }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_helper
+ require 'abc_helper'
+ self.test_helper = AbcHelper
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised { @controller_class.helper :abc }
+ assert_equal [], missing_methods
+ end
+
+ def test_declare_missing_helper
+ assert_equal expected_helper_methods, missing_methods
+ assert_raise(MissingSourceFile) { @controller_class.helper :missing }
+ end
+
+ def test_declare_missing_file_from_helper
+ require 'broken_helper'
+ rescue LoadError => e
+ assert_nil(/\bbroken_helper\b/.match(e.to_s)[1])
+ end
+
+ def test_helper_block
+ assert_nothing_raised {
+ @controller_class.helper { def block_helper_method; end }
+ }
+ assert master_helper_methods.include?('block_helper_method')
+ end
+
+ def test_helper_block_include
+ assert_equal expected_helper_methods, missing_methods
+ assert_nothing_raised {
+ @controller_class.helper { include HelperTest::TestHelper }
+ }
+ assert [], missing_methods
+ end
+
+ def test_helper_method
+ assert_nothing_raised { @controller_class.helper_method :delegate_method }
+ assert master_helper_methods.include?('delegate_method')
+ end
+
+ def test_helper_attr
+ assert_nothing_raised { @controller_class.helper_attr :delegate_attr }
+ assert master_helper_methods.include?('delegate_attr')
+ assert master_helper_methods.include?('delegate_attr=')
+ end
+
+ def test_helper_for_nested_controller
+ request = ActionController::TestRequest.new
+ response = ActionController::TestResponse.new
+ request.action = 'render_hello_world'
+
+ assert_equal 'hello: Iz guuut!', Fun::GamesController.process(request, response).body
+ end
+
+ def test_helper_for_acronym_controller
+ request = ActionController::TestRequest.new
+ response = ActionController::TestResponse.new
+ request.action = 'test'
+
+ assert_equal 'test: baz', Fun::PdfController.process(request, response).body
+ end
+
+ def test_all_helpers
+ methods = ApplicationController.master_helper_module.instance_methods.map(&:to_s)
+
+ # abc_helper.rb
+ assert methods.include?('bare_a')
+
+ # fun/games_helper.rb
+ assert methods.include?('stratego')
+
+ # fun/pdf_helper.rb
+ assert methods.include?('foobar')
+ end
+
+ def test_helper_proxy
+ methods = ApplicationController.helpers.methods.map(&:to_s)
+
+ # ActionView
+ assert methods.include?('pluralize')
+
+ # abc_helper.rb
+ assert methods.include?('bare_a')
+
+ # fun/games_helper.rb
+ assert methods.include?('stratego')
+
+ # fun/pdf_helper.rb
+ assert methods.include?('foobar')
+ end
+
+ private
+ def expected_helper_methods
+ TestHelper.instance_methods.map(&:to_s)
+ end
+
+ def master_helper_methods
+ @controller_class.master_helper_module.instance_methods.map(&:to_s)
+ end
+
+ def missing_methods
+ expected_helper_methods - master_helper_methods
+ end
+
+ def test_helper=(helper_module)
+ silence_warnings { self.class.const_set('TestHelper', helper_module) }
+ end
+end
+
+
+class IsolatedHelpersTest < Test::Unit::TestCase
+ class A < ActionController::Base
+ def index
+ render :inline => '<%= shout %>'
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ class B < A
+ helper { def shout; 'B' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ class C < A
+ helper { def shout; 'C' end }
+
+ def index
+ render :inline => '<%= shout %>'
+ end
+ end
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @request.action = 'index'
+ end
+
+ def test_helper_in_a
+ assert_raise(NameError) { A.process(@request, @response) }
+ end
+
+ def test_helper_in_b
+ assert_equal 'B', B.process(@request, @response).body
+ end
+
+ def test_helper_in_c
+ assert_equal 'C', C.process(@request, @response).body
+ end
+end
diff --git a/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb
new file mode 100644
index 0000000..0519533
--- /dev/null
+++ b/vendor/rails/actionpack/test/controller/html-scanner/document_test.rb
@@ -0,0 +1,123 @@
+require 'abstract_unit'
+
+class DocumentTest < Test::Unit::TestCase
+ def test_handle_doctype
+ doc = nil
+ assert_nothing_raised do
+ doc = HTML::Document.new <<-HTML.strip
+
+
+
+ HTML
+ end
+ assert_equal 3, doc.root.children.length
+ assert_equal %{}, doc.root.children[0].content
+ assert_match %r{\s+}m, doc.root.children[1].content
+ assert_equal "html", doc.root.children[2].name
+ end
+
+ def test_find_img
+ doc = HTML::Document.new <<-HTML.strip
+
+
+
+
+
+ HTML
+ assert doc.find(:tag=>"img", :attributes=>{"src"=>"hello.gif"})
+ end
+
+ def test_find_all
+ doc = HTML::Document.new <<-HTML.strip
+
+
+
+
"))
+ assert_equal("Weirdos", sanitizer.sanitize("Wei<a onclick='alert(document.cookie);'/>rdos"))
+ assert_equal("This is a test.", sanitizer.sanitize("This is a test."))
+ assert_equal(
+ %{This is a test.\n\n\nIt no longer contains any HTML.\n}, sanitizer.sanitize(
+ %{This is a test.\n\n\n\n
It no longer contains any HTML.
\n}))
+ assert_equal "This has a here.", sanitizer.sanitize("This has a here.")
+ [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
+ end
+
+ def test_strip_links
+ sanitizer = HTML::LinkSanitizer.new
+ assert_equal "Dont touch me", sanitizer.sanitize("Dont touch me")
+ assert_equal "on my mind\nall day long", sanitizer.sanitize("on my mind\nall day long")
+ assert_equal "0wn3d", sanitizer.sanitize("0wn3d")
+ assert_equal "Magic", sanitizer.sanitize("Magic")
+ assert_equal "FrrFox", sanitizer.sanitize("FrrFox")
+ assert_equal "My mind\nall day long", sanitizer.sanitize("My mind\nall day long")
+ assert_equal "all day long", sanitizer.sanitize("<a href='hello'>all day long</a>")
+
+ assert_equal "", ''
+ end
+
+ def test_sanitize_plaintext
+ raw = "foo"
+ assert_sanitized raw, "foo"
+ end
+
+ def test_sanitize_script
+ assert_sanitized "a b cd e f", "a b cd e f"
+ end
+
+ # fucked
+ def test_sanitize_js_handlers
+ raw = %{onthis="do that" hello}
+ assert_sanitized raw, %{onthis="do that" hello}
+ end
+
+ def test_sanitize_javascript_href
+ raw = %{href="javascript:bang" foo, bar}
+ assert_sanitized raw, %{href="javascript:bang" foo, bar}
+ end
+
+ def test_sanitize_image_src
+ raw = %{src="javascript:bang" foo, bar}
+ assert_sanitized raw, %{src="javascript:bang" foo, bar}
+ end
+
+ HTML::WhiteListSanitizer.allowed_tags.each do |tag_name|
+ define_method "test_should_allow_#{tag_name}_tag" do
+ assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo bar baz#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz#{tag_name}> end)
+ end
+ end
+
+ def test_should_allow_anchors
+ assert_sanitized %(), %()
+ end
+
+ # RFC 3986, sec 4.2
+ def test_allow_colons_in_path_component
+ assert_sanitized("foo")
+ end
+
+ %w(src width height alt).each do |img_attr|
+ define_method "test_should_allow_image_#{img_attr}_attribute" do
+ assert_sanitized %(), %()
+ end
+ end
+
+ def test_should_handle_non_html
+ assert_sanitized 'abc'
+ end
+
+ def test_should_handle_blank_text
+ assert_sanitized nil
+ assert_sanitized ''
+ end
+
+ def test_should_allow_custom_tags
+ text = "foo"
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_only_custom_tags
+ text = "foo with bar"
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal("foo with bar", sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_custom_tags_with_attributes
+ text = %(
foo
)
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text))
+ end
+
+ def test_should_allow_custom_tags_with_custom_attributes
+ text = %(
Lorem ipsum
)
+ sanitizer = HTML::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo']))
+ end
+
+ [%w(img src), %w(a href)].each do |(tag, attr)|
+ define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
+ assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo#{tag}>), %(<#{tag} title="1">boo#{tag}>)
+ end
+ end
+
+ def test_should_flag_bad_protocols
+ sanitizer = HTML::WhiteListSanitizer.new
+ %w(about chrome data disk hcp help javascript livescript lynxcgi lynxexec ms-help ms-its mhtml mocha opera res resource shell vbscript view-source vnd.ms.radio wysiwyg).each do |proto|
+ assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
+ end
+ end
+
+ def test_should_accept_good_protocols
+ sanitizer = HTML::WhiteListSanitizer.new
+ HTML::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://good")
+ end
+ end
+
+ def test_should_reject_hex_codes_in_protocol
+ assert_sanitized %(1), "1"
+ assert @sanitizer.send(:contains_bad_protocols?, 'src', "%6A%61%76%61%73%63%72%69%70%74%3A%61%6C%65%72%74%28%22%58%53%53%22%29")
+ end
+
+ def test_should_block_script_tag
+ assert_sanitized %(), ""
+ end
+
+ [%(),
+ %(),
+ %(),
+ %(">),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %(),
+ %()].each_with_index do |img_hack, i|
+ define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
+ assert_sanitized img_hack, ""
+ end
+ end
+
+ def test_should_sanitize_tag_broken_up_by_null
+ assert_sanitized %(alert(\"XSS\")), "alert(\"XSS\")"
+ end
+
+ def test_should_sanitize_invalid_script_tag
+ assert_sanitized %(), ""
+ end
+
+ def test_should_sanitize_script_tag_with_multiple_open_brackets
+ assert_sanitized %(<), "<"
+ assert_sanitized %(}
+ assert_end
+ end
+
+ def test_unterminated_comment
+ tokenize %{hello \n\n
") { |url| truncate(url, 10) }
+ end
+
+ def test_cycle_class
+ value = Cycle.new("one", 2, "3")
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ assert_equal("one", value.to_s)
+ value.reset
+ assert_equal("one", value.to_s)
+ assert_equal("2", value.to_s)
+ assert_equal("3", value.to_s)
+ end
+
+ def test_cycle_class_with_no_arguments
+ assert_raise(ArgumentError) { value = Cycle.new() }
+ end
+
+ def test_cycle
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ assert_equal("one", cycle("one", 2, "3"))
+ assert_equal("2", cycle("one", 2, "3"))
+ assert_equal("3", cycle("one", 2, "3"))
+ end
+
+ def test_cycle_with_no_arguments
+ assert_raise(ArgumentError) { value = cycle() }
+ end
+
+ def test_cycle_resets_with_new_values
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("odd", cycle("even", "odd"))
+ assert_equal("even", cycle("even", "odd"))
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ assert_equal("3", cycle(1, 2, 3))
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_named_cycles
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("3", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_default_named_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3, :name => "default"))
+ assert_equal("3", cycle(1, 2, 3))
+ end
+
+ def test_reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ assert_equal("2", cycle(1, 2, 3))
+ reset_cycle
+ assert_equal("1", cycle(1, 2, 3))
+ end
+
+ def test_reset_unknown_cycle
+ reset_cycle("colors")
+ end
+
+ def test_recet_named_cycle
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ reset_cycle("numbers")
+ assert_equal("1", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("blue", cycle("red", "blue", :name => "colors"))
+ assert_equal("2", cycle(1, 2, 3, :name => "numbers"))
+ assert_equal("red", cycle("red", "blue", :name => "colors"))
+ end
+
+ def test_cycle_no_instance_variable_clashes
+ @cycles = %w{Specialized Fuji Giant}
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal("blue", cycle("red", "blue"))
+ assert_equal("red", cycle("red", "blue"))
+ assert_equal(%w{Specialized Fuji Giant}, @cycles)
+ end
+end
diff --git a/vendor/rails/actionpack/test/template/url_helper_test.rb b/vendor/rails/actionpack/test/template/url_helper_test.rb
new file mode 100644
index 0000000..d45ea08
--- /dev/null
+++ b/vendor/rails/actionpack/test/template/url_helper_test.rb
@@ -0,0 +1,534 @@
+require 'abstract_unit'
+
+RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env)
+
+class UrlHelperTest < ActionView::TestCase
+ tests ActionView::Helpers::UrlHelper
+
+ def setup
+ @controller = Class.new do
+ attr_accessor :url, :request
+ def url_for(options)
+ url
+ end
+ end
+ @controller = @controller.new
+ @controller.url = "http://www.example.com"
+ end
+
+ def test_url_for_escapes_urls
+ @controller.url = "http://www.example.com?a=b&c=d"
+ assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd')
+ assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => true)
+ assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false)
+ end
+
+ def test_url_for_escapes_url_once
+ @controller.url = "http://www.example.com?a=b&c=d"
+ assert_equal "http://www.example.com?a=b&c=d", url_for("http://www.example.com?a=b&c=d")
+ end
+
+ # todo: missing test cases
+ def test_button_to_with_straight_url
+ assert_dom_equal "", button_to("Hello", "http://www.example.com")
+ end
+
+ def test_button_to_with_query
+ assert_dom_equal "", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ end
+
+ def test_button_to_with_escaped_query
+ assert_dom_equal "", button_to("Hello", "http://www.example.com/q1=v1&q2=v2")
+ end
+
+ def test_button_to_with_query_and_no_name
+ assert_dom_equal "", button_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ end
+
+ def test_button_to_with_javascript_confirm
+ assert_dom_equal(
+ "",
+ button_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
+ )
+ end
+
+ def test_button_to_enabled_disabled
+ assert_dom_equal(
+ "",
+ button_to("Hello", "http://www.example.com", :disabled => false)
+ )
+ assert_dom_equal(
+ "",
+ button_to("Hello", "http://www.example.com", :disabled => true)
+ )
+ end
+
+ def test_button_to_with_method_delete
+ assert_dom_equal(
+ "",
+ button_to("Hello", "http://www.example.com", :method => :delete)
+ )
+ end
+
+ def test_button_to_with_method_get
+ assert_dom_equal(
+ "",
+ button_to("Hello", "http://www.example.com", :method => :get)
+ )
+ end
+
+ def test_link_tag_with_straight_url
+ assert_dom_equal "Hello", link_to("Hello", "http://www.example.com")
+ end
+
+ def test_link_tag_without_host_option
+ ActionController::Base.class_eval { attr_accessor :url }
+ url = {:controller => 'weblog', :action => 'show'}
+ @controller = ActionController::Base.new
+ @controller.request = ActionController::TestRequest.new
+ @controller.url = ActionController::UrlRewriter.new(@controller.request, url)
+ assert_dom_equal(%q{Test Link}, link_to('Test Link', url))
+ end
+
+ def test_link_tag_with_host_option
+ ActionController::Base.class_eval { attr_accessor :url }
+ url = {:controller => 'weblog', :action => 'show', :host => 'www.example.com'}
+ @controller = ActionController::Base.new
+ @controller.request = ActionController::TestRequest.new
+ @controller.url = ActionController::UrlRewriter.new(@controller.request, url)
+ assert_dom_equal(%q{Test Link}, link_to('Test Link', url))
+ end
+
+ def test_link_tag_with_query
+ assert_dom_equal "Hello", link_to("Hello", "http://www.example.com?q1=v1&q2=v2")
+ end
+
+ def test_link_tag_with_query_and_no_name
+ assert_dom_equal "http://www.example.com?q1=v1&q2=v2", link_to(nil, "http://www.example.com?q1=v1&q2=v2")
+ end
+
+ def test_link_tag_with_back
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'})
+ assert_dom_equal "go back", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back_and_no_referer
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {})
+ assert_dom_equal "go back", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {'HTTP_REFERER' => 'http://www.example.com/referer'})
+ assert_dom_equal "go back", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_back_and_no_referer
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show", nil, nil, {})
+ assert_dom_equal "go back", link_to('go back', :back)
+ end
+
+ def test_link_tag_with_img
+ assert_dom_equal "", link_to("", "http://www.example.com")
+ end
+
+ def test_link_with_nil_html_options
+ assert_dom_equal "Hello", link_to("Hello", {:action => 'myaction'}, nil)
+ end
+
+ def test_link_tag_with_custom_onclick
+ assert_dom_equal "Hello", link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')")
+ end
+
+ def test_link_tag_with_javascript_confirm
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :confirm => "Are you sure?")
+ )
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?")
+ )
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?")
+ )
+ end
+
+ def test_link_tag_with_popup
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :popup => true)
+ )
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :popup => 'true')
+ )
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :popup => ['window_name', 'width=300,height=300'])
+ )
+ end
+
+ def test_link_tag_with_popup_and_javascript_confirm
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", { :popup => true, :confirm => "Fo' sho'?" })
+ )
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", { :popup => ['window_name', 'width=300,height=300'], :confirm => "Are you serious?" })
+ )
+ end
+
+ def test_link_tag_using_post_javascript
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :method => :post)
+ )
+ end
+
+ def test_link_tag_using_delete_javascript
+ assert_dom_equal(
+ "Destroy",
+ link_to("Destroy", "http://www.example.com", :method => :delete)
+ )
+ end
+
+ def test_link_tag_using_delete_javascript_and_href
+ assert_dom_equal(
+ "Destroy",
+ link_to("Destroy", "http://www.example.com", :method => :delete, :href => '#')
+ )
+ end
+
+ def test_link_tag_using_post_javascript_and_confirm
+ assert_dom_equal(
+ "Hello",
+ link_to("Hello", "http://www.example.com", :method => :post, :confirm => "Are you serious?")
+ )
+ end
+
+ def test_link_tag_using_post_javascript_and_popup
+ assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") }
+ end
+
+ def test_link_to_unless
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog")
+ assert_dom_equal "Listing", link_to_unless(false, "Listing", :action => "list", :controller => "weblog")
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1)
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name, options, html_options|
+ "#{name}"
+ }
+ assert_equal "Showing", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) { |name|
+ "#{name}"
+ }
+ assert_equal "test", link_to_unless(true, "Showing", :action => "show", :controller => "weblog", :id => 1) {
+ "test"
+ }
+ end
+
+ def test_link_to_if
+ assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog")
+ assert_dom_equal "Listing", link_to_if(true, "Listing", :action => "list", :controller => "weblog")
+ assert_equal "Showing", link_to_if(false, "Showing", :action => "show", :controller => "weblog", :id => 1)
+ end
+
+ def test_link_unless_current
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show")
+ @controller.url = "http://www.example.com/weblog/show"
+ assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" })
+ assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show")
+
+ @controller.request = RequestMock.new("http://www.example.com/weblog/show")
+ @controller.url = "http://www.example.com/weblog/list"
+ assert_equal "Listing",
+ link_to_unless_current("Listing", :action => "list", :controller => "weblog")
+ assert_equal "Listing",
+ link_to_unless_current("Listing", "http://www.example.com/weblog/list")
+ end
+
+ def test_mail_to
+ assert_dom_equal "david@loudthinking.com", mail_to("david@loudthinking.com")
+ assert_dom_equal "David Heinemeier Hansson", mail_to("david@loudthinking.com", "David Heinemeier Hansson")
+ assert_dom_equal(
+ "David Heinemeier Hansson",
+ mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin")
+ )
+ assert_equal mail_to("david@loudthinking.com", "David Heinemeier Hansson", "class" => "admin"),
+ mail_to("david@loudthinking.com", "David Heinemeier Hansson", :class => "admin")
+ end
+
+ def test_mail_to_with_javascript
+ assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript")
+ end
+
+ def test_mail_with_options
+ assert_dom_equal(
+ %(My email),
+ mail_to("me@example.com", "My email", :cc => "ccaddress@example.com", :bcc => "bccaddress@example.com", :subject => "This is an example email", :body => "This is the body of the message.")
+ )
+ end
+
+ def test_mail_to_with_img
+ assert_dom_equal %(), mail_to('feedback@example.com', '')
+ end
+
+ def test_mail_to_with_hex
+ assert_dom_equal "My email", mail_to("me@domain.com", "My email", :encode => "hex")
+ assert_dom_equal "me@domain.com", mail_to("me@domain.com", nil, :encode => "hex")
+ end
+
+ def test_mail_to_with_replace_options
+ assert_dom_equal "wolfgang(at)stufenlos(dot)net", mail_to("wolfgang@stufenlos.net", nil, :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "me(at)domain.com", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)")
+ assert_dom_equal "My email", mail_to("me@domain.com", "My email", :encode => "hex", :replace_at => "(at)")
+ assert_dom_equal "me(at)domain(dot)com", mail_to("me@domain.com", nil, :encode => "hex", :replace_at => "(at)", :replace_dot => "(dot)")
+ assert_dom_equal "", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)")
+ end
+
+ def protect_against_forgery?
+ false
+ end
+end
+
+class UrlHelperWithControllerTest < ActionView::TestCase
+ class UrlHelperController < ActionController::Base
+ self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ]
+
+ def self.controller_path; 'url_helper_with_controller' end
+
+ def show_url_for
+ render :inline => "<%= url_for :controller => 'url_helper_with_controller', :action => 'show_url_for' %>"
+ end
+
+ def show_named_route
+ render :inline => "<%= show_named_route_#{params[:kind]} %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ tests ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = UrlHelperController.new
+ end
+
+ def test_url_for_shows_only_path
+ get :show_url_for
+ assert_equal '/url_helper_with_controller/show_url_for', @response.body
+ end
+
+ def test_named_route_shows_host_and_path
+ with_url_helper_routing do
+ get :show_named_route, :kind => 'url'
+ assert_equal 'http://test.host/url_helper_with_controller/show_named_route', @response.body
+ end
+ end
+
+ def test_named_route_path_shows_only_path
+ with_url_helper_routing do
+ get :show_named_route, :kind => 'path'
+ assert_equal '/url_helper_with_controller/show_named_route', @response.body
+ end
+ end
+
+ protected
+ def with_url_helper_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.show_named_route 'url_helper_with_controller/show_named_route', :controller => 'url_helper_with_controller', :action => 'show_named_route'
+ end
+ yield
+ end
+ end
+end
+
+class LinkToUnlessCurrentWithControllerTest < ActionView::TestCase
+ class TasksController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'tasks' end
+
+ def index
+ render_default
+ end
+
+ def show
+ render_default
+ end
+
+ def rescue_action(e) raise e end
+
+ protected
+ def render_default
+ render :inline =>
+ "<%= link_to_unless_current(\"tasks\", tasks_path) %>\n" +
+ "<%= link_to_unless_current(\"tasks\", tasks_url) %>"
+ end
+ end
+
+ tests ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ @controller = TasksController.new
+ end
+
+ def test_link_to_unless_current_to_current
+ with_restful_routing do
+ get :index
+ assert_equal "tasks\ntasks", @response.body
+ end
+ end
+
+ def test_link_to_unless_current_shows_link
+ with_restful_routing do
+ get :show, :id => 1
+ assert_equal "tasks\n" +
+ "tasks",
+ @response.body
+ end
+ end
+
+ protected
+ def with_restful_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :tasks
+ end
+ yield
+ end
+ end
+end
+
+
+class Workshop
+ attr_accessor :id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+class Session
+ attr_accessor :id, :workshop_id, :new_record
+
+ def initialize(id, new_record)
+ @id, @new_record = id, new_record
+ end
+
+ def new_record?
+ @new_record
+ end
+
+ def to_s
+ id.to_s
+ end
+end
+
+class PolymorphicControllerTest < ActionView::TestCase
+ class WorkshopsController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'workshops' end
+
+ def index
+ @workshop = Workshop.new(1, true)
+ render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
+ end
+
+ def show
+ @workshop = Workshop.new(params[:id], false)
+ render :inline => "<%= url_for(@workshop) %>\n<%= link_to('Workshop', @workshop) %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ class SessionsController < ActionController::Base
+ self.view_paths = ["#{File.dirname(__FILE__)}/../fixtures/"]
+
+ def self.controller_path; 'sessions' end
+
+ def index
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(1, true)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def show
+ @workshop = Workshop.new(params[:workshop_id], false)
+ @session = Session.new(params[:id], false)
+ render :inline => "<%= url_for([@workshop, @session]) %>\n<%= link_to('Session', [@workshop, @session]) %>"
+ end
+
+ def rescue_action(e) raise e end
+ end
+
+ tests ActionView::Helpers::UrlHelper
+
+ def setup
+ @request = ActionController::TestRequest.new
+ @response = ActionController::TestResponse.new
+ end
+
+ def test_new_resource
+ @controller = WorkshopsController.new
+
+ with_restful_routing do
+ get :index
+ assert_equal "/workshops\nWorkshop", @response.body
+ end
+ end
+
+ def test_existing_resource
+ @controller = WorkshopsController.new
+
+ with_restful_routing do
+ get :show, :id => 1
+ assert_equal "/workshops/1\nWorkshop", @response.body
+ end
+ end
+
+ def test_new_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :index, :workshop_id => 1
+ assert_equal "/workshops/1/sessions\nSession", @response.body
+ end
+ end
+
+ def test_existing_nested_resource
+ @controller = SessionsController.new
+
+ with_restful_routing do
+ get :show, :workshop_id => 1, :id => 1
+ assert_equal "/workshops/1/sessions/1\nSession", @response.body
+ end
+ end
+
+ protected
+ def with_restful_routing
+ with_routing do |set|
+ set.draw do |map|
+ map.resources :workshops do |w|
+ w.resources :sessions
+ end
+ end
+ yield
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/test/testing_sandbox.rb b/vendor/rails/actionpack/test/testing_sandbox.rb
new file mode 100644
index 0000000..c365851
--- /dev/null
+++ b/vendor/rails/actionpack/test/testing_sandbox.rb
@@ -0,0 +1,15 @@
+module TestingSandbox
+ # Temporarily replaces KCODE for the block
+ def with_kcode(kcode)
+ if RUBY_VERSION < '1.9'
+ old_kcode, $KCODE = $KCODE, kcode
+ begin
+ yield
+ ensure
+ $KCODE = old_kcode
+ end
+ else
+ yield
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/CHANGELOG b/vendor/rails/activerecord/CHANGELOG
new file mode 100644
index 0000000..1c7c977
--- /dev/null
+++ b/vendor/rails/activerecord/CHANGELOG
@@ -0,0 +1,5719 @@
+*2.1.0 (May 31st, 2008)*
+
+* Add ActiveRecord::Base.sti_name that checks ActiveRecord::Base#store_full_sti_class? and returns either the full or demodulized name. [rick]
+
+* Add first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
+
+* Added SQL escaping for :limit and :offset #288 [Aaron Bedra, Steven Bristol, Jonathan Wiess]
+
+* Added first/last methods to associations/named_scope. Resolved #226. [Ryan Bates]
+
+* Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung]
+
+* Added protection against duplicate migration names (Aslak Hellesøy) [#112]
+
+* Base#instantiate_time_object: eliminate check for Time.zone, since we can assume this is set if time_zone_aware_attributes is set to true [Geoff Buesing]
+
+* Time zone aware attribute methods use Time.zone.parse instead of #to_time for String arguments, so that offset information in String is respected. Resolves #105. [Scott Fleckenstein, Geoff Buesing]
+
+* Added change_table for migrations (Jeff Dean) [#71]. Example:
+
+ change_table :videos do |t|
+ t.timestamps # adds created_at, updated_at
+ t.belongs_to :goat # adds goat_id integer
+ t.string :name, :email, :limit => 20 # adds name and email both with a 20 char limit
+ t.remove :name, :email # removes the name and email columns
+ end
+
+* Fixed has_many :through .create with no parameters caused a "can't dup NilClass" error (Steven Soroka) [#85]
+
+* Added block-setting of attributes for Base.create like Base.new already has (Adam Meehan) [#39]
+
+* Fixed that pessimistic locking you reference the quoted table name (Josh Susser) [#67]
+
+* Fixed that change_column should be able to use :null => true on a field that formerly had false [Nate Wiger] [#26]
+
+* Added that the MySQL adapter should map integer to either smallint, int, or bigint depending on the :limit just like PostgreSQL [DHH]
+
+* Change validates_uniqueness_of :case_sensitive option default back to true (from [9160]). Love your database columns, don't LOWER them. [rick]
+
+* Add support for interleaving migrations by storing which migrations have run in the new schema_migrations table. Closes #11493 [jordi]
+
+* ActiveRecord::Base#sum defaults to 0 if no rows are returned. Closes #11550 [kamal]
+
+* Ensure that respond_to? considers dynamic finder methods. Closes #11538. [floehopper]
+
+* Ensure that save on parent object fails for invalid has_one association. Closes #10518. [Pratik]
+
+* Remove duplicate code from associations. [Pratik]
+
+* Refactor HasManyThroughAssociation to inherit from HasManyAssociation. Association callbacks and _ids= now work with hm:t. #11516 [rubyruy]
+
+* Ensure HABTM#create and HABTM#build do not load entire association. [Pratik]
+
+* Improve documentation. [Xavier Noria, Jack Danger Canty, leethal]
+
+* Tweak ActiveRecord::Base#to_json to include a root value in the returned hash: {"post": {"title": ...}} [rick]
+
+ Post.find(1).to_json # => {"title": ...}
+ config.active_record.include_root_in_json = true
+ Post.find(1).to_json # => {"post": {"title": ...}}
+
+* Add efficient #include? to AssociationCollection (for has_many/has_many :through/habtm). [stopdropandrew]
+
+* PostgreSQL: create_ and drop_database support. #9042 [ez, pedz, nicksieger]
+
+* Ensure that validates_uniqueness_of works with with_scope. Closes #9235. [nik.wakelin, cavalle]
+
+* Partial updates include only unsaved attributes. Off by default; set YourClass.partial_updates = true to enable. [Jeremy Kemper]
+
+* Removing unnecessary uses_tzinfo helper from tests, given that TZInfo is now bundled [Geoff Buesing]
+
+* Fixed that validates_size_of :within works in associations #11295, #10019 [cavalle]
+
+* Track changes to unsaved attributes. [Jeremy Kemper]
+
+* Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches #11458 [jbarnette]
+
+* Fixed that has_many :through would ignore the hash conditions #11447 [miloops]
+
+* Fix issue where the :uniq option of a has_many :through association is ignored when find(:all) is called. Closes #9407 [cavalle]
+
+* Fix duplicate table alias error when including an association with a has_many :through association on the same join table. Closes #7310 [cavalle]
+
+* More efficient association preloading code that compacts a through_records array in a central location. Closes #11427 [danger]
+
+* Improve documentation. [Radar, Jan De Poorter, chuyeow, xaviershay, danger, miloops, Xavier Noria, Sunny Ripert]
+
+* Fixed that ActiveRecord#Base.find_or_create/initialize would not honor attr_protected/accessible when used with a hash #11422 [miloops]
+
+* Added ActiveRecord#Base.all/first/last as aliases for find(:all/:first/:last) #11413 [nkallen, thechrisoshow]
+
+* Merge the has_finder gem, renamed as 'named_scope'. #11404 [nkallen]
+
+ class Article < ActiveRecord::Base
+ named_scope :published, :conditions => {:published => true}
+ named_scope :popular, :conditions => ...
+ end
+
+ Article.published.paginate(:page => 1)
+ Article.published.popular.count
+ Article.popular.find(:first)
+ Article.popular.find(:all, :conditions => {...})
+
+ See http://pivots.pivotallabs.com/users/nick/blog/articles/284-hasfinder-it-s-now-easier-than-ever-to-create-complex-re-usable-sql-queries
+
+* Add has_one :through support. #4756 [thechrisoshow]
+
+* Migrations: create_table supports primary_key_prefix_type. #10314 [student, thechrisoshow]
+
+* Added logging for dependency load errors with fixtures #11056 [stuthulhu]
+
+* Time zone aware attributes use Time#in_time_zone [Geoff Buesing]
+
+* Fixed that scoped joins would not always be respected #6821 [Theory/Danger]
+
+* Ensure that ActiveRecord::Calculations disambiguates field names with the table name. #11027 [cavalle]
+
+* Added add/remove_timestamps to the schema statements for adding the created_at/updated_at columns on existing tables #11129 [jramirez]
+
+* Added ActiveRecord::Base.find(:last) #11338 [miloops]
+
+* test_native_types expects DateTime.local_offset instead of DateTime.now.offset; fixes test breakage due to dst transition [Geoff Buesing]
+
+* Add :readonly option to HasManyThrough associations. #11156 [miloops]
+
+* Improve performance on :include/:conditions/:limit queries by selectively joining in the pre-query. #9560 [dasil003]
+
+* Perf fix: Avoid the use of named block arguments. Closes #11109 [adymo]
+
+* PostgreSQL: support server versions 7.4 through 8.0 and the ruby-pg driver. #11127 [jdavis]
+
+* Ensure association preloading doesn't break when an association returns nil. ##11145 [GMFlash]
+
+* Make dynamic finders respect the :include on HasManyThrough associations. #10998. [cpytel]
+
+* Base#instantiate_time_object only uses Time.zone when Base.time_zone_aware_attributes is true; leverages Time#time_with_datetime_fallback for readability [Geoff Buesing]
+
+* Refactor ConnectionAdapters::Column.new_time: leverage DateTime failover behavior of Time#time_with_datetime_fallback [Geoff Buesing]
+
+* Improve associations performance by using symbol callbacks instead of string callbacks. #11108 [adymo]
+
+* Optimise the BigDecimal conversion code. #11110 [adymo]
+
+* Introduce the :readonly option to all associations. Records from the association cannot be saved. #11084 [miloops]
+
+* Multiparameter attributes for time columns fail over to DateTime when out of range of Time [Geoff Buesing]
+
+* Base#instantiate_time_object uses Time.zone.local() [Geoff Buesing]
+
+* Add timezone-aware attribute readers and writers. #10982 [Geoff Buesing]
+
+* Instantiating time objects in multiparameter attributes uses Time.zone if available. #10982 [rick]
+
+* Add note about how ActiveRecord::Observer classes are initialized in a Rails app. #10980 [fxn]
+
+* MySQL: omit text/blob defaults from the schema instead of using an empty string. #10963 [mdeiters]
+
+* belongs_to supports :dependent => :destroy and :delete. #10592 [Jonathan Viney]
+
+* Introduce preload query strategy for eager :includes. #9640 [Frederick Cheung, Aleksey Kondratenko, codafoo]
+
+* Support aggregations in finder conditions. #10572 [Ryan Kinderman]
+
+* Organize and clean up the Active Record test suite. #10742 [John Barnette]
+
+* Ensure that modifying has_and_belongs_to_many actions clear the query cache. Closes #10840 [john.andrews]
+
+* Fix issue where Table#references doesn't pass a :null option to a *_type attribute for polymorphic associations. Closes #10753 [railsjitsu]
+
+* Fixtures: removed support for the ancient pre-YAML file format. #10736 [John Barnette]
+
+* More thoroughly quote table names. #10698 [dimdenis, lotswholetime, Jeremy Kemper]
+
+* update_all ignores scoped :order and :limit, so post.comments.update_all doesn't try to include the comment order in the update statement. #10686 [Brendan Ribera]
+
+* Added ActiveRecord::Base.cache_key to make it easier to cache Active Records in combination with the new ActiveSupport::Cache::* libraries [DHH]
+
+* Make sure CSV fixtures are compatible with ruby 1.9's new csv implementation. [JEG2]
+
+* Added by parameter to increment, decrement, and their bang varieties so you can do player1.increment!(:points, 5) #10542 [Sam]
+
+* Optimize ActiveRecord::Base#exists? to use #select_all instead of #find. Closes #10605 [jamesh, fcheung, protocool]
+
+* Don't unnecessarily load has_many associations in after_update callbacks. Closes #6822 [stopdropandrew, canadaduane]
+
+* Eager belongs_to :include infers the foreign key from the association name rather than the class name. #10517 [Jonathan Viney]
+
+* SQLite: fix rename_ and remove_column for columns with unique indexes. #10576 [Brandon Keepers]
+
+* Ruby 1.9 compatibility. #10655 [Jeremy Kemper, Dirkjan Bussink]
+
+
+*2.0.2* (December 16th, 2007)
+
+* Ensure optimistic locking handles nil #lock_version values properly. Closes #10510 [rick]
+
+* Make the Fixtures Test::Unit enhancements more supporting for double-loaded test cases. Closes #10379 [brynary]
+
+* Fix that validates_acceptance_of still works for non-existent tables (useful for bootstrapping new databases). Closes #10474 [hasmanyjosh]
+
+* Ensure that the :uniq option for has_many :through associations retains the order. #10463 [remvee]
+
+* Base.exists? doesn't rescue exceptions to avoid hiding SQL errors. #10458 [Michael Klishin]
+
+* Documentation: Active Record exceptions, destroy_all and delete_all. #10444, #10447 [Michael Klishin]
+
+
+*2.0.1* (December 7th, 2007)
+
+* Removed query cache rescue as it could cause code to be run twice (closes #10408) [DHH]
+
+
+*2.0.0* (December 6th, 2007)
+
+* Anchor DateTimeTest to fixed DateTime instead of a variable value based on Time.now#advance#to_datetime, so that this test passes on 64-bit platforms running Ruby 1.8.6+ [Geoff Buesing]
+
+* Fixed that the Query Cache should just be ignored if the database is misconfigured (so that the "About your applications environment" works even before the database has been created) [DHH]
+
+* Fixed that the truncation of strings longer than 50 chars should use inspect
+so newlines etc are escaped #10385 [Norbert Crombach]
+
+* Fixed that habtm associations should be able to set :select as part of their definition and have that honored [DHH]
+
+* Document how the :include option can be used in Calculations::calculate. Closes #7446 [adamwiggins, ultimoamore]
+
+* Fix typo in documentation for polymorphic associations w/STI. Closes #7461 [johnjosephbachir]
+
+* Reveal that the type option in migrations can be any supported column type for your database but also include caveat about agnosticism. Closes #7531 [adamwiggins, mikong]
+
+* More complete documentation for find_by_sql. Closes #7912 [fearoffish]
+
+* Added ActiveRecord::Base#becomes to turn a record into one of another class (mostly relevant for STIs) [DHH]. Example:
+
+ render :partial => @client.becomes(Company) # renders companies/company instead of clients/client
+
+* Fixed that to_xml should not automatically pass :procs to associations included with :include #10162 [Cheah Chu Yeow]
+
+* Fix documentation typo introduced in [8250]. Closes #10339 [Henrik N]
+
+* Foxy fixtures: support single-table inheritance. #10234 [tom]
+
+* Foxy fixtures: allow mixed usage to make migration easier and more attractive. #10004 [lotswholetime]
+
+* Make the record_timestamps class-inheritable so it can be set per model. #10004 [tmacedo]
+
+* Allow validates_acceptance_of to use a real attribute instead of only virtual (so you can record that the acceptance occured) #7457 [ambethia]
+
+* DateTimes use Ruby's default calendar reform setting. #10201 [Geoff Buesing]
+
+* Dynamic finders on association collections respect association :order and :limit. #10211, #10227 [Patrick Joyce, Rick Olson, Jack Danger Canty]
+
+* Add 'foxy' support for fixtures of polymorphic associations. #10183 [John Barnette, David Lowenfels]
+
+* validates_inclusion_of and validates_exclusion_of allow formatted :message strings. #8132 [devrieda, Mike Naberezny]
+
+* attr_readonly behaves well with optimistic locking. #10188 [Nick Bugajski]
+
+* Base#to_xml supports the nil="true" attribute like Hash#to_xml. #8268 [Catfish]
+
+* Change plings to the more conventional quotes in the documentation. Closes #10104 [danger]
+
+* Fix HasManyThrough Association so it uses :conditions on the HasMany Association. Closes #9729 [danger]
+
+* Ensure that column names are quoted. Closes #10134 [wesley.moxam]
+
+* Smattering of grammatical fixes to documentation. Closes #10083 [BobSilva]
+
+* Enhance explanation with more examples for attr_accessible macro. Closes #8095 [fearoffish, Marcel Molina]
+
+* Update association/method mapping table to refected latest collection methods for has_many :through. Closes #8772 [Pratik Naik]
+
+* Explain semantics of having several different AR instances in a transaction block. Closes #9036 [jacobat, Marcel Molina]
+
+* Update Schema documentation to use updated sexy migration notation. Closes #10086 [sjgman9]
+
+* Make fixtures work with the new test subclasses. [Tarmo Tänav, Koz]
+
+* Introduce finder :joins with associations. Same :include syntax but with inner rather than outer joins. #10012 [RubyRedRick]
+ # Find users with an avatar
+ User.find(:all, :joins => :avatar)
+
+ # Find posts with a high-rated comment.
+ Post.find(:all, :joins => :comments, :conditions => 'comments.rating > 3')
+
+* Associations: speedup duplicate record check. #10011 [Pratik Naik]
+
+* Make sure that << works on has_many associations on unsaved records. Closes #9989 [hasmanyjosh]
+
+* Allow association redefinition in subclasses. #9346 [wildchild]
+
+* Fix has_many :through delete with custom foreign keys. #6466 [naffis]
+
+* Foxy fixtures, from rathole (http://svn.geeksomnia.com/rathole/trunk/README)
+ - stable, autogenerated IDs
+ - specify associations (belongs_to, has_one, has_many) by label, not ID
+ - specify HABTM associations as inline lists
+ - autofill timestamp columns
+ - support YAML defaults
+ - fixture label interpolation
+ Enabled for fixtures that correspond to a model class and don't specify a primary key value. #9981 [John Barnette]
+
+* Add docs explaining how to protect all attributes using attr_accessible with no arguments. Closes #9631 [boone, rmm5t]
+
+* Update add_index documentation to use new options api. Closes #9787 [Kamal Fariz Mahyuddin]
+
+* Allow find on a has_many association defined with :finder_sql to accept id arguments as strings like regular find does. Closes #9916 [krishna]
+
+* Use VALID_FIND_OPTIONS when resolving :find scoping rather than hard coding the list of valid find options. Closes #9443 [sur]
+
+* Limited eager loading no longer ignores scoped :order. Closes #9561 [danger, Josh Peek]
+
+* Assigning an instance of a foreign class to a composed_of aggregate calls an optional conversion block. Refactor and simplify composed_of implementation. #6322 [brandon, Chris Cruft]
+
+* Assigning nil to a composed_of aggregate also sets its immediate value to nil. #9843 [Chris Cruft]
+
+* Ensure that mysql quotes table names with database names correctly. Closes #9911 [crayz]
+
+ "foo.bar" => "`foo`.`bar`"
+
+* Complete the assimilation of Sexy Migrations from ErrFree [Chris Wanstrath, PJ Hyett]
+ http://errtheblog.com/post/2381
+
+* Qualified column names work in hash conditions, like :conditions => { 'comments.created_at' => ... }. #9733 [danger]
+
+* Fix regression where the association would not construct new finder SQL on save causing bogus queries for "WHERE owner_id = NULL" even after owner was saved. #8713 [Bryan Helmkamp]
+
+* Refactor association create and build so before & after callbacks behave consistently. #8854 [Pratik Naik, mortent]
+
+* Quote table names. Defaults to column quoting. #4593 [Justin Lynn, gwcoffey, eadz, Dmitry V. Sabanin, Jeremy Kemper]
+
+* Alias association #build to #new so it behaves predictably. #8787 [Pratik Naik]
+
+* Add notes to documentation regarding attr_readonly behavior with counter caches and polymorphic associations. Closes #9835 [saimonmoore, rick]
+
+* Observers can observe model names as symbols properly now. Closes #9869 [queso]
+
+* find_and_(initialize|create)_by methods can now properly initialize protected attributes [Tobias Luetke]
+
+* belongs_to infers the foreign key from the association name instead of from the class name. [Jeremy Kemper]
+
+* PostgreSQL: support multiline default values. #7533 [Carl Lerche, aguynamedryan, Rein Henrichs, Tarmo Tänav]
+
+* MySQL: fix change_column on not-null columns that don't accept dfeault values of ''. #6663 [Jonathan Viney, Tarmo Tänav]
+
+* validates_uniqueness_of behaves well with abstract superclasses and
+single-table inheritance. #3833, #9886 [Gabriel Gironda, rramdas, François Beausoleil, Josh Peek, Tarmo Tänav, pat]
+
+* Warn about protected attribute assigments in development and test environments when mass-assigning to an attr_protected attribute. #9802 [Henrik N]
+
+* Speedup database date/time parsing. [Jeremy Kemper, Tarmo Tänav]
+
+* Fix calling .clear on a has_many :dependent=>:delete_all association. [Tarmo Tänav]
+
+* Allow change_column to set NOT NULL in the PostgreSQL adapter [Tarmo Tänav]
+
+* Fix that ActiveRecord would create attribute methods and override custom attribute getters if the method is also defined in Kernel.methods. [Rick]
+
+* Don't call attr_readonly on polymorphic belongs_to associations, in case it matches the name of some other non-ActiveRecord class/module. [Rick]
+
+* Try loading activerecord--adapter gem before trying a plain require so you can use custom gems for the bundled adapters. Also stops gems from requiring an adapter from an old Active Record gem. [Jeremy Kemper, Derrick Spell]
+
+
+*2.0.0 [Preview Release]* (September 29th, 2007) [Includes duplicates of changes from 1.14.2 - 1.15.3]
+
+* Add attr_readonly to specify columns that are skipped during a normal ActiveRecord #save operation. Closes #6896 [dcmanges]
+
+ class Comment < ActiveRecord::Base
+ # Automatically sets Article#comments_count as readonly.
+ belongs_to :article, :counter_cache => :comments_count
+ end
+
+ class Article < ActiveRecord::Base
+ attr_readonly :approved_comments_count
+ end
+
+* Make size for has_many :through use counter cache if it exists. Closes #9734 [xaviershay]
+
+* Remove DB2 adapter since IBM chooses to maintain their own adapter instead. [Jeremy Kemper]
+
+* Extract Oracle, SQLServer, and Sybase adapters into gems. [Jeremy Kemper]
+
+* Added fixture caching that'll speed up a normal fixture-powered test suite between 50% and 100% #9682 [Frederick Cheung]
+
+* Correctly quote id list for limited eager loading. #7482 [tmacedo]
+
+* Fixed that using version-targetted migrates would fail on loggers other than the default one #7430 [valeksenko]
+
+* Fixed rename_column for SQLite when using symbols for the column names #8616 [drodriguez]
+
+* Added the possibility of using symbols in addition to concrete classes with ActiveRecord::Observer#observe. #3998 [Robby Russell, Tarmo Tänav]
+
+* Added ActiveRecord::Base#to_json/from_json [DHH, Cheah Chu Yeow]
+
+* Added ActiveRecord::Base#from_xml [DHH]. Example:
+
+ xml = "David"
+ Person.new.from_xml(xml).name # => "David"
+
+* Define dynamic finders as real methods after first usage. [bscofield]
+
+* Deprecation: remove deprecated threaded_connections methods. Use allow_concurrency instead. [Jeremy Kemper]
+
+* Associations macros accept extension blocks alongside modules. #9346 [Josh Peek]
+
+* Speed up and simplify query caching. [Jeremy Kemper]
+
+* connection.select_rows 'sql' returns an array (rows) of arrays (field values). #2329 [Michael Schuerig]
+
+* Eager loading respects explicit :joins. #9496 [dasil003]
+
+* Extract Firebird, FrontBase, and OpenBase adapters into gems. #9508, #9509, #9510 [Jeremy Kemper]
+
+* RubyGem database adapters: expects a gem named activerecord--adapter with active_record/connection_adapters/_adapter.rb in its load path. [Jeremy Kemper]
+
+* Fixed that altering join tables in migrations would fail w/ sqlite3 #7453 [TimoMihaljov/brandon]
+
+* Fix association writer with :dependent => :nullify. #7314 [Jonathan Viney]
+
+* OpenBase: update for new lib and latest Rails. Support migrations. #8748 [dcsesq]
+
+* Moved acts_as_tree into a plugin of the same name on the official Rails svn. #9514 [Pratik Naik]
+
+* Moved acts_as_nested_set into a plugin of the same name on the official Rails svn. #9516 [Josh Peek]
+
+* Moved acts_as_list into a plugin of the same name on the official Rails svn. [Josh Peek]
+
+* Explicitly require active_record/query_cache before using it. [Jeremy Kemper]
+
+* Fix bug where unserializing an attribute attempts to modify a frozen @attributes hash for a deleted record. [Rick, marclove]
+
+* Performance: absorb instantiate and initialize_with_callbacks into the Base methods. [Jeremy Kemper]
+
+* Fixed that eager loading queries and with_scope should respect the :group option [DHH]
+
+* Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd]
+
+ For more information see: http://dev.rubyonrails.org/ticket/8049
+
+* Don't clobber includes passed to has_many.count [danger]
+
+* Make sure has_many uses :include when counting [danger]
+
+* Change the implementation of ActiveRecord's attribute reader and writer methods [nzkoz]
+ - Generate Reader and Writer methods which cache attribute values in hashes. This is to avoid repeatedly parsing the same date or integer columns.
+ - Change exception raised when users use find with :select then try to access a skipped column. Plugins could override missing_attribute() to lazily load the columns.
+ - Move method definition to the class, instead of the instance
+ - Always generate the readers, writers and predicate methods.
+
+* Perform a deep #dup on query cache results so that modifying activerecord attributes does not modify the cached attributes. [Rick]
+
+# Ensure that has_many :through associations use a count query instead of loading the target when #size is called. Closes #8800 [Pratik Naik]
+
+* Added :unless clause to validations #8003 [monki]. Example:
+
+ def using_open_id?
+ !identity_url.blank?
+ end
+
+ validates_presence_of :identity_url, :if => using_open_id?
+ validates_presence_of :username, :unless => using_open_id?
+ validates_presence_of :password, :unless => using_open_id?
+
+* Fix #count on a has_many :through association so that it recognizes the :uniq option. Closes #8801 [Pratik Naik]
+
+* Fix and properly document/test count(column_name) usage. Closes #8999 [Pratik Naik]
+
+* Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 [Pratik Naik]
+
+* Change belongs_to so that the foreign_key assumption is taken from the association name, not the class name. Closes #8992 [hasmanyjosh]
+
+ OLD
+ belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is user_id
+
+ NEW
+ belongs_to :visitor, :class_name => 'User' # => inferred foreign_key is visitor_id
+
+* Remove spurious tests from deprecated_associations_test, most of these aren't deprecated, and are duplicated in associations_test. Closes #8987 [Pratik Naik]
+
+* Make create! on a has_many :through association return the association object. Not the collection. Closes #8786 [Pratik Naik]
+
+* Move from select * to select tablename.* to avoid clobbering IDs. Closes #8889 [dasil003]
+
+* Don't call unsupported methods on associated objects when using :include, :method with to_xml #7307, [manfred, jwilger]
+
+* Define collection singular ids method for has_many :through associations. #8763 [Pratik Naik]
+
+* Array attribute conditions work with proxied association collections. #8318 [Kamal Fariz Mahyuddin, theamazingrando]
+
+* Fix polymorphic has_one associations declared in an abstract class. #8638 [Pratik Naik, Dax Huiberts]
+
+* Fixed validates_associated should not stop on the first error. #4276 [mrj, Manfred Stienstra, Josh Peek]
+
+* Rollback if commit raises an exception. #8642 [kik, Jeremy Kemper]
+
+* Update tests' use of fixtures for the new collections api. #8726 [Kamal Fariz Mahyuddin]
+
+* Save associated records only if the association is already loaded. #8713 [blaine]
+
+* MySQL: fix show_variable. #8448 [matt, Jeremy Kemper]
+
+* Fixtures: correctly delete and insert fixtures in a single transaction. #8553 [Michael Schuerig]
+
+* Fixtures: people(:technomancy, :josh) returns both fixtures. #7880 [technomancy, Josh Peek]
+
+* Calculations support non-numeric foreign keys. #8154 [Kamal Fariz Mahyuddin]
+
+* with_scope is protected. #8524 [Josh Peek]
+
+* Quickref for association methods. #7723 [marclove, Mindsweeper]
+
+* Calculations: return nil average instead of 0 when there are no rows to average. #8298 [davidw]
+
+* acts_as_nested_set: direct_children is sorted correctly. #4761 [Josh Peek, rails@33lc0.net]
+
+* Raise an exception if both attr_protected and attr_accessible are declared. #8507 [stellsmi]
+
+* SQLite, MySQL, PostgreSQL, Oracle: quote column names in column migration SQL statements. #8466 [marclove, lorenjohnson]
+
+* Allow nil serialized attributes with a set class constraint. #7293 [sandofsky]
+
+* Oracle: support binary fixtures. #7987 [Michael Schoen]
+
+* Fixtures: pull fixture insertion into the database adapters. #7987 [Michael Schoen]
+
+* Announce migration versions as they're performed. [Jeremy Kemper]
+
+* find gracefully copes with blank :conditions. #7599 [Dan Manges, johnnyb]
+
+* validates_numericality_of takes :greater_than, :greater_than_or_equal_to, :equal_to, :less_than, :less_than_or_equal_to, :odd, and :even options. #3952 [Bob Silva, Dan Kubb, Josh Peek]
+
+* MySQL: create_database takes :charset and :collation options. Charset defaults to utf8. #8448 [matt]
+
+* Find with a list of ids supports limit/offset. #8437 [hrudududu]
+
+* Optimistic locking: revert the lock version when an update fails. #7840 [plang]
+
+* Migrations: add_column supports custom column types. #7742 [jsgarvin, Theory]
+
+* Load database adapters on demand. Eliminates config.connection_adapters and RAILS_CONNECTION_ADAPTERS. Add your lib directory to the $LOAD_PATH and put your custom adapter in lib/active_record/connection_adapters/adaptername_adapter.rb. This way you can provide custom adapters as plugins or gems without modifying Rails. [Jeremy Kemper]
+
+* Ensure that associations with :dependent => :delete_all respect :conditions option. Closes #8034 [danger, Josh Peek, Rick]
+
+* belongs_to assignment creates a new proxy rather than modifying its target in-place. #8412 [mmangino@elevatedrails.com]
+
+* Fix column type detection while loading fixtures. Closes #7987 [roderickvd]
+
+* Document deep eager includes. #6267 [Josh Susser, Dan Manges]
+
+* Document warning that associations names shouldn't be reserved words. #4378 [murphy@cYcnus.de, Josh Susser]
+
+* Sanitize Base#inspect. #8392, #8623 [Nik Wakelin, jnoon]
+
+* Replace the transaction {|transaction|..} semantics with a new Exception ActiveRecord::Rollback. [Koz]
+
+* Oracle: extract column length for CHAR also. #7866 [ymendel]
+
+* Document :allow_nil option for validates_acceptance_of since it defaults to true. [tzaharia]
+
+* Update documentation for :dependent declaration so that it explicitly uses the non-deprecated API. [danger]
+
+* Add documentation caveat about when to use count_by_sql. [fearoffish]
+
+* Enhance documentation for increment_counter and decrement_counter. [fearoffish]
+
+* Provide brief introduction to what optimistic locking is. [fearoffish]
+
+* Add documentation for :encoding option to mysql adapter. [marclove]
+
+* Added short-hand declaration style to migrations (inspiration from Sexy Migrations, http://errtheblog.com/post/2381) [DHH]. Example:
+
+ create_table "products" do |t|
+ t.column "shop_id", :integer
+ t.column "creator_id", :integer
+ t.column "name", :string, :default => "Untitled"
+ t.column "value", :string, :default => "Untitled"
+ t.column "created_at", :datetime
+ t.column "updated_at", :datetime
+ end
+
+ ...can now be written as:
+
+ create_table :products do |t|
+ t.integer :shop_id, :creator_id
+ t.string :name, :value, :default => "Untitled"
+ t.timestamps
+ end
+
+* Use association name for the wrapper element when using .to_xml. Previous behavior lead to non-deterministic situations with STI and polymorphic associations. [Koz, jstrachan]
+
+* Improve performance of calling .create on has_many :through associations. [evan]
+
+* Improved cloning performance by relying less on exception raising #8159 [Blaine]
+
+* Added ActiveRecord::Base.inspect to return a column-view like # [DHH]
+
+* Added yielding of Builder instance for ActiveRecord::Base#to_xml calls [DHH]
+
+* Small additions and fixes for ActiveRecord documentation. Closes #7342 [jeremymcanally]
+
+* Add helpful debugging info to the ActiveRecord::StatementInvalid exception in ActiveRecord::ConnectionAdapters::SqliteAdapter#table_structure. Closes #7925. [court3nay]
+
+* SQLite: binary escaping works with $KCODE='u'. #7862 [tsuka]
+
+* Base#to_xml supports serialized attributes. #7502 [jonathan]
+
+* Base.update_all :order and :limit options. Useful for MySQL updates that must be ordered to avoid violating unique constraints. [Jeremy Kemper]
+
+* Remove deprecated object transactions. People relying on this functionality should install the object_transactions plugin at http://code.bitsweat.net/svn/object_transactions. Closes #5637 [Koz, Jeremy Kemper]
+
+* PostgreSQL: remove DateTime -> Time downcast. Warning: do not enable translate_results for the C bindings if you have timestamps outside Time's domain. [Jeremy Kemper]
+
+* find_or_create_by_* takes a hash so you can create with more attributes than are in the method name. For example, Person.find_or_create_by_name(:name => 'Henry', :comments => 'Hi new user!') is equivalent to Person.find_by_name('Henry') || Person.create(:name => 'Henry', :comments => 'Hi new user!'). #7368 [Josh Susser]
+
+* Make sure with_scope takes both :select and :joins into account when setting :readonly. Allows you to save records you retrieve using method_missing on a has_many :through associations. [Koz]
+
+* Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
+
+* Consistent public/protected/private visibility for chained methods. #7813 [Dan Manges]
+
+* Oracle: fix quoted primary keys and datetime overflow. #7798 [Michael Schoen]
+
+* Consistently quote primary key column names. #7763 [toolmantim]
+
+* Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
+
+* DateTimes assume the default timezone. #7764 [Geoff Buesing]
+
+* Sybase: hide timestamp columns since they're inherently read-only. #7716 [Mike Joyce]
+
+* Oracle: overflow Time to DateTime. #7718 [Michael Schoen]
+
+* PostgreSQL: don't use async_exec and async_query with postgres-pr. #7727, #7762 [flowdelic, toolmantim]
+
+* Fix has_many :through << with custom foreign keys. #6466, #7153 [naffis, Rich Collins]
+
+* Test DateTime native type in migrations, including an edge case with dates
+during calendar reform. #7649, #7724 [fedot, Geoff Buesing]
+
+* SQLServer: correctly schema-dump tables with no indexes or descending indexes. #7333, #7703 [Jakob S, Tom Ward]
+
+* SQLServer: recognize real column type as Ruby float. #7057 [sethladd, Tom Ward]
+
+* Added fixtures :all as a way of loading all fixtures in the fixture directory at once #7214 [manfred]
+
+* Added database connection as a yield parameter to ActiveRecord::Base.transaction so you can manually rollback [DHH]. Example:
+
+ transaction do |transaction|
+ david.withdrawal(100)
+ mary.deposit(100)
+ transaction.rollback! # rolls back the transaction that was otherwise going to be successful
+ end
+
+* Made increment_counter/decrement_counter play nicely with optimistic locking, and added a more general update_counters method [Jamis Buck]
+
+* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Luetke]
+ Task.cache { Task.find(1); Task.find(1) } #=> 1 query
+
+* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
+
+* Oracle: fix lob and text default handling. #7344 [gfriedrich, Michael Schoen]
+
+* SQLServer: don't choke on strings containing 'null'. #7083 [Jakob S]
+
+* MySQL: blob and text columns may not have defaults in 5.x. Update fixtures schema for strict mode. #6695 [Dan Kubb]
+
+* update_all can take a Hash argument. sanitize_sql splits into two methods for conditions and assignment since NULL values and delimiters are handled differently. #6583, #7365 [sandofsky, Assaf]
+
+* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 [Jonathan Viney, timc]
+
+* Use Date#to_s(:db) for quoted dates. #7411 [Michael Schoen]
+
+* Don't create instance writer methods for class attributes. Closes #7401 [Rick]
+
+* Docs: validations examples. #7343 [zackchandler]
+
+* Add missing tests ensuring callbacks work with class inheritance. Closes #7339 [sandofsky]
+
+* Fixtures use the table name and connection from set_fixture_class. #7330 [Anthony Eden]
+
+* Remove useless code in #attribute_present? since 0 != blank?. Closes #7249 [Josh Susser]
+
+* Fix minor doc typos. Closes #7157 [Josh Susser]
+
+* Fix incorrect usage of #classify when creating the eager loading join statement. Closes #7044 [Josh Susser]
+
+* SQLServer: quote table name in indexes query. #2928 [keithm@infused.org]
+
+* Subclasses of an abstract class work with single-table inheritance. #5704, #7284 [BertG, nick+rails@ag.arizona.edu]
+
+* Make sure sqlite3 driver closes open connections on disconnect [Rob Rasmussen]
+
+* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 [jeremymcanally]
+
+* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 [Dan Manges, Jeremy Kemper]
+
+* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 [Jonathan Viney, manfred, altano@bigfoot.com]
+
+* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 [Michael Schoen]
+
+* MySQL: retain SSL settings on reconnect. #6976 [randyv2]
+
+* Apply scoping during initialize instead of create. Fixes setting of foreign key when using find_or_initialize_by with scoping. [Cody Fauser]
+
+* SQLServer: handle [quoted] table names. #6635 [rrich]
+
+* acts_as_nested_set works with single-table inheritance. #6030 [Josh Susser]
+
+* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 [eventualbuddha, Michael Schoen]
+
+* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 [Dan Manges]
+ Student.find(:all, :conditions => { :grade => 9..12 })
+
+* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 [mschoen, tdfowler]
+
+* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 [Robby Russell]
+
+* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 [Caio Chassot]
+
+* Bring the sybase adapter up to scratch for 1.2 release. [jsheets]
+
+* Rollback new_record? and id when an exception is raised in a save callback. #6910 [Ben Curren, outerim]
+
+* Pushing a record on an association collection doesn't unnecessarily load all the associated records. [Obie Fernandez, Jeremy Kemper]
+
+* Oracle: fix connection reset failure. #6846 [leonlleslie]
+
+* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 [leei, Jeremy Kemper]
+
+* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 [protocool]
+
+* Consolidated different create and create! versions to call through to the base class with scope. This fixes inconsistencies, especially related to protected attribtues. Closes #5847 [Alexander Dymo, Tobias Luetke]
+
+* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 [vitaly, Jeremy Kemper]
+
+* Add AssociationCollection#create! to be consistent with AssociationCollection#create when dealing with a foreign key that is a protected attribute [Cody Fauser]
+
+* Added counter optimization for AssociationCollection#any? so person.friends.any? won't actually load the full association if we have the count in a cheaper form [DHH]
+
+* Change fixture_path to a class inheritable accessor allowing test cases to have their own custom set of fixtures. #6672 [zdennis]
+
+* Quote ActiveSupport::Multibyte::Chars. #6653 [Julian Tarkhanov]
+
+* Simplify query_attribute by typecasting the attribute value and checking whether it's nil, false, zero or blank. #6659 [Jonathan Viney]
+
+* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 [Andreas Schwarz]
+
+* Run validations in the order they were declared. #6657 [obrie]
+
+* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 [simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper]
+
+* Simplify association proxy implementation by factoring construct_scope out of method_missing. #6643 [martin]
+
+* Oracle: automatically detect the primary key. #6594 [vesaria, Michael Schoen]
+
+* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 [philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen]
+
+* Don't inspect unloaded associations. #2905 [lmarlow]
+
+* SQLite: use AUTOINCREMENT primary key in >= 3.1.0. #6588, #6616 [careo, lukfugl]
+
+* Cache inheritance_column. #6592 [Stefan Kaes]
+
+* Firebird: decimal/numeric support. #6408 [macrnic]
+
+* make add_order a tad faster. #6567 [Stefan Kaes]
+
+* Find with :include respects scoped :order. #5850
+
+* Support nil and Array in :conditions => { attr => value } hashes. #6548 [Assaf, Jeremy Kemper]
+ find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
+
+* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 [Si]
+
+* SQLite: fix calculations workaround, remove count(distinct) query rewrite, cleanup test connection scripts. [Jeremy Kemper]
+
+* SQLite: count(distinct) queries supported in >= 3.2.6. #6544 [Bob Silva]
+
+* Dynamically generate reader methods for serialized attributes. #6362 [Stefan Kaes]
+
+* Deprecation: object transactions warning. [Jeremy Kemper]
+
+* has_one :dependent => :nullify ignores nil associates. #4848, #6528 [bellis@deepthought.org, janovetz, Jeremy Kemper]
+
+* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 [Michael Schoen]
+
+* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 [obrie]
+
+* Document other options available to migration's add_column. #6419 [grg]
+
+* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 [Jeremy Kemper]
+
+* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. [Jonathan Viney]
+
+* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters [Rick]
+
+* Restore eager condition interpolation, document it's differences [Rick]
+
+* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 [Jacob Fugal, Jeremy Kemper]
+
+* Add #delete support to has_many :through associations. Closes #6049 [Martin Landers]
+
+* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 [Rick]
+
+* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 [turnip@turnipspatch.com]
+
+* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 [wreese@gmail.com]
+
+* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 [jonathan]
+
+* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 [BobSilva]
+
+* The has_many create method works with polymorphic associations. #6361 [Dan Peterson]
+
+* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 [Stefan Kaes]
+
+* save! shouldn't validate twice. #6324 [maiha, Bob Silva]
+
+* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 [Michael Schuerig]
+
+* Add an attribute reader method for ActiveRecord::Base.observers [Rick Olson]
+
+* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 [Bob Silva]
+
+* has_one associations with a nil target may be safely marshaled. #6279 [norbauer, Jeremy Kemper]
+
+* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects [Koz]
+
+* Add a :namespace option to AR::Base#to_xml [Koz]
+
+* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. [Jeremy Kemper]
+
+* Mock Time.now for more accurate Touch mixin tests. #6213 [Dan Peterson]
+
+* Improve yaml fixtures error reporting. #6205 [Bruce Williams]
+
+* Rename AR::Base#quote so people can use that name in their models. #3628 [Koz]
+
+* Add deprecation warning for inferred foreign key. #6029 [Josh Susser]
+
+* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 [jimw@mysql.com]
+
+* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 [Josh Susser]
+
+* Document validates_presences_of behavior with booleans: you probably want validates_inclusion_of :attr, :in => [true, false]. #2253 [Bob Silva]
+
+* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 [Tom Ward]
+
+* to_xml: the :methods option works on arrays of records. #5845 [Josh Starcher]
+
+* Deprecation: update docs. #5998 [jakob@mentalized.net, Kevin Clark]
+
+* Add some XmlSerialization tests for ActiveRecord [Rick Olson]
+
+* has_many :through conditions are sanitized by the associating class. #5971 [martin.emde@gmail.com]
+
+* Tighten rescue clauses. #5985 [james@grayproductions.net]
+
+* Fix spurious newlines and spaces in AR::Base#to_xml output [Jamis Buck]
+
+* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
+
+* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney]
+
+* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]
+
+* SQLServer: fix eager association test. #5901 [Tom Ward]
+
+* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 [Kevin Clark]
+
+* Fixtures: correct escaping of \n and \r. #5859 [evgeny.zislis@gmail.com]
+
+* Migrations: gracefully handle missing migration files. #5857 [eli.gordon@gmail.com]
+
+* MySQL: update test schema for MySQL 5 strict mode. #5861 [Tom Ward]
+
+* to_xml: correct naming of included associations. #5831 [josh.starcher@gmail.com]
+
+* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 [josh@hasmanythrough.com]
+
+* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. [Jeremy Kemper]
+
+ # Create a tagging to associate the post and tag.
+ post.tags << Tag.find_by_name('old')
+ post.tags.create! :name => 'general'
+
+ # Would have been:
+ post.taggings.create!(:tag => Tag.find_by_name('finally')
+ transaction do
+ post.taggings.create!(:tag => Tag.create!(:name => 'general'))
+ end
+
+* Cache nil results for :included has_one associations also. #5787 [Michael Schoen]
+
+* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. [Tobias Luetke]
+
+* Nested classes are given table names prefixed by the singular form of the parent's table name. [Jeremy Kemper]
+ Example: Invoice::Lineitem is given table name invoice_lineitems
+
+* Migrations: uniquely name multicolumn indexes so you don't have to. [Jeremy Kemper]
+ # people_active_last_name_index, people_active_deactivated_at_index
+ add_index :people, [:active, :last_name]
+ add_index :people, [:active, :deactivated_at]
+ remove_index :people, [:active, :last_name]
+ remove_index :people, [:active, :deactivated_at]
+
+ WARNING: backward-incompatibility. Multicolumn indexes created before this
+ revision were named using the first column name only. Now they're uniquely
+ named using all indexed columns.
+
+ To remove an old multicolumn index, remove_index :table_name, :first_column
+
+* Fix for deep includes on the same association. [richcollins@gmail.com]
+
+* Tweak fixtures so they don't try to use a non-ActiveRecord class. [Kevin Clark]
+
+* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. [Rick Olson]
+
+* Document find's :from option. Closes #5762. [andrew@redlinesoftware.com]
+
+* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 [guy.naor@famundo.com]
+
+* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
+
+* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael A. Schoen]
+
+* Add documentation for how to disable timestamps on a per model basis. Closes #5684. [matt@mattmargolis.net Marcel Molina Jr.]
+
+* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
+
+* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson]
+
+* Formally deprecate the deprecated finders. [Koz]
+
+* Formally deprecate rich associations. [Koz]
+
+* Fixed that default timezones for new / initialize should uphold utc setting #5709 [daniluk@yahoo.com]
+
+* Fix announcement of very long migration names. #5722 [blake@near-time.com]
+
+* The exists? class method should treat a string argument as an id rather than as conditions. #5698 [jeremy@planetargon.com]
+
+* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 [alexkwolfe@gmail.com]
+
+* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
+
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
+
+* Schema dumper quotes date :default values. [Dave Thomas]
+
+* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. [Dan Peterson]
+
+* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper]
+
+* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
+
+* Allow #count through a has_many association to accept :include. [Dan Peterson]
+
+* create_table rdoc: suggest :id => false for habtm join tables. [Zed Shaw]
+
+* PostgreSQL: return array fields as strings. #4664 [Robby Russell]
+
+* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 [Tom Ward]
+
+* SQLServer: fix db:schema:dump case-sensitivity. #4684 [Will Rogers]
+
+* Oracle: BigDecimal support. #5667 [schoenm@earthlink.net]
+
+* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk]
+
+* Firebird migrations support. #5337 [Ken Kunz ]
+
+* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
+
+* Update callbacks documentation. #3970 [Robby Russell ]
+
+* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net]
+
+* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com]
+
+* Clearer has_one/belongs_to model names (account has_one :user). #5632 [matt@mattmargolis.net]
+
+* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 [schoenm@earthlink.net]
+
+* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 [schoenm@earthlink.net]
+
+* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org]
+
+* Minor tweak to improve performance of ActiveRecord::Base#to_param.
+
+* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au]
+
+* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper]
+
+* rake build_mysql_database grants permissions to rails@localhost. #5501 [brianegge@yahoo.com]
+
+* PostgreSQL: support microsecond time resolution. #5492 [alex@msgpad.com]
+
+* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
+
+* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. [Sam Stephenson]
+
+* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). [Shugo Maeda]
+ # Obtain an exclusive lock on person 1 so we can safely increment visits.
+ Person.transaction do
+ # select * from people where id=1 for update
+ person = Person.find(1, :lock => true)
+ person.visits += 1
+ person.save!
+ end
+
+* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. [Jeremy Kemper]
+
+* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. [Jeremy Kemper]
+
+* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. [Nicholas Seckar]
+
+* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
+
+* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization [DHH]
+
+* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [hcatlin@gmail.com]. Example:
+
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+
+...is the same as:
+
+ Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
+
+ This makes it easier to pass in the options from a form or otherwise outside.
+
+
+* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com]
+
+* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 [alex@purefiction.net]
+
+* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 [greg@lapcominc.com]
+
+* Fixed problems with eager loading and counting on SQL Server #5212 [kajism@yahoo.com]
+
+* Fixed that count distinct should use the selected column even when using :include #5251 [anna@wota.jp]
+
+* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 [alex@purefiction.net]
+
+* Allow models to override to_xml. #4989 [Blair Zajac ]
+
+* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 [shimbo@is.naist.jp]
+
+* Records and arrays of records are bound as quoted ids. [Jeremy Kemper]
+ Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
+ Foo.find(:first, :conditions => ['bar_id = ?', bar])
+
+* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty [DHH]
+
+* Add a list of regexes assert_queries skips in the ActiveRecord test suite. [Rick]
+
+* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [josh@hasmanythrough.com]
+
+* Provide Association Extensions access to the instance that the association is being accessed from.
+ Closes #4433 [josh@hasmanythrough.com]
+
+* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
+
+* Add a quick note about :select and eagerly included associations. [Rick]
+
+* Add docs for the :as option in has_one associations. Closes #5144 [cdcarter@gmail.com]
+
+* Fixed that has_many collections shouldn't load the entire association to do build or create [DHH]
+
+* Added :allow_nil option for aggregations #5091 [ian.w.white@gmail.com]
+
+* Fix Oracle boolean support and tests. Closes #5139. [schoenm@earthlink.net]
+
+* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) [Jeremy Kemper]
+
+* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
+
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
+
+* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
+
+* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 [kajism@yahoo.com]
+
+* Allow :uniq => true with has_many :through associations. [Jeremy Kemper]
+
+* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
+
+* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
+
+* Replace superfluous name_to_class_name variant with camelize. [Marcel Molina Jr.]
+
+* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
+
+* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
+
+* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac ]
+
+* Update FrontBase adapter to check binding version. Closes #4920. [mlaster@metavillage.com]
+
+* New Frontbase connections don't start in auto-commit mode. Closes #4922. [mlaster@metavillage.com]
+
+* When grouping, use the appropriate option key. [Marcel Molina Jr.]
+
+* Only modify the sequence name in the FrontBase adapter if the FrontBase adapter is actually being used. [Marcel Molina Jr.]
+
+* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
+
+* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil ]
+
+* Fix syntax error in documentation. Closes #4679. [mislav@nippur.irb.hr]
+
+* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
+
+* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
+
+* Add support for :order option to with_scope. Closes #3887. [eric.daspet@survol.net]
+
+* Prettify output of schema_dumper by making things line up. Closes #4241 [Caio Chassot ]
+
+* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. [mlaster@metavillage.com]
+
+* Sybase Adapter type conversion cleanup. Closes #4736. [dev@metacasa.net]
+
+* Fix bug where calculations with long alias names return null. [Rick]
+
+* Raise error when trying to add to a has_many :through association. Use the Join Model instead. [Rick]
+
+ @post.tags << @tag # BAD
+ @post.taggings.create(:tag => @tag) # GOOD
+
+* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick]
+
+* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
+
+* Add ActiveRecord::Errors#to_xml [Jamis Buck]
+
+* Properly quote index names in migrations (closes #4764) [John Long]
+
+* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. [Rick]
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick]
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick]
+
+* DRY up association collection reader method generation. [Marcel Molina Jr.]
+
+* DRY up and tweak style of the validation error object. [Marcel Molina Jr.]
+
+* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick]
+
+ class Account < ActiveRecord::Base
+ validates_uniqueness_of :email, :case_sensitive => false
+ end
+
+* Allow multiple association extensions with :extend option (closes #4666) [Josh Susser]
+
+ class Account < ActiveRecord::Base
+ has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
+ end
+
+ *1.15.3* (March 12th, 2007)
+
+ * Allow a polymorphic :source for has_many :through associations. Closes #7143 [protocool]
+
+ * Consistently quote primary key column names. #7763 [toolmantim]
+
+ * Fixtures: fix YAML ordered map support. #2665 [Manuel Holtgrewe, nfbuckley]
+
+ * Fix has_many :through << with custom foreign keys. #6466, #7153 [naffis, Rich Collins]
+
+
+*1.15.2* (February 5th, 2007)
+
+* Pass a range in :conditions to use the SQL BETWEEN operator. #6974 [dcmanges]
+ Student.find(:all, :conditions => { :grade => 9..12 })
+
+* Don't create instance writer methods for class attributes. [Rick]
+
+* When dealing with SQLite3, use the table_info pragma helper, so that the bindings can do some translation for when sqlite3 breaks incompatibly between point releases. [Jamis Buck]
+
+* SQLServer: don't choke on strings containing 'null'. #7083 [Jakob S]
+
+* Consistently use LOWER() for uniqueness validations (rather than mixing with UPPER()) so the database can always use a functional index on the lowercased column. #6495 [Si]
+
+* MySQL: SET SQL_AUTO_IS_NULL=0 so 'where id is null' doesn't select the last inserted id. #6778 [Jonathan Viney, timc]
+
+* Fixtures use the table name and connection from set_fixture_class. #7330 [Anthony Eden]
+
+* SQLServer: quote table name in indexes query. #2928 [keithm@infused.org]
+
+
+*1.15.1* (January 17th, 2007)
+
+* Fix nodoc breaking of adapters
+
+
+*1.15.0* (January 16th, 2007)
+
+* [DOC] clear up some ambiguity with the way has_and_belongs_to_many creates the default join table name. #7072 [jeremymcanally]
+
+* change_column accepts :default => nil. Skip column options for primary keys. #6956, #7048 [dcmanges, Jeremy Kemper]
+
+* MySQL, PostgreSQL: change_column_default quotes the default value and doesn't lose column type information. #3987, #6664 [Jonathan Viney, manfred, altano@bigfoot.com]
+
+* Oracle: create_table takes a :sequence_name option to override the 'tablename_seq' default. #7000 [Michael Schoen]
+
+* MySQL: retain SSL settings on reconnect. #6976 [randyv2]
+
+* SQLServer: handle [quoted] table names. #6635 [rrich]
+
+* acts_as_nested_set works with single-table inheritance. #6030 [Josh Susser]
+
+* PostgreSQL, Oracle: correctly perform eager finds with :limit and :order. #4668, #7021 [eventualbuddha, Michael Schoen]
+
+* Fix the Oracle adapter for serialized attributes stored in CLOBs. Closes #6825 [mschoen, tdfowler]
+
+* [DOCS] Apply more documentation for ActiveRecord Reflection. Closes #4055 [Robby Russell]
+
+* [DOCS] Document :allow_nil option of #validate_uniqueness_of. Closes #3143 [Caio Chassot]
+
+* Bring the sybase adapter up to scratch for 1.2 release. [jsheets]
+
+* Oracle: fix connection reset failure. #6846 [leonlleslie]
+
+* Subclass instantiation doesn't try to explicitly require the corresponding subclass. #6840 [leei, Jeremy Kemper]
+
+* fix faulty inheritance tests and that eager loading grabs the wrong inheritance column when the class of your association is an STI subclass. Closes #6859 [protocool]
+
+* find supports :lock with :include. Check whether your database allows SELECT ... FOR UPDATE with outer joins before using. #6764 [vitaly, Jeremy Kemper]
+
+* Support nil and Array in :conditions => { attr => value } hashes. #6548 [Assaf, Jeremy Kemper]
+ find(:all, :conditions => { :topic_id => [1, 2, 3], :last_read => nil }
+
+* Quote ActiveSupport::Multibyte::Chars. #6653 [Julian Tarkhanov]
+
+* MySQL: detect when a NOT NULL column without a default value is misreported as default ''. Can't detect for string, text, and binary columns since '' is a legitimate default. #6156 [simon@redhillconsulting.com.au, obrie, Jonathan Viney, Jeremy Kemper]
+
+* validates_numericality_of uses \A \Z to ensure the entire string matches rather than ^ $ which may match one valid line of a multiline string. #5716 [Andreas Schwarz]
+
+* Oracle: automatically detect the primary key. #6594 [vesaria, Michael Schoen]
+
+* Oracle: to increase performance, prefetch 100 rows and enable similar cursor sharing. Both are configurable in database.yml. #6607 [philbogle@gmail.com, ray.fortna@jobster.com, Michael Schoen]
+
+* Firebird: decimal/numeric support. #6408 [macrnic]
+
+* Find with :include respects scoped :order. #5850
+
+* Dynamically generate reader methods for serialized attributes. #6362 [Stefan Kaes]
+
+* Deprecation: object transactions warning. [Jeremy Kemper]
+
+* has_one :dependent => :nullify ignores nil associates. #6528 [janovetz, Jeremy Kemper]
+
+* Oracle: resolve test failures, use prefetched primary key for inserts, check for null defaults, fix limited id selection for eager loading. Factor out some common methods from all adapters. #6515 [Michael Schoen]
+
+* Make add_column use the options hash with the Sqlite Adapter. Closes #6464 [obrie]
+
+* Document other options available to migration's add_column. #6419 [grg]
+
+* MySQL: all_hashes compatibility with old MysqlRes class. #6429, #6601 [Jeremy Kemper]
+
+* Fix has_many :through to add the appropriate conditions when going through an association using STI. Closes #5783. [Jonathan Viney]
+
+* fix select_limited_ids_list issues in postgresql, retain current behavior in other adapters [Rick]
+
+* Restore eager condition interpolation, document it's differences [Rick]
+
+* Don't rollback in teardown unless a transaction was started. Don't start a transaction in create_fixtures if a transaction is started. #6282 [Jacob Fugal, Jeremy Kemper]
+
+* Add #delete support to has_many :through associations. Closes #6049 [Martin Landers]
+
+* Reverted old select_limited_ids_list postgresql fix that caused issues in mysql. Closes #5851 [Rick]
+
+* Removes the ability for eager loaded conditions to be interpolated, since there is no model instance to use as a context for interpolation. #5553 [turnip@turnipspatch.com]
+
+* Added timeout option to SQLite3 configurations to deal more gracefully with SQLite3::BusyException, now the connection can instead retry for x seconds to see if the db clears up before throwing that exception #6126 [wreese@gmail.com]
+
+* Added update_attributes! which uses save! to raise an exception if a validation error prevents saving #6192 [jonathan]
+
+* Deprecated add_on_boundary_breaking (use validates_length_of instead) #6292 [BobSilva]
+
+* The has_many create method works with polymorphic associations. #6361 [Dan Peterson]
+
+* MySQL: introduce Mysql::Result#all_hashes to support further optimization. #5581 [Stefan Kaes]
+
+* save! shouldn't validate twice. #6324 [maiha, Bob Silva]
+
+* Association collections have an _ids reader method to match the existing writer for collection_select convenience (e.g. employee.task_ids). The writer method skips blank ids so you can safely do @employee.task_ids = params[:tasks] without checking every time for an empty list or blank values. #1887, #5780 [Michael Schuerig]
+
+* Add an attribute reader method for ActiveRecord::Base.observers [Rick Olson]
+
+* Deprecation: count class method should be called with an options hash rather than two args for conditions and joins. #6287 [Bob Silva]
+
+* has_one associations with a nil target may be safely marshaled. #6279 [norbauer, Jeremy Kemper]
+
+* Duplicate the hash provided to AR::Base#to_xml to prevent unexpected side effects [Koz]
+
+* Add a :namespace option to AR::Base#to_xml [Koz]
+
+* Deprecation tests. Remove warnings for dynamic finders and for the foo_count method if it's also an attribute. [Jeremy Kemper]
+
+* Mock Time.now for more accurate Touch mixin tests. #6213 [Dan Peterson]
+
+* Improve yaml fixtures error reporting. #6205 [Bruce Williams]
+
+* Rename AR::Base#quote so people can use that name in their models. #3628 [Koz]
+
+* Add deprecation warning for inferred foreign key. #6029 [Josh Susser]
+
+* Fixed the Ruby/MySQL adapter we ship with Active Record to work with the new authentication handshake that was introduced in MySQL 4.1, along with the other protocol changes made at that time #5723 [jimw@mysql.com]
+
+* Deprecation: use :dependent => :delete_all rather than :exclusively_dependent => true. #6024 [Josh Susser]
+
+* Optimistic locking: gracefully handle nil versions, treat as zero. #5908 [Tom Ward]
+
+* to_xml: the :methods option works on arrays of records. #5845 [Josh Starcher]
+
+* has_many :through conditions are sanitized by the associating class. #5971 [martin.emde@gmail.com]
+
+* Fix spurious newlines and spaces in AR::Base#to_xml output [Jamis Buck]
+
+* has_one supports the :dependent => :delete option which skips the typical callback chain and deletes the associated object directly from the database. #5927 [Chris Mear, Jonathan Viney]
+
+* Nested subclasses are not prefixed with the parent class' table_name since they should always use the base class' table_name. #5911 [Jonathan Viney]
+
+* SQLServer: work around bug where some unambiguous date formats are not correctly identified if the session language is set to german. #5894 [Tom Ward, kruth@bfpi]
+
+* Clashing type columns due to a sloppy join shouldn't wreck single-table inheritance. #5838 [Kevin Clark]
+
+* Fixtures: correct escaping of \n and \r. #5859 [evgeny.zislis@gmail.com]
+
+* Migrations: gracefully handle missing migration files. #5857 [eli.gordon@gmail.com]
+
+* MySQL: update test schema for MySQL 5 strict mode. #5861 [Tom Ward]
+
+* to_xml: correct naming of included associations. #5831 [josh.starcher@gmail.com]
+
+* Pushing a record onto a has_many :through sets the association's foreign key to the associate's primary key and adds it to the correct association. #5815, #5829 [josh@hasmanythrough.com]
+
+* Add records to has_many :through using <<, push, and concat by creating the association record. Raise if base or associate are new records since both ids are required to create the association. #build raises since you can't associate an unsaved record. #create! takes an attributes hash and creates the associated record and its association in a transaction. [Jeremy Kemper]
+
+ # Create a tagging to associate the post and tag.
+ post.tags << Tag.find_by_name('old')
+ post.tags.create! :name => 'general'
+
+ # Would have been:
+ post.taggings.create!(:tag => Tag.find_by_name('finally')
+ transaction do
+ post.taggings.create!(:tag => Tag.create!(:name => 'general'))
+ end
+
+* Cache nil results for :included has_one associations also. #5787 [Michael Schoen]
+
+* Fixed a bug which would cause .save to fail after trying to access a empty has_one association on a unsaved record. [Tobias Luetke]
+
+* Nested classes are given table names prefixed by the singular form of the parent's table name. [Jeremy Kemper]
+ Example: Invoice::Lineitem is given table name invoice_lineitems
+
+* Migrations: uniquely name multicolumn indexes so you don't have to. [Jeremy Kemper]
+ # people_active_last_name_index, people_active_deactivated_at_index
+ add_index :people, [:active, :last_name]
+ add_index :people, [:active, :deactivated_at]
+ remove_index :people, [:active, :last_name]
+ remove_index :people, [:active, :deactivated_at]
+
+ WARNING: backward-incompatibility. Multicolumn indexes created before this
+ revision were named using the first column name only. Now they're uniquely
+ named using all indexed columns.
+
+ To remove an old multicolumn index, remove_index :table_name, :first_column
+
+* Fix for deep includes on the same association. [richcollins@gmail.com]
+
+* Tweak fixtures so they don't try to use a non-ActiveRecord class. [Kevin Clark]
+
+* Remove ActiveRecord::Base.reset since Dispatcher doesn't use it anymore. [Rick Olson]
+
+* PostgreSQL: autodetected sequences work correctly with multiple schemas. Rely on the schema search_path instead of explicitly qualifying the sequence name with its schema. #5280 [guy.naor@famundo.com]
+
+* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
+
+* Cache nil results for has_one associations so multiple calls don't call the database. Closes #5757. [Michael A. Schoen]
+
+* Don't save has_one associations unnecessarily. #5735 [Jonathan Viney]
+
+* Refactor ActiveRecord::Base.reset_subclasses to #reset, and add global observer resetting. [Rick Olson]
+
+* Formally deprecate the deprecated finders. [Koz]
+
+* Formally deprecate rich associations. [Koz]
+
+* Fixed that default timezones for new / initialize should uphold utc setting #5709 [daniluk@yahoo.com]
+
+* Fix announcement of very long migration names. #5722 [blake@near-time.com]
+
+* The exists? class method should treat a string argument as an id rather than as conditions. #5698 [jeremy@planetargon.com]
+
+* Fixed to_xml with :include misbehaviors when invoked on array of model instances #5690 [alexkwolfe@gmail.com]
+
+* Added support for conditions on Base.exists? #5689 [Josh Peek]. Examples:
+
+ assert (Topic.exists?(:author_name => "David"))
+ assert (Topic.exists?(:author_name => "Mary", :approved => true))
+ assert (Topic.exists?(["parent_id = ?", 1]))
+
+* Schema dumper quotes date :default values. [Dave Thomas]
+
+* Calculate sum with SQL, not Enumerable on HasManyThrough Associations. [Dan Peterson]
+
+* Factor the attribute#{suffix} methods out of method_missing for easier extension. [Jeremy Kemper]
+
+* Patch sql injection vulnerability when using integer or float columns. [Jamis Buck]
+
+* Allow #count through a has_many association to accept :include. [Dan Peterson]
+
+* create_table rdoc: suggest :id => false for habtm join tables. [Zed Shaw]
+
+* PostgreSQL: return array fields as strings. #4664 [Robby Russell]
+
+* SQLServer: added tests to ensure all database statements are closed, refactored identity_insert management code to use blocks, removed update/delete rowcount code out of execute and into update/delete, changed insert to go through execute method, removed unused quoting methods, disabled pessimistic locking tests as feature is currently unsupported, fixed RakeFile to load sqlserver specific tests whether running in ado or odbc mode, fixed support for recently added decimal types, added support for limits on integer types. #5670 [Tom Ward]
+
+* SQLServer: fix db:schema:dump case-sensitivity. #4684 [Will Rogers]
+
+* Oracle: BigDecimal support. #5667 [schoenm@earthlink.net]
+
+* Numeric and decimal columns map to BigDecimal instead of Float. Those with scale 0 map to Integer. #5454 [robbat2@gentoo.org, work@ashleymoran.me.uk]
+
+* Firebird migrations support. #5337 [Ken Kunz ]
+
+* PostgreSQL: create/drop as postgres user. #4790 [mail@matthewpainter.co.uk, mlaster@metavillage.com]
+
+* PostgreSQL: correctly quote the ' in pk_and_sequence_for. #5462 [tietew@tietew.net]
+
+* PostgreSQL: correctly quote microseconds in timestamps. #5641 [rick@rickbradley.com]
+
+* Clearer has_one/belongs_to model names (account has_one :user). #5632 [matt@mattmargolis.net]
+
+* Oracle: use nonblocking queries if allow_concurrency is set, fix pessimistic locking, don't guess date vs. time by default (set OracleAdapter.emulate_dates = true for the old behavior), adapter cleanup. #5635 [schoenm@earthlink.net]
+
+* Fixed a few Oracle issues: Allows Oracle's odd date handling to still work consistently within #to_xml, Passes test that hardcode insert statement by dropping the :id column, Updated RUNNING_UNIT_TESTS with Oracle instructions, Corrects method signature for #exec #5294 [schoenm@earthlink.net]
+
+* Added :group to available options for finds done on associations #5516 [mike@michaeldewey.org]
+
+* Observers also watch subclasses created after they are declared. #5535 [daniels@pronto.com.au]
+
+* Removed deprecated timestamps_gmt class methods. [Jeremy Kemper]
+
+* rake build_mysql_database grants permissions to rails@localhost. #5501 [brianegge@yahoo.com]
+
+* PostgreSQL: support microsecond time resolution. #5492 [alex@msgpad.com]
+
+* Add AssociationCollection#sum since the method_missing invokation has been shadowed by Enumerable#sum.
+
+* Added find_or_initialize_by_X which works like find_or_create_by_X but doesn't save the newly instantiated record. [Sam Stephenson]
+
+* Row locking. Provide a locking clause with the :lock finder option or true for the default "FOR UPDATE". Use the #lock! method to obtain a row lock on a single record (reloads the record with :lock => true). [Shugo Maeda]
+ # Obtain an exclusive lock on person 1 so we can safely increment visits.
+ Person.transaction do
+ # select * from people where id=1 for update
+ person = Person.find(1, :lock => true)
+ person.visits += 1
+ person.save!
+ end
+
+* PostgreSQL: introduce allow_concurrency option which determines whether to use blocking or asynchronous #execute. Adapters with blocking #execute will deadlock Ruby threads. The default value is ActiveRecord::Base.allow_concurrency. [Jeremy Kemper]
+
+* Use a per-thread (rather than global) transaction mutex so you may execute concurrent transactions on separate connections. [Jeremy Kemper]
+
+* Change AR::Base#to_param to return a String instead of a Fixnum. Closes #5320. [Nicholas Seckar]
+
+* Use explicit delegation instead of method aliasing for AR::Base.to_param -> AR::Base.id. #5299 (skaes@web.de)
+
+* Refactored ActiveRecord::Base.to_xml to become a delegate for XmlSerializer, which restores sanity to the mega method. This refactoring also reinstates the opinions that type="string" is redundant and ugly and nil-differentiation is not a concern of serialization [DHH]
+
+* Added simple hash conditions to find that'll just convert hash to an AND-based condition string #5143 [hcatlin@gmail.com]. Example:
+
+ Person.find(:all, :conditions => { :last_name => "Catlin", :status => 1 }, :limit => 2)
+
+...is the same as:
+
+ Person.find(:all, :conditions => [ "last_name = ? and status = ?", "Catlin", 1 ], :limit => 2)
+
+ This makes it easier to pass in the options from a form or otherwise outside.
+
+
+* Fixed issues with BLOB limits, charsets, and booleans for Firebird #5194, #5191, #5189 [kennethkunz@gmail.com]
+
+* Fixed usage of :limit and with_scope when the association in scope is a 1:m #5208 [alex@purefiction.net]
+
+* Fixed migration trouble with SQLite when NOT NULL is used in the new definition #5215 [greg@lapcominc.com]
+
+* Fixed problems with eager loading and counting on SQL Server #5212 [kajism@yahoo.com]
+
+* Fixed that count distinct should use the selected column even when using :include #5251 [anna@wota.jp]
+
+* Fixed that :includes merged from with_scope won't cause the same association to be loaded more than once if repetition occurs in the clauses #5253 [alex@purefiction.net]
+
+* Allow models to override to_xml. #4989 [Blair Zajac ]
+
+* PostgreSQL: don't ignore port when host is nil since it's often used to label the domain socket. #5247 [shimbo@is.naist.jp]
+
+* Records and arrays of records are bound as quoted ids. [Jeremy Kemper]
+ Foo.find(:all, :conditions => ['bar_id IN (?)', bars])
+ Foo.find(:first, :conditions => ['bar_id = ?', bar])
+
+* Fixed that Base.find :all, :conditions => [ "id IN (?)", collection ] would fail if collection was empty [DHH]
+
+* Add a list of regexes assert_queries skips in the ActiveRecord test suite. [Rick]
+
+* Fix the has_and_belongs_to_many #create doesn't populate the join for new records. Closes #3692 [josh@hasmanythrough.com]
+
+* Provide Association Extensions access to the instance that the association is being accessed from.
+ Closes #4433 [josh@hasmanythrough.com]
+
+* Update OpenBase adaterp's maintainer's email address. Closes #5176. [Derrick Spell]
+
+* Add a quick note about :select and eagerly included associations. [Rick]
+
+* Add docs for the :as option in has_one associations. Closes #5144 [cdcarter@gmail.com]
+
+* Fixed that has_many collections shouldn't load the entire association to do build or create [DHH]
+
+* Added :allow_nil option for aggregations #5091 [ian.w.white@gmail.com]
+
+* Fix Oracle boolean support and tests. Closes #5139. [schoenm@earthlink.net]
+
+* create! no longer blows up when no attributes are passed and a :create scope is in effect (e.g. foo.bars.create! failed whereas foo.bars.create!({}) didn't.) [Jeremy Kemper]
+
+* Call Inflector#demodulize on the class name when eagerly including an STI model. Closes #5077 [info@loobmedia.com]
+
+* Preserve MySQL boolean column defaults when changing a column in a migration. Closes #5015. [pdcawley@bofh.org.uk]
+
+* PostgreSQL: migrations support :limit with :integer columns by mapping limit < 4 to smallint, > 4 to bigint, and anything else to integer. #2900 [keegan@thebasement.org]
+
+* Dates and times interpret empty strings as nil rather than 2000-01-01. #4830 [kajism@yahoo.com]
+
+* Allow :uniq => true with has_many :through associations. [Jeremy Kemper]
+
+* Ensure that StringIO is always available for the Schema dumper. [Marcel Molina Jr.]
+
+* Allow AR::Base#to_xml to include methods too. Closes #4921. [johan@textdrive.com]
+
+* Remove duplicate fixture entry in comments.yml. Closes #4923. [Blair Zajac ]
+
+* When grouping, use the appropriate option key. [Marcel Molina Jr.]
+
+* Add support for FrontBase (http://www.frontbase.com/) with a new adapter thanks to the hard work of one Mike Laster. Closes #4093. [mlaster@metavillage.com]
+
+* Add warning about the proper way to validate the presence of a foreign key. Closes #4147. [Francois Beausoleil ]
+
+* Fix syntax error in documentation. Closes #4679. [mislav@nippur.irb.hr]
+
+* Add Oracle support for CLOB inserts. Closes #4748. [schoenm@earthlink.net sandra.metz@duke.edu]
+
+* Various fixes for sqlserver_adapter (odbc statement finishing, ado schema dumper, drop index). Closes #4831. [kajism@yahoo.com]
+
+* Add support for :order option to with_scope. Closes #3887. [eric.daspet@survol.net]
+
+* Prettify output of schema_dumper by making things line up. Closes #4241 [Caio Chassot ]
+
+* Make build_postgresql_databases task make databases owned by the postgres user. Closes #4790. [mlaster@metavillage.com]
+
+* Sybase Adapter type conversion cleanup. Closes #4736. [dev@metacasa.net]
+
+* Fix bug where calculations with long alias names return null. [Rick]
+
+* Raise error when trying to add to a has_many :through association. Use the Join Model instead. [Rick]
+
+ @post.tags << @tag # BAD
+ @post.taggings.create(:tag => @tag) # GOOD
+
+* Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick]
+
+* Add ActiveRecord::Errors#to_xml [Jamis Buck]
+
+* Properly quote index names in migrations (closes #4764) [John Long]
+
+* Fix the HasManyAssociation#count method so it uses the new ActiveRecord::Base#count syntax, while maintaining backwards compatibility. [Rick]
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick]
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick]
+
+* Add :case_sensitive option to validates_uniqueness_of (closes #3090) [Rick]
+
+ class Account < ActiveRecord::Base
+ validates_uniqueness_of :email, :case_sensitive => false
+ end
+
+* Allow multiple association extensions with :extend option (closes #4666) [Josh Susser]
+
+ class Account < ActiveRecord::Base
+ has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
+ end
+
+
+*1.14.4* (August 8th, 2006)
+
+* Add warning about the proper way to validate the presence of a foreign key. #4147 [Francois Beausoleil ]
+
+* Fix syntax error in documentation. #4679 [mislav@nippur.irb.hr]
+
+* Update inconsistent migrations documentation. #4683 [machomagna@gmail.com]
+
+
+*1.14.3* (June 27th, 2006)
+
+* Fix announcement of very long migration names. #5722 [blake@near-time.com]
+
+* Update callbacks documentation. #3970 [Robby Russell ]
+
+* Properly quote index names in migrations (closes #4764) [John Long]
+
+* Ensure that Associations#include_eager_conditions? checks both scoped and explicit conditions [Rick]
+
+* Associations#select_limited_ids_list adds the ORDER BY columns to the SELECT DISTINCT List for postgresql. [Rick]
+
+
+*1.14.2* (April 9th, 2006)
+
+* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen]
+
+
+*1.14.1* (April 6th, 2006)
+
+* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. [Nicholas Seckar]
+
+* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) [Lars Pind]
+
+* Enable Limit/Offset in Calculations (closes #4558) [lmarlow@yahoo.com]
+
+* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) [Rick]
+
+* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 [lagroue@free.fr]
+
+* Allow AR::Base#respond_to? to behave when @attributes is nil [zenspider]
+
+* Support eager includes when going through a polymorphic has_many association. [Rick]
+
+* Added support for eagerly including polymorphic has_one associations. (closes #4525) [Rick]
+
+ class Post < ActiveRecord::Base
+ has_one :tagging, :as => :taggable
+ end
+
+ Post.find :all, :include => :tagging
+
+* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick]
+
+* Added support for going through a polymorphic has_many association: (closes #4401) [Rick]
+
+ class PhotoCollection < ActiveRecord::Base
+ has_many :photos, :as => :photographic
+ belongs_to :firm
+ end
+
+ class Firm < ActiveRecord::Base
+ has_many :photo_collections
+ has_many :photos, :through => :photo_collections
+ end
+
+* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. [ruben.nine@gmail.com]
+
+* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. [Florian Weber]
+
+* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) [Stefan]
+
+* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) [Rick]
+
+* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]
+
+* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) [DHH]
+
+* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). [Marcel Mollina Jr.]
+
+* Fixed broken OCIAdapter #4457 [schoenm@earthlink.net]
+
+
+*1.14.0* (March 27th, 2006)
+
+* Replace 'rescue Object' with a finer grained rescue. Closes #4431. [Nicholas Seckar]
+
+* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table [Rick]
+
+* Add support for :include to with_scope [andrew@redlinesoftware.com]
+
+* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [schoenm@earthlink.net]
+
+* Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com]
+
+* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 [Rick]
+
+* Fixed issue that kept :select options from being scoped [Rick]
+
+* Fixed db_schema_import when binary types are present #3101 [DHH]
+
+* Fixed that MySQL enums should always be returned as strings #3501 [DHH]
+
+* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson]
+
+ class Connection < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :channel
+ end
+
+ class Channel < ActiveRecord::Base
+ has_many :connections
+ has_many :contacts, :through => :connections, :class_name => 'User' # OLD
+ has_many :contacts, :through => :connections, :source => :user # NEW
+ end
+
+* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de]
+
+* Allow overriding of find parameters in scoped has_many :through calls [Rick Olson]
+
+ In this example, :include => false disables the default eager association from loading. :select changes the standard
+ select clause. :joins specifies a join that is added to the end of the has_many :through query.
+
+ class Post < ActiveRecord::Base
+ has_many :tags, :through => :taggings, :include => :tagging do
+ def add_joins_and_select
+ find :all, :select => 'tags.*, authors.id as author_id', :include => false,
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ end
+ end
+ end
+
+* Fixed that schema changes while the database was open would break any connections to a SQLite database (now we reconnect if that error is throw) [DHH]
+
+* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [jonathan@bluewire.net.nz]
+
+* Quit ignoring default :include options in has_many :through calls [Mark James]
+
+* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) [jonathan@bluewire.net.nz]
+
+* Eager Loading support added for has_many :through => :has_many associations (see below). [Rick Olson]
+
+* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_many :invoices, :through => :clients
+ end
+
+ class Client < ActiveRecord::Base
+ belongs_to :firm
+ has_many :invoices
+ end
+
+ class Invoice < ActiveRecord::Base
+ belongs_to :client
+ end
+
+* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) [josh@hasmanythrough.com]
+
+* Fixed has_many :through to include :conditions set on the :through association. closes #4020 [jonathan@bluewire.net.nz]
+
+* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) [andylien@gmail.com / Rick]
+
+* SQL Server adapter gets some love #4298 [rtomayko@gmail.com]
+
+* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 [derrickspell@cdmplus.com]
+
+* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
+
+ Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
+
+* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. [Rick]
+
+* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 [Koz]
+
+* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 [Koz]
+
+* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 [Tom ward]
+
+* Fixed that Migration#execute would have the table name prefix appended to its query #4110 [mark.imbriaco@pobox.com]
+
+* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) [Jamis Buck]
+
+* Use association's :conditions when eager loading. [jeremyevans0@gmail.com] #4144
+
+* Alias the has_and_belongs_to_many join table on eager includes. #4106 [jeremyevans0@gmail.com]
+
+ This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
+
+ Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
+
+* Oracle adapter gets some love #4230 [schoenm@earthlink.net]
+
+ * Changes :text to CLOB rather than BLOB [Moses Hohman]
+ * Fixes an issue with nil numeric length/scales (several)
+ * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
+ * Tweaks a unit test to get it all green again
+ * Adds support for #current_database
+
+* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 [Rick Olson]
+
+ class CachedModel < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class Post < CachedModel
+ end
+
+ CachedModel.abstract_class?
+ => true
+
+ Post.abstract_class?
+ => false
+
+ Post.base_class
+ => Post
+
+ Post.table_name
+ => 'posts'
+
+* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson]
+
+ class Foo < ActiveRecord::Base
+ has_many :attachments, :as => :attachable, :dependent => :delete_all
+ end
+
+* Nicer error message on has_many :through when :through reflection can not be found. #4042 [court3nay@gmail.com]
+
+* Upgrade to Transaction::Simple 1.3 [Jamis Buck]
+
+* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson]
+
+* Allow ordering of calculated results and/or grouped fields in calculations [solo@gatelys.com]
+
+* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 [johan@johansorensen.com]
+
+* Dynamically set allow_concurrency. #4044 [Stefan Kaes]
+
+* Added Base#to_xml that'll turn the current record into a XML representation [DHH]. Example:
+
+ topic.to_xml
+
+ ...returns:
+
+
+
+ The First Topic
+ David
+ 1
+ false
+ 0
+ 2000-01-01 08:28:00
+ 2003-07-16 09:28:00
+ Have a nice day
+ david@loudthinking.com
+
+ 2004-04-15
+
+
+ ...and you can configure with:
+
+ topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
+
+ ...that'll return:
+
+
+ The First Topic
+ David
+ false
+ Have a nice day
+ david@loudthinking.com
+
+ 2004-04-15
+
+
+ You can even do load first-level associations as part of the document:
+
+ firm.to_xml :include => [ :account, :clients ]
+
+ ...that'll return something like:
+
+
+
+ 1
+ 1
+ 37signals
+
+
+ 1
+ Summit
+
+
+ 1
+ Microsoft
+
+
+
+ 1
+ 50
+
+
+
+* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
+
+* Documentation fixes for :dependent [robby@planetargon.com]
+
+* Stop the MySQL adapter crashing when views are present. #3782 [Jonathan Viney]
+
+* Don't classify the belongs_to class, it is already singular #4117 [keithm@infused.org]
+
+* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. [Rick Olson]
+
+* Fix quoting of inheritance column for STI eager loading #4098 [Jonathan Viney ]
+
+* Added smarter table aliasing for eager associations for multiple self joins #3580 [Rick Olson]
+
+ * The first time a table is referenced in a join, no alias is used.
+ * After that, the parent class name and the reflection name are used.
+
+ Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
+
+ * Any additional join references get a numerical suffix like '_2', '_3', etc.
+
+* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
+
+* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
+
+ # cascaded in two levels
+ >> Author.find(:all, :include=>{:posts=>:comments})
+ => authors
+ +- posts
+ +- comments
+
+ # cascaded in two levels and normal association
+ >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in two levels with two has_many associations
+ >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in three levels
+ >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
+ => companies
+ +- groups
+ +- members
+ +- favorites
+
+* Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
+
+* Make migrations verbose [Jamis Buck]
+
+* Make counter_cache work with polymorphic belongs_to [Jamis Buck]
+
+* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp]
+
+* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
+
+ * Does not support DATE SQL column types; use DATETIME instead.
+ * Date columns on HABTM join tables are returned as String, not Time.
+ * Insertions are potentially broken for :polymorphic join tables
+ * BLOB column access not yet fully supported
+
+* Clear stale, cached connections left behind by defunct threads. [Jeremy Kemper]
+
+* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. [Jeremy Kemper]
+
+* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [schoenm@earthlink.net]
+
+* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [DHH]
+
+* Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes]
+
+* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. [Kevin Clark]
+
+* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 [dblack@wobblini.net]
+
+* Define attribute query methods to avoid method_missing calls. #3677 [jonathan@bluewire.net.nz]
+
+* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 [Simon Stapleton, Tom Ward]
+
+* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
+
+ Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
+
+ # inner rule is used. (all previous parameters are ignored)
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
+ end
+
+ # parameters are merged
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
+ end
+ end
+
+* Fixed db2 connection with empty user_name and auth options #3622 [phurley@gmail.com]
+
+* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 [Masao Mutoh]
+
+* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 [lars@pind.com]
+
+* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
+
+ Person.average :age
+ Person.minimum :age
+ Person.maximum :age
+ Person.sum :salary, :group => :last_name
+
+* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 [contact@lukeredpath.co.uk]
+
+* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. [Jeremy Kemper]
+
+* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 [Aggregated by schoenm@earthlink.net]
+
+* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 [rubyonrails@atyp.de]
+
+* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 [skaes]
+
+* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first [Bob Silva]
+
+* Allow :include to be used with has_many :through associations #3611 [Michael Schoen]
+
+* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 [Blair Zajac]
+
+* SQLServer: more compatible limit/offset emulation. #3779 [Tom Ward]
+
+* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 [Rick Olson]
+
+* PostgreSQL: correctly parse negative integer column defaults. #3776 [bellis@deepthought.org]
+
+* Fix problems with count when used with :include [Jeremy Hopple and Kevin Clark]
+
+* ActiveRecord::RecordInvalid now states which validations failed in its default error message [Tobias Luetke]
+
+* Using AssociationCollection#build with arrays of hashes should call build, not create [DHH]
+
+* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. [Nicholas Seckar]
+
+* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 [Maik Schmidt]
+
+* Support the :column option for remove_index with the PostgreSQL adapter. #3661 [shugo@ruby-lang.org]
+
+* Add documentation for add_index and remove_index. #3600 [Manfred Stienstra ]
+
+* If the OCI library is not available, raise an exception indicating as much. #3593 [schoenm@earthlink.net]
+
+* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. [Rick Olson]
+
+* Make dynamic finders honor additional passed in :conditions. #3569 [Oleg Pudeyev , Marcel Molina Jr.]
+
+* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. [Nicholas Seckar]
+
+* Make .count work for has_many associations with multi line finder sql [schoenm@earthlink.net]
+
+* Add AR::Base.base_class for querying the ancestor AR::Base subclass [Jamis Buck]
+
+* Allow configuration of the column used for optimistic locking [wilsonb@gmail.com]
+
+* Don't hardcode 'id' in acts as list. [ror@philippeapril.com]
+
+* Fix date errors for SQLServer in association tests. #3406 [kevin.clark@gmal.com]
+
+* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
+
+* Disambiguate table names for columns in validates_uniquness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
+
+* .with_scope imposed create parameters now bypass attr_protected [Tobias Luetke]
+
+* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. [Marcel Molina Jr.]
+
+* Multiple enhancements and adjustments to DB2 adaptor. #3377 [contact@maik-schmidt.de]
+
+* Sanitize scoped conditions. [Marcel Molina Jr.]
+
+* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) [DHH]
+
+* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. [TobiasLuetke]
+
+* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. [TobiasLuetke]
+
+* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
+
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
+
+* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Luetke]
+
+* made method missing delegation to class methods on relation target work on :through associations. [Tobias Luetke]
+
+* made .find() work on :through relations. [Tobias Luetke]
+
+* Fix typo in association docs. #3296. [Blair Zajac]
+
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Luetke]
+
+*1.13.2* (December 13th, 2005)
+
+* Become part of Rails 1.0
+
+* MySQL: allow encoding option for mysql.rb driver. [Jeremy Kemper]
+
+* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [DHH]. Example:
+
+ class Post
+ has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
+ end
+
+ post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
+ post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
+ post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
+
+* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations [DHH]
+
+* MySQL: fixes for the bundled mysql.rb driver. #3160 [Justin Forder]
+
+* SQLServer: fix obscure optimistic locking bug. #3068 [kajism@yahoo.com]
+
+* SQLServer: support uniqueidentifier columns. #2930 [keithm@infused.org]
+
+* SQLServer: cope with tables names qualified by owner. #3067 [jeff@ministrycentered.com]
+
+* SQLServer: cope with columns with "desc" in the name. #1950 [Ron Lusk, Ryan Tomayko]
+
+* SQLServer: cope with primary keys with "select" in the name. #3057 [rdifrango@captechventures.com]
+
+* Oracle: active? performs a select instead of a commit. #3133 [Michael Schoen]
+
+* MySQL: more robust test for nullified result hashes. #3124 [Stefan Kaes]
+
+* Reloading an instance refreshes its aggregations as well as its associations. #3024 [François Beausoleil]
+
+* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond]
+
+* PostgreSQL: more robust sequence name discovery. #3087 [Rick Olson]
+
+* Oracle: use syntax compatible with Oracle 8. #3131 [Michael Schoen]
+
+* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper]
+
+* Added preliminary support for polymorphic associations [DHH]
+
+* Added preliminary support for join models [DHH]
+
+* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]
+
+* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz ]
+
+* Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz ]
+
+* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper]
+
+* Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen]
+
+* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com]
+
+* Reloading a model doesn't lose track of its connection. #2996 [junk@miriamtech.com, Jeremy Kemper]
+
+* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen]
+
+* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 [Shugo Maeda]
+
+* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. [Jeremy Kemper]
+
+* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda]
+
+* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.]
+
+* Correct boolean handling in generated reader methods. #2945 [don.park@gmail.com, Stefan Kaes]
+
+* Don't generate read methods for columns whose names are not valid ruby method names. #2946 [Stefan Kaes]
+
+* Document :force option to create_table. #2921 [Blair Zajac ]
+
+* Don't add the same conditions twice in has_one finder sql. #2916 [Jeremy Evans]
+
+* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
+
+* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz ]
+
+* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward ]
+
+* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com]
+
+* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 [kajism@yahoo.com, Tom Ward ]
+
+* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen ]
+
+* Correct documentation for Base.delete_all. #1568 [Newhydra]
+
+* Oracle: test case for column default parsing. #2788 [Michael Schoen ]
+
+* Update documentation for Migrations. #2861 [Tom Werner ]
+
+* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper]
+
+* Oracle: Much faster column reflection. #2848 [Michael Schoen ]
+
+* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. [Jeremy Kemper]
+
+* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. [Jeremy Kemper]
+
+* PostgreSQL: correctly discover custom primary key sequences. #2594 [Blair Zajac , meadow.nnick@gmail.com, Jeremy Kemper]
+
+* SQLServer: don't report limits for unsupported field types. #2835 [Ryan Tomayko]
+
+* Include the Enumerable module in ActiveRecord::Errors. [Rick Bradley ]
+
+* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 [rubyonrails@atyp.de]
+
+* Don't cast nil or empty strings to a dummy date. #2789 [Rick Bradley ]
+
+* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 [rephorm@rephorm.com]
+
+* Fix sqlite adaptor's detection of missing dbfile or database declaration. [Nicholas Seckar]
+
+* Fixed acts_as_list for definitions without an explicit :order #2803 [jonathan@bluewire.net.nz]
+
+* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. [tommy@tmtm.org, akuroda@gmail.com, Doug Fales , Jeremy Kemper]
+
+* Correct handling of complex order clauses with SQL Server limit emulation. #2770 [Tom Ward , Matt B.]
+
+* Correct whitespace problem in Oracle default column value parsing. #2788 [rick@rickbradley.com]
+
+* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper]
+
+* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper]
+
+* More compatible Oracle column reflection. #2771 [Ryan Davis , Michael Schoen ]
+
+
+*1.13.0* (November 7th, 2005)
+
+* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko]
+
+* Added :include as an option for association declarations [DHH]. Example:
+
+ has_many :posts, :include => [ :author, :comments ]
+
+* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
+
+ Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
+ # Find where name = ? and active=true
+ Comment.find :all, :conditions => ['name = ?', name]
+ # Create comment associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko]
+
+* Added constrain scoping for creates using a hash of attributes bound to the :creation key [DHH]. Example:
+
+ Comment.constrain(:creation => { :post_id => 5 }) do
+ # Associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+ This is rarely used directly, but allows for find_or_create on associations. So you can do:
+
+ # If the tag doesn't exist, a new one is created that's associated with the person
+ person.tags.find_or_create_by_name("Summer")
+
+* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [DHH]. Example:
+
+ # No 'Summer' tag exists
+ Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+
+ # Now the 'Summer' tag does exist
+ Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
+
+* Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example:
+
+ class Account < ActiveRecord::Base
+ has_many :people do
+ def find_or_create_by_name(name)
+ first_name, *last_name = name.split
+ last_name = last_name.join " "
+
+ find_or_create_by_first_name_and_last_name(first_name, last_name)
+ end
+ end
+ end
+
+ person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
+ person.first_name # => "David"
+ person.last_name # => "Heinemeier Hansson"
+
+ Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
+
+* Omit internal dtproperties table from SQLServer table list. #2729 [rtomayko@gmail.com]
+
+* Quote column names in generated SQL. #2728 [rtomayko@gmail.com]
+
+* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper]
+
+* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper]
+
+* Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley ]
+
+* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
+
+* Added migration support for Oracle #2647 [Michael Schoen]
+
+* Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen ]
+
+* Fixed SQL Server adapter to pass even more tests and do even better #2634 [rtomayko@gmail.com]
+
+* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward]
+
+* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
+
+* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
+
+* Constraints are cloned so they can't be inadvertently modified while they're
+in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper ]
+
+* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com]
+
+* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [schoenm@earthlink.net]
+
+* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt]
+
+
+*1.12.2* (October 26th, 2005)
+
+* Allow symbols to rename columns when using SQLite adapter. #2531 [kevin.clark@gmail.com]
+
+* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell ]
+
+* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [skaes@web.de]
+
+* Fixed Association#clear for associations which have not yet been accessed. #2524 [Patrick Lenz ]
+
+* HABTM finders shouldn't return readonly records. #2525 [Patrick Lenz ]
+
+* Make all tests runnable on their own. #2521. [Blair Zajac ]
+
+
+*1.12.1* (October 19th, 2005)
+
+* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
+
+* Correct PostgreSQL primary key sequence detection. #2507 [tmornini@infomania.com]
+
+* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
+
+
+*1.12.0* (October 16th, 2005)
+
+* Update/clean up documentation (rdoc)
+
+* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 [Rick Olson , Robby Russell ]
+
+* Change default logging colors to work on both white and black backgrounds. [Sam Stephenson]
+
+* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
+
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell ]
+
+* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
+
+* Avoid memleak in dev mode when using fcgi
+
+* Simplified .clear on active record associations by using the existing delete_records method. #1906 [Caleb ]
+
+* Delegate access to a customized primary key to the conventional id method. #2444. [Blair Zajac ]
+
+* Fix errors caused by assigning a has-one or belongs-to property to itself
+
+* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped [Sam Stephenson]
+
+* Update DB2 adapter. #2206. [contact@maik-schmidt.de]
+
+* Corrections to SQLServer native data types. #2267. [rails.20.clarry@spamgourmet.com]
+
+* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
+
+* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. [Blair Zajac ]
+
+* Misc doc fixes (typos/grammar/etc.). #2430. [coffee2code]
+
+* Add test coverage for content_columns. #2432. [coffee2code]
+
+* Speed up for unthreaded environments. #2431. [skaes@web.de]
+
+* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. [skaes@web.de]
+
+* Speed up the setting of table_name. #2428. [skaes@web.de]
+
+* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. [skaes@web.de]
+
+* Fix typo of 'constrains' to 'contraints'. #2069. [Michael Schuerig ]
+
+* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. [skaes@web.de]
+
+* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. [Michael Schuerig ]
+
+* Add geometric type for postgresql adapter. #2233 [akaspick@gmail.com]
+
+* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. [skaes@web.de]
+
+* Add convenience predicate methods on Column class. In partial fullfilment of #1236. [skaes@web.de]
+
+* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 [Chad Fowler , Nicholas Seckar]
+
+* Added :force option to create_table that'll try to drop the table if it already exists before creating
+
+* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. [Nicholas Seckar]
+
+* Use foreign_key inflection uniformly. #2156 [Blair Zajac ]
+
+* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 [joergd@pobox.com, ObieFernandez ]
+
+* Returning false from before_destroy should cancel the action. #1829 [Jeremy Huffman]
+
+* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 [mat ]
+
+* Extensive documentation for the abstract database adapter. #2250 [François Beausoleil ]
+
+* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 [jay@jay.fm, Blair Zajac ]
+
+* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 [Manuel Holtgrewe ]
+
+* Make update_attribute use the same writer method that update_attributes uses.
+ #2237 [trevor@protocool.com]
+
+* Make migrations honor table name prefixes and suffixes. #2298 [Jakob S, Marcel Molina]
+
+* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 [dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org]
+
+* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 [chris@chrisbrinker.com]
+
+* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
+
+* Added new symbol-driven approach to activating observers with Base#observers= [DHH]. Example:
+
+ ActiveRecord::Base.observers = :cacher, :garbage_collector
+
+* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 [solo@gatelys.com]
+
+* Wrap :conditions in parentheses to prevent problems with OR's #1871 [Jamis Buck]
+
+* Allow the postgresql adapter to work with the SchemaDumper. [Jamis Buck]
+
+* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. [Jamis Buck]
+
+* Fixed migrations for Windows when using more than 10 [David Naseby]
+
+* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 [Florian Weber]
+
+* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 [Tobias Luetke]
+
+* Improved migrations' behavior when the schema_info table is empty. [Nicholas Seckar]
+
+* Fixed that Observers didn't observe sub-classes #627 [Florian Weber]
+
+* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 [Marcel Molina]
+
+* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 [skae]
+
+* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 [Florian Weber]
+
+* Added better exception error when unknown column types are used with migrations #1814 [fbeausoleil@ftml.net]
+
+* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 [kajism@yahoo.com]
+
+* Fixed comparison of Active Record objects so two new objects are not equal #2099 [deberg]
+
+* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 [Tom Ward]
+
+* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 [coffee2code]
+
+* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 [sd@notso.net]
+
+* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
+
+* Make sure the schema_info table is created before querying the current version #1903
+
+* Fixtures ignore table name prefix and suffix #1987 [Jakob S]
+
+* Add documentation for index_type argument to add_index method for migrations #2005 [blaine@odeo.com]
+
+* Modify read_attribute to allow a symbol argument #2024 [Ken Kunz]
+
+* Make destroy return self #1913 [sebastian.kanthak@muehlheim.de]
+
+* Fix typo in validations documentation #1938 [court3nay]
+
+* Make acts_as_list work for insert_at(1) #1966 [hensleyl@papermountain.org]
+
+* Fix typo in count_by_sql documentation #1969 [Alexey Verkhovsky]
+
+* Allow add_column and create_table to specify NOT NULL #1712 [emptysands@gmail.com]
+
+* Fix create_table so that id column is implicitly added [Rick Olson]
+
+* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
+
+* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
+
+* Fixed the handling of camelCase columns names in Oracle #1798
+
+* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
+
+* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
+
+* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 [Tobias Luetke]
+
+ class Comment < AR:B
+ def self.search(q)
+ find(:all, :conditions => ["body = ?", q])
+ end
+ end
+
+ class Post < AR:B
+ has_many :comments
+ end
+
+ Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
+
+ NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
+ by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
+ details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
+ noticed :)
+
+* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 [Sam Stephenson]
+
+* Remove extra definition of supports_migrations? from abstract_adaptor.rb [Nicholas Seckar]
+
+* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
+
+* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 [Maik Schmidt]
+
+* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 [Stefan Kaes]
+
+
+*1.11.1* (11 July, 2005)
+
+* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 [Rick Olsen]
+
+* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 [tyler@kianta.com]
+
+* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 [Sam Stephenson]
+
+* Correct reflected table name for singular associations. #1688 [court3nay@gmail.com]
+
+* Fixed optimistic locking with SQL Server #1660 [tom@popdog.net]
+
+* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
+
+* Added better error message for "packets out of order" #1630 [courtenay]
+
+* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
+
+
+*1.11.0* (6 July, 2005)
+
+* Fixed that Yaml error message in fixtures hid the real error #1623 [Nicholas Seckar]
+
+* Changed logging of SQL statements to use the DEBUG level instead of INFO
+
+* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Luetke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
+
+* Added callback hooks to association collections #1549 [Florian Weber]. Example:
+
+ class Project
+ has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
+
+ def evaluate_velocity(developer)
+ ...
+ end
+ end
+
+ ..raising an exception will cause the object not to be added (or removed, with before_remove).
+
+
+* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry]
+
+* Fixed Base#write_attribute to work with both symbols and strings #1190 [Paul Legato]
+
+* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 [Florian Weber]
+
+* Speed up ActiveRecord#method_missing for the common case (read_attribute).
+
+* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 [skaes@web.de]
+
+* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 [Chris McGrath]
+
+* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 [raidel@onemail.at]
+
+* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 [michael@schuerig.de]
+
+* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]
+
+* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry]
+
+* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net]
+
+* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl]
+
+* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
+
+ Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
+
+ ...should instead be:
+
+ Developer.find(
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :conditions => 'project_id=1'
+ )
+
+* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina]
+
+* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 [cluon]
+
+* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
+
+* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 [Alisdair McDiarmid]
+
+* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+
+* Allow any Enumerable, not just Array, to work as bind variables #1344 [Jeremy Kemper]
+
+* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
+ Example:
+
+ david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
+ david.save
+
+ If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
+ project is saved when david.save is called.
+
+ Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
+
+ david.project_ids = [1, 5, 7]
+
+* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com]
+
+* Fixed sanitized conditions for has_many finder method. #1281 [jackc@hylesanderson.com, pragdave, Tobias Luetke]
+
+* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 [dave@cherryville.org]
+
+* Corrected @@configurations typo #1410 [david@ruppconsulting.com]
+
+* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
+
+* Allow before/after update hooks to work on models using optimistic locking
+
+* Eager loading of dependent has_one associations won't delete the association #1212
+
+* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
+
+* Using transactional fixtures now causes the data to be loaded only once.
+
+* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
+
+ fixtures :web_sites
+
+ def test_something
+ assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+ end
+
+* Added DoubleRenderError exception that'll be raised if render* is called twice #518 [Nicholas Seckar]
+
+* Fixed exceptions occuring after render has been called #1096 [Nicholas Seckar]
+
+* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
+
+* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
+
+* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
+
+ Conditional validations such as the following are made possible:
+ validates_numericality_of :income, :if => :employed?
+
+ Conditional validations can also solve the salted login generator problem:
+ validates_confirmation_of :password, :if => :new_password?
+
+ Using blocks:
+ validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
+
+* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net]
+
+* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson]
+
+* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 [Jeremy Kemper]
+
+* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
+
+* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
+
+* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
+
+ # SELECT * FROM topics WHERE title IN ('First', 'Second')
+ Topic.find_all_by_title(["First", "Second"])
+
+* Added compatibility with camelCase column names for dynamic finders #533 [Dee.Zsombor]
+
+* Fixed extraneous comma in count() function that made it not work with joins #1156 [jarkko/Dee.Zsombor]
+
+* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 [Alisdair McDiarmid]
+
+* Fixed that validate_length_of lost :on option when :within was specified #1195 [jhosteny@mac.com]
+
+* Added encoding and min_messages options for PostgreSQL #1205 [shugo]. Configuration example:
+
+ development:
+ adapter: postgresql
+ database: rails_development
+ host: localhost
+ username: postgres
+ password:
+ encoding: UTF8
+ min_messages: ERROR
+
+* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 [Jamis Buck]
+
+* Added validates_exclusion_of as a negative of validates_inclusion_of
+
+* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
+
+
+*1.10.1* (20th April, 2005)
+
+* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
+
+* Fixed order of loading in eager associations
+
+* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
+
+
+*1.10.0* (19th April, 2005)
+
+* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
+
+ for post in Post.find(:all, :limit => 100)
+ puts "Post: " + post.title
+ puts "Written by: " + post.author.name
+ puts "Last comment on: " + post.comments.first.created_on
+ end
+
+ This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
+
+ for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+
+ ...and the number of database queries needed is now 1.
+
+* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
+
+ Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(:first, :order => "created_on DESC", :offset => 5)
+ Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ Person.find(:all, :offset => 10, :limit => 10)
+
+* Added acts_as_nested_set #1000 [wschenk]. Introduction:
+
+ This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
+ the added feature that you can select the children and all of it's descendants with
+ a single query. A good use case for this is a threaded post system, where you want
+ to display every reply to a comment without multiple selects.
+
+* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid [After much pestering from Dave Thomas]
+
+* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
+
+* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
+
+* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com]
+
+* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher]
+
+* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle]
+
+* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com]
+
+* Added insert_at(position) to acts_as_list #1083 [DeLynnB]
+
+* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
+
+* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com]
+
+* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org]
+
+* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
+
+* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt]
+
+* Added quoting of column names for fixtures #997 [jcfischer@gmail.com]
+
+* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis]
+
+* Fixed that benchmarking times for rendering included db runtimes #987 [skaes@web.de]
+
+* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org]
+
+* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [Jeremy Kemper]
+
+* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org]
+
+* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [skaes@web.de]
+
+* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [skaes@web.de]
+
+* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas]
+
+* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
+
+
+*1.9.1* (27th March, 2005)
+
+* Fixed that Active Record objects with float attribute could not be cloned #808
+
+* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar]
+
+* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
+
+* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
+
+* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb]
+
+
+*1.9.0* (22th March, 2005)
+
+* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
+
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
+
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+
+* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
+
+* Improved the performance of the OCI8 adapter for Oracle #723 [pilx/gjenkins]
+
+* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 [dave@cherryville.org]
+
+* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 [mindel]
+
+* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 [Alisdair McDiarmid]
+
+* Added the possibility of specifying fixtures in multiple calls #816 [kim@tinker.com]
+
+* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 [stian@grytoyr.net]
+
+* Added optionally allow for nil or empty strings with validates_numericality_of #801 [Sebastian Kanthak]
+
+* Fixed problem with using slashes in validates_format_of regular expressions #801 [Sebastian Kanthak]
+
+* Fixed that SQLite3 exceptions are caught and reported properly #823 [yerejm]
+
+* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
+
+* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
+
+
+*1.8.0* (7th March, 2005)
+
+* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
+
+* Added support for timestamp with time zone in PostgreSQL #560 [Scott Barron]
+
+* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ objects that should be inspected to determine which attributes triggered the errors.
+ * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
+ You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+
+* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k]
+
+* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [Jeremy Kemper]
+
+* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
+
+ class Account < ActiveRecord::Base
+ has_one :credit_card, :dependent => true
+ end
+ class CreditCard < ActiveRecord::Base
+ belongs_to :account
+ end
+
+ account.credit_card # => returns existing credit card, lets say id = 12
+ account.credit_card = CreditCard.create("number" => "123")
+ account.save # => CC with id = 12 is destroyed
+
+
+* Added validates_numericality_of #716 [skanthak/c.r.mcgrath]. Docuemntation:
+
+ Validates whether the value of the specified attribute is numeric by trying to convert it to
+ a float with Kernel.Float (if integer is false) or applying it to the regular expression
+ /^[\+\-]?\d+$/ (if integer is set to true).
+
+ class Person < ActiveRecord::Base
+ validates_numericality_of :value, :on => :create
+ end
+
+ Configuration options:
+ * message - A custom error message (default is: "is not a number")
+ * on Specifies when this validation is active (default is :save, other options :create, :update)
+ * only_integer Specifies whether the value has to be an integer, e.g. an integral value (default is false)
+
+
+* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
+
+* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
+
+* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 [james@slashetc.com]
+
+
+*1.7.0* (24th February, 2005)
+
+* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 [Jamis Buck]
+
+* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
+
+ 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
+ and safe way of passing table-specific sequence information to the adapter.)
+ 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
+ resort to some hacks to get data converted to Date or Time in Ruby.
+ If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
+ hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
+ This is nasty - but if you use Duck Typing you'll probably not care very much.
+ In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
+ valid - too many databases use DATE for both.
+ Timezones and sub-second precision on timestamps are not supported.
+ 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
+ restriction of the way active record supports default values.
+ 4. Referential integrity constraints are not fully supported. Under at least
+ some circumstances, active record appears to delete parent and child records out of
+ sequence and out of transaction scope. (Or this may just be a problem of test setup.)
+
+ The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
+
+* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 [YuriSchimke]
+
+* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 [yerejm]
+
+* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 [tonka]
+
+* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 [Nicholas Seckar]
+
+* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 [daniel@nightrunner.com]
+
+* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 [adelle@bullet.net.au]
+
+* Fixed that find_by_* would fail when column names had numbers #670 [demetrius]
+
+* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
+
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
+
+* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow@yahoo.com]
+
+* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
+
+ class Person < ActiveRecord::Base
+ validates_each :first_name, :last_name do |record, attr|
+ record.errors.add attr, 'starts with z.' if attr[0] == ?z
+ end
+ end
+
+* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 [Jeremy Kemper]
+
+* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
+
+ class Person < ActiveRecord::Base
+ validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
+ end
+
+* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 [Tim Bates]
+
+* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
+
+ class Project < ActiveRecord::Base
+ primary_key "sysid"
+ table_name "XYZ_PROJECT"
+ inheritance_column { original_inheritance_column + "_id" }
+ end
+
+* Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
+
+
+*1.6.0* (January 25th, 2005)
+
+* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
+
+* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
+
+* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
+
+* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
+
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
+ Person.update(people.keys, people.values)
+
+* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
+
+* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat]
+
+* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius]
+
+* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [Jeremy Kemper]
+
+* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
+
+* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
+
+* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
+
+* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
+
+* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius]
+
+* Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron]
+
+
+*1.5.1* (January 18th, 2005)
+
+* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates]
+
+* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 [Pelle]
+
+
+*1.5.0* (January 17th, 2005)
+
+* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel]
+
+* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
+
+ class Book < ActiveRecord::Base
+ has_many :pages
+ belongs_to :library
+
+ validates_associated :pages, :library
+ end
+
+* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
+
+ == Unsaved objects and associations
+
+ You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
+ aware of, mostly involving the saving of associated objects.
+
+ === 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 (new_record? == true).
+ * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
+ is cancelled.
+ * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
+ * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
+ not save the parent either.
+
+ === Collections
+
+ * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
+ (the owner of the collection) is not yet stored in the database.
+ * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
+ * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
+ * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
+
+* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates]
+
+* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates]
+
+* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates]
+
+* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates]
+
+* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
+
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+ it'll take one less SELECT query if you use length.
+
+* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
+
+* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson]
+
+* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey]
+
+* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz]
+
+* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
+
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
+
+* Added that Observers can use the observes class method instead of overwriting self.observed_class().
+
+ Before:
+ class ListSweeper < ActiveRecord::Base
+ def self.observed_class() [ List, Item ]
+ end
+
+ After:
+ class ListSweeper < ActiveRecord::Base
+ observes List, Item
+ end
+
+* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
+
+* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
+
+* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
+
+ Before: topic.update_attribute(:approved, !approved?)
+ After : topic.toggle!(:approved)
+
+* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
+
+ page.views # => 1
+ page.increment!(:views) # executes an UPDATE statement
+ page.views # => 2
+
+ page.increment(:views).increment!(:views)
+ page.views # => 4
+
+* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
+
+
+
+
+*1.14.2* (April 9th, 2005)
+
+* Fixed calculations for the Oracle Adapter (closes #4626) [Michael Schoen]
+
+
+*1.14.1* (April 6th, 2006)
+
+* Fix type_name_with_module to handle type names that begin with '::'. Closes #4614. [Nicholas Seckar]
+
+* Fixed that that multiparameter assignment doesn't work with aggregations (closes #4620) [Lars Pind]
+
+* Enable Limit/Offset in Calculations (closes #4558) [lmarlow@yahoo.com]
+
+* Fixed that loading including associations returns all results if Load IDs For Limited Eager Loading returns none (closes #4528) [Rick]
+
+* Fixed HasManyAssociation#find bugs when :finder_sql is set #4600 [lagroue@free.fr]
+
+* Allow AR::Base#respond_to? to behave when @attributes is nil [zenspider]
+
+* Support eager includes when going through a polymorphic has_many association. [Rick]
+
+* Added support for eagerly including polymorphic has_one associations. (closes #4525) [Rick]
+
+ class Post < ActiveRecord::Base
+ has_one :tagging, :as => :taggable
+ end
+
+ Post.find :all, :include => :tagging
+
+* Added descriptive error messages for invalid has_many :through associations: going through :has_one or :has_and_belongs_to_many [Rick]
+
+* Added support for going through a polymorphic has_many association: (closes #4401) [Rick]
+
+ class PhotoCollection < ActiveRecord::Base
+ has_many :photos, :as => :photographic
+ belongs_to :firm
+ end
+
+ class Firm < ActiveRecord::Base
+ has_many :photo_collections
+ has_many :photos, :through => :photo_collections
+ end
+
+* Multiple fixes and optimizations in PostgreSQL adapter, allowing ruby-postgres gem to work properly. [ruben.nine@gmail.com]
+
+* Fixed that AssociationCollection#delete_all should work even if the records of the association are not loaded yet. [Florian Weber]
+
+* Changed those private ActiveRecord methods to take optional third argument :auto instead of nil for performance optimizations. (closes #4456) [Stefan]
+
+* Private ActiveRecord methods add_limit!, add_joins!, and add_conditions! take an OPTIONAL third argument 'scope' (closes #4456) [Rick]
+
+* DEPRECATED: Using additional attributes on has_and_belongs_to_many associations. Instead upgrade your association to be a real join model [DHH]
+
+* Fixed that records returned from has_and_belongs_to_many associations with additional attributes should be marked as read only (fixes #4512) [DHH]
+
+* Do not implicitly mark recordss of has_many :through as readonly but do mark habtm records as readonly (eventually only on join tables without rich attributes). [Marcel Mollina Jr.]
+
+* Fixed broken OCIAdapter #4457 [schoenm@earthlink.net]
+
+
+*1.14.0* (March 27th, 2006)
+
+* Replace 'rescue Object' with a finer grained rescue. Closes #4431. [Nicholas Seckar]
+
+* Fixed eager loading so that an aliased table cannot clash with a has_and_belongs_to_many join table [Rick]
+
+* Add support for :include to with_scope [andrew@redlinesoftware.com]
+
+* Support the use of public synonyms with the Oracle adapter; required ruby-oci8 v0.1.14 #4390 [schoenm@earthlink.net]
+
+* Change periods (.) in table aliases to _'s. Closes #4251 [jeff@ministrycentered.com]
+
+* Changed has_and_belongs_to_many join to INNER JOIN for Mysql 3.23.x. Closes #4348 [Rick]
+
+* Fixed issue that kept :select options from being scoped [Rick]
+
+* Fixed db_schema_import when binary types are present #3101 [DHH]
+
+* Fixed that MySQL enums should always be returned as strings #3501 [DHH]
+
+* Change has_many :through to use the :source option to specify the source association. :class_name is now ignored. [Rick Olson]
+
+ class Connection < ActiveRecord::Base
+ belongs_to :user
+ belongs_to :channel
+ end
+
+ class Channel < ActiveRecord::Base
+ has_many :connections
+ has_many :contacts, :through => :connections, :class_name => 'User' # OLD
+ has_many :contacts, :through => :connections, :source => :user # NEW
+ end
+
+* Fixed DB2 adapter so nullable columns will be determines correctly now and quotes from column default values will be removed #4350 [contact@maik-schmidt.de]
+
+* Allow overriding of find parameters in scoped has_many :through calls [Rick Olson]
+
+ In this example, :include => false disables the default eager association from loading. :select changes the standard
+ select clause. :joins specifies a join that is added to the end of the has_many :through query.
+
+ class Post < ActiveRecord::Base
+ has_many :tags, :through => :taggings, :include => :tagging do
+ def add_joins_and_select
+ find :all, :select => 'tags.*, authors.id as author_id', :include => false,
+ :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
+ end
+ end
+ end
+
+* Fixed that schema changes while the database was open would break any connections to a SQLite database (now we reconnect if that error is throw) [DHH]
+
+* Don't classify the has_one class when eager loading, it is already singular. Add tests. (closes #4117) [jonathan@bluewire.net.nz]
+
+* Quit ignoring default :include options in has_many :through calls [Mark James]
+
+* Allow has_many :through associations to find the source association by setting a custom class (closes #4307) [jonathan@bluewire.net.nz]
+
+* Eager Loading support added for has_many :through => :has_many associations (see below). [Rick Olson]
+
+* Allow has_many :through to work on has_many associations (closes #3864) [sco@scottraymond.net] Example:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_many :invoices, :through => :clients
+ end
+
+ class Client < ActiveRecord::Base
+ belongs_to :firm
+ has_many :invoices
+ end
+
+ class Invoice < ActiveRecord::Base
+ belongs_to :client
+ end
+
+* Raise error when trying to select many polymorphic objects with has_many :through or :include (closes #4226) [josh@hasmanythrough.com]
+
+* Fixed has_many :through to include :conditions set on the :through association. closes #4020 [jonathan@bluewire.net.nz]
+
+* Fix that has_many :through honors the foreign key set by the belongs_to association in the join model (closes #4259) [andylien@gmail.com / Rick]
+
+* SQL Server adapter gets some love #4298 [rtomayko@gmail.com]
+
+* Added OpenBase database adapter that builds on top of the http://www.spice-of-life.net/ruby-openbase/ driver. All functionality except LIMIT/OFFSET is supported #3528 [derrickspell@cdmplus.com]
+
+* Rework table aliasing to account for truncated table aliases. Add smarter table aliasing when doing eager loading of STI associations. This allows you to use the association name in the order/where clause. [Jonathan Viney / Rick Olson] #4108 Example (SpecialComment is using STI):
+
+ Author.find(:all, :include => { :posts => :special_comments }, :order => 'special_comments.body')
+
+* Add AbstractAdapter#table_alias_for to create table aliases according to the rules of the current adapter. [Rick]
+
+* Provide access to the underlying database connection through Adapter#raw_connection. Enables the use of db-specific methods without complicating the adapters. #2090 [Koz]
+
+* Remove broken attempts at handling columns with a default of 'now()' in the postgresql adapter. #2257 [Koz]
+
+* Added connection#current_database that'll return of the current database (only works in MySQL, SQL Server, and Oracle so far -- please help implement for the rest of the adapters) #3663 [Tom ward]
+
+* Fixed that Migration#execute would have the table name prefix appended to its query #4110 [mark.imbriaco@pobox.com]
+
+* Make all tinyint(1) variants act like boolean in mysql (tinyint(1) unsigned, etc.) [Jamis Buck]
+
+* Use association's :conditions when eager loading. [jeremyevans0@gmail.com] #4144
+
+* Alias the has_and_belongs_to_many join table on eager includes. #4106 [jeremyevans0@gmail.com]
+
+ This statement would normally error because the projects_developers table is joined twice, and therefore joined_on would be ambiguous.
+
+ Developer.find(:all, :include => {:projects => :developers}, :conditions => 'join_project_developers.joined_on IS NOT NULL')
+
+* Oracle adapter gets some love #4230 [schoenm@earthlink.net]
+
+ * Changes :text to CLOB rather than BLOB [Moses Hohman]
+ * Fixes an issue with nil numeric length/scales (several)
+ * Implements support for XMLTYPE columns [wilig / Kubo Takehiro]
+ * Tweaks a unit test to get it all green again
+ * Adds support for #current_database
+
+* Added Base.abstract_class? that marks which classes are not part of the Active Record hierarchy #3704 [Rick Olson]
+
+ class CachedModel < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class Post < CachedModel
+ end
+
+ CachedModel.abstract_class?
+ => true
+
+ Post.abstract_class?
+ => false
+
+ Post.base_class
+ => Post
+
+ Post.table_name
+ => 'posts'
+
+* Allow :dependent options to be used with polymorphic joins. #3820 [Rick Olson]
+
+ class Foo < ActiveRecord::Base
+ has_many :attachments, :as => :attachable, :dependent => :delete_all
+ end
+
+* Nicer error message on has_many :through when :through reflection can not be found. #4042 [court3nay@gmail.com]
+
+* Upgrade to Transaction::Simple 1.3 [Jamis Buck]
+
+* Catch FixtureClassNotFound when using instantiated fixtures on a fixture that has no ActiveRecord model [Rick Olson]
+
+* Allow ordering of calculated results and/or grouped fields in calculations [solo@gatelys.com]
+
+* Make ActiveRecord::Base#save! return true instead of nil on success. #4173 [johan@johansorensen.com]
+
+* Dynamically set allow_concurrency. #4044 [Stefan Kaes]
+
+* Added Base#to_xml that'll turn the current record into a XML representation [DHH]. Example:
+
+ topic.to_xml
+
+ ...returns:
+
+
+
+ The First Topic
+ David
+ 1
+ false
+ 0
+ 2000-01-01 08:28:00
+ 2003-07-16 09:28:00
+ Have a nice day
+ david@loudthinking.com
+
+ 2004-04-15
+
+
+ ...and you can configure with:
+
+ topic.to_xml(:skip_instruct => true, :except => [ :id, bonus_time, :written_on, replies_count ])
+
+ ...that'll return:
+
+
+ The First Topic
+ David
+ false
+ Have a nice day
+ david@loudthinking.com
+
+ 2004-04-15
+
+
+ You can even do load first-level associations as part of the document:
+
+ firm.to_xml :include => [ :account, :clients ]
+
+ ...that'll return something like:
+
+
+
+ 1
+ 1
+ 37signals
+
+
+ 1
+ Summit
+
+
+ 1
+ Microsoft
+
+
+
+ 1
+ 50
+
+
+
+* Allow :counter_cache to take a column name for custom counter cache columns [Jamis Buck]
+
+* Documentation fixes for :dependent [robby@planetargon.com]
+
+* Stop the MySQL adapter crashing when views are present. #3782 [Jonathan Viney]
+
+* Don't classify the belongs_to class, it is already singular #4117 [keithm@infused.org]
+
+* Allow set_fixture_class to take Classes instead of strings for a class in a module. Raise FixtureClassNotFound if a fixture can't load. [Rick Olson]
+
+* Fix quoting of inheritance column for STI eager loading #4098 [Jonathan Viney ]
+
+* Added smarter table aliasing for eager associations for multiple self joins #3580 [Rick Olson]
+
+ * The first time a table is referenced in a join, no alias is used.
+ * After that, the parent class name and the reflection name are used.
+
+ Tree.find(:all, :include => :children) # LEFT OUTER JOIN trees AS tree_children ...
+
+ * Any additional join references get a numerical suffix like '_2', '_3', etc.
+
+* Fixed eager loading problems with single-table inheritance #3580 [Rick Olson]. Post.find(:all, :include => :special_comments) now returns all posts, and any special comments that the posts may have. And made STI work with has_many :through and polymorphic belongs_to.
+
+* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
+
+ # cascaded in two levels
+ >> Author.find(:all, :include=>{:posts=>:comments})
+ => authors
+ +- posts
+ +- comments
+
+ # cascaded in two levels and normal association
+ >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in two levels with two has_many associations
+ >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
+ => authors
+ +- posts
+ +- comments
+ +- categorizations
+
+ # cascaded in three levels
+ >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
+ => companies
+ +- groups
+ +- members
+ +- favorites
+
+* Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
+
+* Make migrations verbose [Jamis Buck]
+
+* Make counter_cache work with polymorphic belongs_to [Jamis Buck]
+
+* Fixed that calling HasOneProxy#build_model repeatedly would cause saving to happen #4058 [anna@wota.jp]
+
+* Added Sybase database adapter that relies on the Sybase Open Client bindings (see http://raa.ruby-lang.org/project/sybase-ctlib) #3765 [John Sheets]. It's almost completely Active Record compliant (including migrations), but has the following caveats:
+
+ * Does not support DATE SQL column types; use DATETIME instead.
+ * Date columns on HABTM join tables are returned as String, not Time.
+ * Insertions are potentially broken for :polymorphic join tables
+ * BLOB column access not yet fully supported
+
+* Clear stale, cached connections left behind by defunct threads. [Jeremy Kemper]
+
+* CHANGED DEFAULT: set ActiveRecord::Base.allow_concurrency to false. Most AR usage is in single-threaded applications. [Jeremy Kemper]
+
+* Renamed the "oci" adapter to "oracle", but kept the old name as an alias #4017 [schoenm@earthlink.net]
+
+* Fixed that Base.save should always return false if the save didn't succeed, including if it has halted by before_save's #1861, #2477 [DHH]
+
+* Speed up class -> connection caching and stale connection verification. #3979 [Stefan Kaes]
+
+* Add set_fixture_class to allow the use of table name accessors with models which use set_table_name. [Kevin Clark]
+
+* Added that fixtures to placed in subdirectories of the main fixture files are also loaded #3937 [dblack@wobblini.net]
+
+* Define attribute query methods to avoid method_missing calls. #3677 [jonathan@bluewire.net.nz]
+
+* ActiveRecord::Base.remove_connection explicitly closes database connections and doesn't corrupt the connection cache. Introducing the disconnect! instance method for the PostgreSQL, MySQL, and SQL Server adapters; implementations for the others are welcome. #3591 [Simon Stapleton, Tom Ward]
+
+* Added support for nested scopes #3407 [anna@wota.jp]. Examples:
+
+ Developer.with_scope(:find => { :conditions => "salary > 10000", :limit => 10 }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (salary > 10000) LIMIT 10
+
+ # inner rule is used. (all previous parameters are ignored)
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (name = 'Jamis')
+ end
+
+ # parameters are merged
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.find(:all) # => SELECT * FROM developers WHERE (( salary > 10000 ) AND ( name = 'Jamis' )) LIMIT 10
+ end
+ end
+
+* Fixed db2 connection with empty user_name and auth options #3622 [phurley@gmail.com]
+
+* Fixed validates_length_of to work on UTF-8 strings by using characters instead of bytes #3699 [Masao Mutoh]
+
+* Fixed that reflections would bleed across class boundaries in single-table inheritance setups #3796 [lars@pind.com]
+
+* Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Base.maxmium, and the generic Base.calculate. All can be used with :group and :having. Calculations and statitics need no longer require custom SQL. #3958 [Rick Olson]. Examples:
+
+ Person.average :age
+ Person.minimum :age
+ Person.maximum :age
+ Person.sum :salary, :group => :last_name
+
+* Renamed Errors#count to Errors#size but kept an alias for the old name (and included an alias for length too) #3920 [contact@lukeredpath.co.uk]
+
+* Reflections don't attempt to resolve module nesting of association classes. Simplify type computation. [Jeremy Kemper]
+
+* Improved the Oracle OCI Adapter with better performance for column reflection (from #3210), fixes to migrations (from #3476 and #3742), tweaks to unit tests (from #3610), and improved documentation (from #2446) #3879 [Aggregated by schoenm@earthlink.net]
+
+* Fixed that the schema_info table used by ActiveRecord::Schema.define should respect table pre- and suffixes #3834 [rubyonrails@atyp.de]
+
+* Added :select option to Base.count that'll allow you to select something else than * to be counted on. Especially important for count queries using DISTINCT #3839 [skaes]
+
+* Correct syntax error in mysql DDL, and make AAACreateTablesTest run first [Bob Silva]
+
+* Allow :include to be used with has_many :through associations #3611 [Michael Schoen]
+
+* PostgreSQL: smarter schema dumps using pk_and_sequence_for(table). #2920 [Blair Zajac]
+
+* SQLServer: more compatible limit/offset emulation. #3779 [Tom Ward]
+
+* Polymorphic join support for has_one associations (has_one :foo, :as => :bar) #3785 [Rick Olson]
+
+* PostgreSQL: correctly parse negative integer column defaults. #3776 [bellis@deepthought.org]
+
+* Fix problems with count when used with :include [Jeremy Hopple and Kevin Clark]
+
+* ActiveRecord::RecordInvalid now states which validations failed in its default error message [Tobias Luetke]
+
+* Using AssociationCollection#build with arrays of hashes should call build, not create [DHH]
+
+* Remove definition of reloadable? from ActiveRecord::Base to make way for new Reloadable code. [Nicholas Seckar]
+
+* Fixed schema handling for DB2 adapter that didn't work: an initial schema could be set, but it wasn't used when getting tables and indexes #3678 [Maik Schmidt]
+
+* Support the :column option for remove_index with the PostgreSQL adapter. #3661 [shugo@ruby-lang.org]
+
+* Add documentation for add_index and remove_index. #3600 [Manfred Stienstra ]
+
+* If the OCI library is not available, raise an exception indicating as much. #3593 [schoenm@earthlink.net]
+
+* Add explicit :order in finder tests as postgresql orders results differently by default. #3577. [Rick Olson]
+
+* Make dynamic finders honor additional passed in :conditions. #3569 [Oleg Pudeyev , Marcel Molina Jr.]
+
+* Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies. [Nicholas Seckar]
+
+* Make .count work for has_many associations with multi line finder sql [schoenm@earthlink.net]
+
+* Add AR::Base.base_class for querying the ancestor AR::Base subclass [Jamis Buck]
+
+* Allow configuration of the column used for optimistic locking [wilsonb@gmail.com]
+
+* Don't hardcode 'id' in acts as list. [ror@philippeapril.com]
+
+* Fix date errors for SQLServer in association tests. #3406 [kevin.clark@gmal.com]
+
+* Escape database name in MySQL adapter when creating and dropping databases. #3409 [anna@wota.jp]
+
+* Disambiguate table names for columns in validates_uniquness_of's WHERE clause. #3423 [alex.borovsky@gmail.com]
+
+* .with_scope imposed create parameters now bypass attr_protected [Tobias Luetke]
+
+* Don't raise an exception when there are more keys than there are named bind variables when sanitizing conditions. [Marcel Molina Jr.]
+
+* Multiple enhancements and adjustments to DB2 adaptor. #3377 [contact@maik-schmidt.de]
+
+* Sanitize scoped conditions. [Marcel Molina Jr.]
+
+* Added option to Base.reflection_of_all_associations to specify a specific association to scope the call. For example Base.reflection_of_all_associations(:has_many) [DHH]
+
+* Added ActiveRecord::SchemaDumper.ignore_tables which tells SchemaDumper which tables to ignore. Useful for tables with funky column like the ones required for tsearch2. [TobiasLuetke]
+
+* SchemaDumper now doesn't fail anymore when there are unknown column types in the schema. Instead the table is ignored and a Comment is left in the schema.rb. [TobiasLuetke]
+
+* Fixed that saving a model with multiple habtm associations would only save the first one. #3244 [yanowitz-rubyonrails@quantumfoam.org, Florian Weber]
+
+* Fix change_column to work with PostgreSQL 7.x and 8.x. #3141 [wejn@box.cz, Rick Olson, Scott Barron]
+
+* removed :piggyback in favor of just allowing :select on :through associations. [Tobias Luetke]
+
+* made method missing delegation to class methods on relation target work on :through associations. [Tobias Luetke]
+
+* made .find() work on :through relations. [Tobias Luetke]
+
+* Fix typo in association docs. #3296. [Blair Zajac]
+
+* Fixed :through relations when using STI inherited classes would use the inherited class's name as foreign key on the join model [Tobias Luetke]
+
+*1.13.2* (December 13th, 2005)
+
+* Become part of Rails 1.0
+
+* MySQL: allow encoding option for mysql.rb driver. [Jeremy Kemper]
+
+* Added option inheritance for find calls on has_and_belongs_to_many and has_many assosociations [DHH]. Example:
+
+ class Post
+ has_many :recent_comments, :class_name => "Comment", :limit => 10, :include => :author
+ end
+
+ post.recent_comments.find(:all) # Uses LIMIT 10 and includes authors
+ post.recent_comments.find(:all, :limit => nil) # Uses no limit but include authors
+ post.recent_comments.find(:all, :limit => nil, :include => nil) # Uses no limit and doesn't include authors
+
+* Added option to specify :group, :limit, :offset, and :select options from find on has_and_belongs_to_many and has_many assosociations [DHH]
+
+* MySQL: fixes for the bundled mysql.rb driver. #3160 [Justin Forder]
+
+* SQLServer: fix obscure optimistic locking bug. #3068 [kajism@yahoo.com]
+
+* SQLServer: support uniqueidentifier columns. #2930 [keithm@infused.org]
+
+* SQLServer: cope with tables names qualified by owner. #3067 [jeff@ministrycentered.com]
+
+* SQLServer: cope with columns with "desc" in the name. #1950 [Ron Lusk, Ryan Tomayko]
+
+* SQLServer: cope with primary keys with "select" in the name. #3057 [rdifrango@captechventures.com]
+
+* Oracle: active? performs a select instead of a commit. #3133 [Michael Schoen]
+
+* MySQL: more robust test for nullified result hashes. #3124 [Stefan Kaes]
+
+* Reloading an instance refreshes its aggregations as well as its associations. #3024 [François Beausolei]
+
+* Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond]
+
+* PostgreSQL: more robust sequence name discovery. #3087 [Rick Olson]
+
+* Oracle: use syntax compatible with Oracle 8. #3131 [Michael Schoen]
+
+* MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper]
+
+* Added preliminary support for polymorphic associations [DHH]
+
+* Added preliminary support for join models [DHH]
+
+* Allow validate_uniqueness_of to be scoped by more than just one column. #1559. [jeremy@jthopple.com, Marcel Molina Jr.]
+
+* Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz ]
+
+* Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz ]
+
+* MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper]
+
+* Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen]
+
+* SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com]
+
+* Reloading a model doesn't lose track of its connection. #2996 [junk@miriamtech.com, Jeremy Kemper]
+
+* Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen]
+
+* MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. #2978 [Shugo Maeda]
+
+* has_and_belongs_to_many: use JOIN instead of LEFT JOIN. [Jeremy Kemper]
+
+* MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda]
+
+* Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.]
+
+* Correct boolean handling in generated reader methods. #2945 [don.park@gmail.com, Stefan Kaes]
+
+* Don't generate read methods for columns whose names are not valid ruby method names. #2946 [Stefan Kaes]
+
+* Document :force option to create_table. #2921 [Blair Zajac ]
+
+* Don't add the same conditions twice in has_one finder sql. #2916 [Jeremy Evans]
+
+* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
+
+* Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz ]
+
+* SQLServer: active? and reconnect! methods for handling stale connections. #428 [kajism@yahoo.com, Tom Ward ]
+
+* Associations handle case-equality more consistently: item.parts.is_a?(Array) and item.parts === Array. #1345 [MarkusQ@reality.com]
+
+* SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY. #2866 [kajism@yahoo.com, Tom Ward ]
+
+* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen ]
+
+* Correct documentation for Base.delete_all. #1568 [Newhydra]
+
+* Oracle: test case for column default parsing. #2788 [Michael Schoen ]
+
+* Update documentation for Migrations. #2861 [Tom Werner ]
+
+* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper]
+
+* Oracle: Much faster column reflection. #2848 [Michael Schoen ]
+
+* Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing). Base.define_attr_method allows nil values. [Jeremy Kemper]
+
+* PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence. [Jeremy Kemper]
+
+* PostgreSQL: correctly discover custom primary key sequences. #2594 [Blair Zajac , meadow.nnick@gmail.com, Jeremy Kemper]
+
+* SQLServer: don't report limits for unsupported field types. #2835 [Ryan Tomayko]
+
+* Include the Enumerable module in ActiveRecord::Errors. [Rick Bradley ]
+
+* Add :group option, correspond to GROUP BY, to the find method and to the has_many association. #2818 [rubyonrails@atyp.de]
+
+* Don't cast nil or empty strings to a dummy date. #2789 [Rick Bradley ]
+
+* acts_as_list plays nicely with inheritance by remembering the class which declared it. #2811 [rephorm@rephorm.com]
+
+* Fix sqlite adaptor's detection of missing dbfile or database declaration. [Nicholas Seckar]
+
+* Fixed acts_as_list for definitions without an explicit :order #2803 [jonathan@bluewire.net.nz]
+
+* Upgrade bundled ruby-mysql 0.2.4 with mysql411 shim (see #440) to ruby-mysql 0.2.6 with a patchset for 4.1 protocol support. Local change [301] is now a part of the main driver; reapplied local change [2182]. Removed GC.start from Result.free. [tommy@tmtm.org, akuroda@gmail.com, Doug Fales , Jeremy Kemper]
+
+* Correct handling of complex order clauses with SQL Server limit emulation. #2770 [Tom Ward , Matt B.]
+
+* Correct whitespace problem in Oracle default column value parsing. #2788 [rick@rickbradley.com]
+
+* Destroy associated has_and_belongs_to_many records after all before_destroy callbacks but before destroy. This allows you to act on the habtm association as you please while preserving referential integrity. #2065 [larrywilliams1@gmail.com, sam.kirchmeier@gmail.com, elliot@townx.org, Jeremy Kemper]
+
+* Deprecate the old, confusing :exclusively_dependent option in favor of :dependent => :delete_all. [Jeremy Kemper]
+
+* More compatible Oracle column reflection. #2771 [Ryan Davis , Michael Schoen ]
+
+
+*1.13.0* (November 7th, 2005)
+
+* Fixed faulty regex in get_table_name method (SQLServerAdapter) #2639 [Ryan Tomayko]
+
+* Added :include as an option for association declarations [DHH]. Example:
+
+ has_many :posts, :include => [ :author, :comments ]
+
+* Rename Base.constrain to Base.with_scope so it doesn't conflict with existing concept of database constraints. Make scoping more robust: uniform method => parameters, validated method names and supported finder parameters, raise exception on nested scopes. [Jeremy Kemper] Example:
+
+ Comment.with_scope(:find => { :conditions => 'active=true' }, :create => { :post_id => 5 }) do
+ # Find where name = ? and active=true
+ Comment.find :all, :conditions => ['name = ?', name]
+ # Create comment associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+* Fixed that SQL Server should ignore :size declarations on anything but integer and string in the agnostic schema representation #2756 [Ryan Tomayko]
+
+* Added constrain scoping for creates using a hash of attributes bound to the :creation key [DHH]. Example:
+
+ Comment.constrain(:creation => { :post_id => 5 }) do
+ # Associated with :post_id
+ Comment.create :body => "Hello world"
+ end
+
+ This is rarely used directly, but allows for find_or_create on associations. So you can do:
+
+ # If the tag doesn't exist, a new one is created that's associated with the person
+ person.tags.find_or_create_by_name("Summer")
+
+* Added find_or_create_by_X as a second type of dynamic finder that'll create the record if it doesn't already exist [DHH]. Example:
+
+ # No 'Summer' tag exists
+ Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+
+ # Now the 'Summer' tag does exist
+ Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
+
+* Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example:
+
+ class Account < ActiveRecord::Base
+ has_many :people do
+ def find_or_create_by_name(name)
+ first_name, *last_name = name.split
+ last_name = last_name.join " "
+
+ find_or_create_by_first_name_and_last_name(first_name, last_name)
+ end
+ end
+ end
+
+ person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
+ person.first_name # => "David"
+ person.last_name # => "Heinemeier Hansson"
+
+ Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
+
+* Omit internal dtproperties table from SQLServer table list. #2729 [rtomayko@gmail.com]
+
+* Quote column names in generated SQL. #2728 [rtomayko@gmail.com]
+
+* Correct the pure-Ruby MySQL 4.1.1 shim's version test. #2718 [Jeremy Kemper]
+
+* Add Model.create! to match existing model.save! method. When save! raises RecordInvalid, you can catch the exception, retrieve the invalid record (invalid_exception.record), and see its errors (invalid_exception.record.errors). [Jeremy Kemper]
+
+* Correct fixture behavior when table name pluralization is off. #2719 [Rick Bradley ]
+
+* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
+
+* Added migration support for Oracle #2647 [Michael Schoen]
+
+* Worked around that connection can't be reset if allow_concurrency is off. #2648 [Michael Schoen ]
+
+* Fixed SQL Server adapter to pass even more tests and do even better #2634 [rtomayko@gmail.com]
+
+* Fixed SQL Server adapter so it honors options[:conditions] when applying :limits #1978 [Tom Ward]
+
+* Added migration support to SQL Server adapter (please someone do the same for Oracle and DB2) #2625 [Tom Ward]
+
+* Use AR::Base.silence rather than AR::Base.logger.silence in fixtures to preserve Log4r compatibility. #2618 [dansketcher@gmail.com]
+
+* Constraints are cloned so they can't be inadvertently modified while they're
+in effect. Added :readonly finder constraint. Calling an association collection's class method (Part.foobar via item.parts.foobar) constrains :readonly => false since the collection's :joins constraint would otherwise force it to true. [Jeremy Kemper ]
+
+* Added :offset and :limit to the kinds of options that Base.constrain can use #2466 [duane.johnson@gmail.com]
+
+* Fixed handling of nil number columns on Oracle and cleaned up tests for Oracle in general #2555 [schoenm@earthlink.net]
+
+* Added quoted_true and quoted_false methods and tables to db2_adapter and cleaned up tests for DB2 #2493, #2624 [maik schmidt]
+
+
+*1.12.2* (October 26th, 2005)
+
+* Allow symbols to rename columns when using SQLite adapter. #2531 [kevin.clark@gmail.com]
+
+* Map Active Record time to SQL TIME. #2575, #2576 [Robby Russell ]
+
+* Clarify semantics of ActiveRecord::Base#respond_to? #2560 [skaes@web.de]
+
+* Fixed Association#clear for associations which have not yet been accessed. #2524 [Patrick Lenz ]
+
+* HABTM finders shouldn't return readonly records. #2525 [Patrick Lenz ]
+
+* Make all tests runnable on their own. #2521. [Blair Zajac ]
+
+
+*1.12.1* (October 19th, 2005)
+
+* Always parenthesize :conditions options so they may be safely combined with STI and constraints.
+
+* Correct PostgreSQL primary key sequence detection. #2507 [tmornini@infomania.com]
+
+* Added support for using limits in eager loads that involve has_many and has_and_belongs_to_many associations
+
+
+*1.12.0* (October 16th, 2005)
+
+* Update/clean up documentation (rdoc)
+
+* PostgreSQL sequence support. Use set_sequence_name in your model class to specify its primary key sequence. #2292 [Rick Olson , Robby Russell ]
+
+* Change default logging colors to work on both white and black backgrounds. [Sam Stephenson]
+
+* YAML fixtures support ordered hashes for fixtures with foreign key dependencies in the same table. #1896 [purestorm@ggnore.net]
+
+* :dependent now accepts :nullify option. Sets the foreign key of the related objects to NULL instead of deleting them. #2015 [Robby Russell ]
+
+* Introduce read-only records. If you call object.readonly! then it will mark the object as read-only and raise ReadOnlyRecord if you call object.save. object.readonly? reports whether the object is read-only. Passing :readonly => true to any finder method will mark returned records as read-only. The :joins option now implies :readonly, so if you use this option, saving the same record will now fail. Use find_by_sql to work around.
+
+* Avoid memleak in dev mode when using fcgi
+
+* Simplified .clear on active record associations by using the existing delete_records method. #1906 [Caleb ]
+
+* Delegate access to a customized primary key to the conventional id method. #2444. [Blair Zajac ]
+
+* Fix errors caused by assigning a has-one or belongs-to property to itself
+
+* Add ActiveRecord::Base.schema_format setting which specifies how databases should be dumped [Sam Stephenson]
+
+* Update DB2 adapter. #2206. [contact@maik-schmidt.de]
+
+* Corrections to SQLServer native data types. #2267. [rails.20.clarry@spamgourmet.com]
+
+* Deprecated ActiveRecord::Base.threaded_connection in favor of ActiveRecord::Base.allow_concurrency.
+
+* Protect id attribute from mass assigment even when the primary key is set to something else. #2438. [Blair Zajac ]
+
+* Misc doc fixes (typos/grammar/etc.). #2430. [coffee2code]
+
+* Add test coverage for content_columns. #2432. [coffee2code]
+
+* Speed up for unthreaded environments. #2431. [skaes@web.de]
+
+* Optimization for Mysql selects using mysql-ruby extension greater than 2.6.3. #2426. [skaes@web.de]
+
+* Speed up the setting of table_name. #2428. [skaes@web.de]
+
+* Optimize instantiation of STI subclass records. In partial fullfilment of #1236. [skaes@web.de]
+
+* Fix typo of 'constrains' to 'contraints'. #2069. [Michael Schuerig ]
+
+* Optimization refactoring for add_limit_offset!. In partial fullfilment of #1236. [skaes@web.de]
+
+* Add ability to get all siblings, including the current child, with acts_as_tree. Recloses #2140. [Michael Schuerig ]
+
+* Add geometric type for postgresql adapter. #2233 [akaspick@gmail.com]
+
+* Add option (true by default) to generate reader methods for each attribute of a record to avoid the overhead of calling method missing. In partial fullfilment of #1236. [skaes@web.de]
+
+* Add convenience predicate methods on Column class. In partial fullfilment of #1236. [skaes@web.de]
+
+* Raise errors when invalid hash keys are passed to ActiveRecord::Base.find. #2363 [Chad Fowler , Nicholas Seckar]
+
+* Added :force option to create_table that'll try to drop the table if it already exists before creating
+
+* Fix transactions so that calling return while inside a transaction will not leave an open transaction on the connection. [Nicholas Seckar]
+
+* Use foreign_key inflection uniformly. #2156 [Blair Zajac ]
+
+* model.association.clear should destroy associated objects if :dependent => true instead of nullifying their foreign keys. #2221 [joergd@pobox.com, ObieFernandez ]
+
+* Returning false from before_destroy should cancel the action. #1829 [Jeremy Huffman]
+
+* Recognize PostgreSQL NOW() default as equivalent to CURRENT_TIMESTAMP or CURRENT_DATE, depending on the column's type. #2256 [mat ]
+
+* Extensive documentation for the abstract database adapter. #2250 [François Beausoleil ]
+
+* Clean up Fixtures.reset_sequences for PostgreSQL. Handle tables with no rows and models with custom primary keys. #2174, #2183 [jay@jay.fm, Blair Zajac ]
+
+* Improve error message when nil is assigned to an attr which validates_size_of within a range. #2022 [Manuel Holtgrewe ]
+
+* Make update_attribute use the same writer method that update_attributes uses.
+ #2237 [trevor@protocool.com]
+
+* Make migrations honor table name prefixes and suffixes. #2298 [Jakob S, Marcel Molina]
+
+* Correct and optimize PostgreSQL bytea escaping. #1745, #1837 [dave@cherryville.org, ken@miriamtech.com, bellis@deepthought.org]
+
+* Fixtures should only reset a PostgreSQL sequence if it corresponds to an integer primary key named id. #1749 [chris@chrisbrinker.com]
+
+* Standardize the interpretation of boolean columns in the Mysql and Sqlite adapters. (Use MysqlAdapter.emulate_booleans = false to disable this behavior)
+
+* Added new symbol-driven approach to activating observers with Base#observers= [DHH]. Example:
+
+ ActiveRecord::Base.observers = :cacher, :garbage_collector
+
+* Added AbstractAdapter#select_value and AbstractAdapter#select_values as convenience methods for selecting single values, instead of hashes, of the first column in a SELECT #2283 [solo@gatelys.com]
+
+* Wrap :conditions in parentheses to prevent problems with OR's #1871 [Jamis Buck]
+
+* Allow the postgresql adapter to work with the SchemaDumper. [Jamis Buck]
+
+* Add ActiveRecord::SchemaDumper for dumping a DB schema to a pure-ruby file, making it easier to consolidate large migration lists and port database schemas between databases. [Jamis Buck]
+
+* Fixed migrations for Windows when using more than 10 [David Naseby]
+
+* Fixed that the create_x method from belongs_to wouldn't save the association properly #2042 [Florian Weber]
+
+* Fixed saving a record with two unsaved belongs_to associations pointing to the same object #2023 [Tobias Luetke]
+
+* Improved migrations' behavior when the schema_info table is empty. [Nicholas Seckar]
+
+* Fixed that Observers didn't observe sub-classes #627 [Florian Weber]
+
+* Fix eager loading error messages, allow :include to specify tables using strings or symbols. Closes #2222 [Marcel Molina]
+
+* Added check for RAILS_CONNECTION_ADAPTERS on startup and only load the connection adapters specified within if its present (available in Rails through config.connection_adapters using the new config) #1958 [skae]
+
+* Fixed various problems with has_and_belongs_to_many when using customer finder_sql #2094 [Florian Weber]
+
+* Added better exception error when unknown column types are used with migrations #1814 [fbeausoleil@ftml.net]
+
+* Fixed "connection lost" issue with the bundled Ruby/MySQL driver (would kill the app after 8 hours of inactivity) #2163, #428 [kajism@yahoo.com]
+
+* Fixed comparison of Active Record objects so two new objects are not equal #2099 [deberg]
+
+* Fixed that the SQL Server adapter would sometimes return DBI::Timestamp objects instead of Time #2127 [Tom Ward]
+
+* Added the instance methods #root and #ancestors on acts_as_tree and fixed siblings to not include the current node #2142, #2140 [coffee2code]
+
+* Fixed that Active Record would call SHOW FIELDS twice (or more) for the same model when the cached results were available #1947 [sd@notso.net]
+
+* Added log_level and use_silence parameter to ActiveRecord::Base.benchmark. The first controls at what level the benchmark statement will be logged (now as debug, instead of info) and the second that can be passed false to include all logging statements during the benchmark block/
+
+* Make sure the schema_info table is created before querying the current version #1903
+
+* Fixtures ignore table name prefix and suffix #1987 [Jakob S]
+
+* Add documentation for index_type argument to add_index method for migrations #2005 [blaine@odeo.com]
+
+* Modify read_attribute to allow a symbol argument #2024 [Ken Kunz]
+
+* Make destroy return self #1913 [sebastian.kanthak@muehlheim.de]
+
+* Fix typo in validations documentation #1938 [court3nay]
+
+* Make acts_as_list work for insert_at(1) #1966 [hensleyl@papermountain.org]
+
+* Fix typo in count_by_sql documentation #1969 [Alexey Verkhovsky]
+
+* Allow add_column and create_table to specify NOT NULL #1712 [emptysands@gmail.com]
+
+* Fix create_table so that id column is implicitly added [Rick Olson]
+
+* Default sequence names for Oracle changed to #{table_name}_seq, which is the most commonly used standard. In addition, a new method ActiveRecord::Base#set_sequence_name allows the developer to set the sequence name per model. This is a non-backwards-compatible change -- anyone using the old-style "rails_sequence" will need to either create new sequences, or set: ActiveRecord::Base.set_sequence_name = "rails_sequence" #1798
+
+* OCIAdapter now properly handles synonyms, which are commonly used to separate out the schema owner from the application user #1798
+
+* Fixed the handling of camelCase columns names in Oracle #1798
+
+* Implemented for OCI the Rakefile tasks of :clone_structure_to_test, :db_structure_dump, and :purge_test_database, which enable Oracle folks to enjoy all the agile goodness of Rails for testing. Note that the current implementation is fairly limited -- only tables and sequences are cloned, not constraints or indexes. A full clone in Oracle generally requires some manual effort, and is version-specific. Post 9i, Oracle recommends the use of the DBMS_METADATA package, though that approach requires editing of the physical characteristics generated #1798
+
+* Fixed the handling of multiple blob columns in Oracle if one or more of them are null #1798
+
+* Added support for calling constrained class methods on has_many and has_and_belongs_to_many collections #1764 [Tobias Luetke]
+
+ class Comment < AR:B
+ def self.search(q)
+ find(:all, :conditions => ["body = ?", q])
+ end
+ end
+
+ class Post < AR:B
+ has_many :comments
+ end
+
+ Post.find(1).comments.search('hi') # => SELECT * from comments WHERE post_id = 1 AND body = 'hi'
+
+ NOTICE: This patch changes the underlying SQL generated by has_and_belongs_to_many queries. If your relying on that, such as
+ by explicitly referencing the old t and j aliases, you'll need to update your code. Of course, you _shouldn't_ be relying on
+ details like that no less than you should be diving in to touch private variables. But just in case you do, consider yourself
+ noticed :)
+
+* Added migration support for SQLite (using temporary tables to simulate ALTER TABLE) #1771 [Sam Stephenson]
+
+* Remove extra definition of supports_migrations? from abstract_adaptor.rb [Nicholas Seckar]
+
+* Fix acts_as_list so that moving next-to-last item to the bottom does not result in duplicate item positions
+
+* Fixed incompatibility in DB2 adapter with the new limit/offset approach #1718 [Maik Schmidt]
+
+* Added :select option to find which can specify a different value than the default *, like find(:all, :select => "first_name, last_name"), if you either only want to select part of the columns or exclude columns otherwise included from a join #1338 [Stefan Kaes]
+
+
+*1.11.1* (11 July, 2005)
+
+* Added support for limit and offset with eager loading of has_one and belongs_to associations. Using the options with has_many and has_and_belongs_to_many associations will now raise an ActiveRecord::ConfigurationError #1692 [Rick Olsen]
+
+* Fixed that assume_bottom_position (in acts_as_list) could be called on items already last in the list and they would move one position away from the list #1648 [tyler@kianta.com]
+
+* Added ActiveRecord::Base.threaded_connections flag to turn off 1-connection per thread (required for thread safety). By default it's on, but WEBrick in Rails need it off #1685 [Sam Stephenson]
+
+* Correct reflected table name for singular associations. #1688 [court3nay@gmail.com]
+
+* Fixed optimistic locking with SQL Server #1660 [tom@popdog.net]
+
+* Added ActiveRecord::Migrator.migrate that can figure out whether to go up or down based on the target version and the current
+
+* Added better error message for "packets out of order" #1630 [courtenay]
+
+* Fixed first run of "rake migrate" on PostgreSQL by not expecting a return value on the id #1640
+
+
+*1.11.0* (6 July, 2005)
+
+* Fixed that Yaml error message in fixtures hid the real error #1623 [Nicholas Seckar]
+
+* Changed logging of SQL statements to use the DEBUG level instead of INFO
+
+* Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Luetke] See documentation under ActiveRecord::Migration and the additional support in the Rails rakefile/generator.
+
+* Added callback hooks to association collections #1549 [Florian Weber]. Example:
+
+ class Project
+ has_and_belongs_to_many :developers, :before_add => :evaluate_velocity
+
+ def evaluate_velocity(developer)
+ ...
+ end
+ end
+
+ ..raising an exception will cause the object not to be added (or removed, with before_remove).
+
+
+* Fixed Base.content_columns call for SQL Server adapter #1450 [DeLynn Berry]
+
+* Fixed Base#write_attribute to work with both symbols and strings #1190 [Paul Legato]
+
+* Fixed that has_and_belongs_to_many didn't respect single table inheritance types #1081 [Florian Weber]
+
+* Speed up ActiveRecord#method_missing for the common case (read_attribute).
+
+* Only notify observers on after_find and after_initialize if these methods are defined on the model. #1235 [skaes@web.de]
+
+* Fixed that single-table inheritance sub-classes couldn't be used to limit the result set with eager loading #1215 [Chris McGrath]
+
+* Fixed validates_numericality_of to work with overrided getter-method when :allow_nil is on #1316 [raidel@onemail.at]
+
+* Added roots, root, and siblings to the batch of methods added by acts_as_tree #1541 [michael@schuerig.de]
+
+* Added support for limit/offset with the MS SQL Server driver so that pagination will now work #1569 [DeLynn Berry]
+
+* Added support for ODBC connections to MS SQL Server so you can connect from a non-Windows machine #1569 [Mark Imbriaco/DeLynn Berry]
+
+* Fixed that multiparameter posts ignored attr_protected #1532 [alec+rails@veryclever.net]
+
+* Fixed problem with eager loading when using a has_and_belongs_to_many association using :association_foreign_key #1504 [flash@vanklinkenbergsoftware.nl]
+
+* Fixed Base#find to honor the documentation on how :joins work and make them consistent with Base#count #1405 [pritchie@gmail.com]. What used to be:
+
+ Developer.find :all, :joins => 'developers_projects', :conditions => 'id=developer_id AND project_id=1'
+
+ ...should instead be:
+
+ Developer.find(
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :conditions => 'project_id=1'
+ )
+
+* Fixed that validations didn't respecting custom setting for too_short, too_long messages #1437 [Marcel Molina]
+
+* Fixed that clear_association_cache doesn't delete new associations on new records (so you can safely place new records in the session with Action Pack without having new associations wiped) #1494 [cluon]
+
+* Fixed that calling Model.find([]) returns [] and doesn't throw an exception #1379
+
+* Fixed that adding a record to a has_and_belongs_to collection would always save it -- now it only saves if its a new record #1203 [Alisdair McDiarmid]
+
+* Fixed saving of in-memory association structures to happen as a after_create/after_update callback instead of after_save -- that way you can add new associations in after_create/after_update callbacks without getting them saved twice
+
+* Allow any Enumerable, not just Array, to work as bind variables #1344 [Jeremy Kemper]
+
+* Added actual database-changing behavior to collection assigment for has_many and has_and_belongs_to_many #1425 [Sebastian Kanthak].
+ Example:
+
+ david.projects = [Project.find(1), Project.new("name" => "ActionWebSearch")]
+ david.save
+
+ If david.projects already contain the project with ID 1, this is left unchanged. Any other projects are dropped. And the new
+ project is saved when david.save is called.
+
+ Also included is a way to do assignments through IDs, which is perfect for checkbox updating, so you get to do:
+
+ david.project_ids = [1, 5, 7]
+
+* Corrected typo in find SQL for has_and_belongs_to_many. #1312 [ben@bensinclair.com]
+
+* Fixed sanitized conditions for has_many finder method. #1281 [jackc@hylesanderson.com, pragdave, Tobias Luetke]
+
+* Comprehensive PostgreSQL schema support. Use the optional schema_search_path directive in database.yml to give a comma-separated list of schemas to search for your tables. This allows you, for example, to have tables in a shared schema without having to use a custom table name. See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html to learn more. #827 [dave@cherryville.org]
+
+* Corrected @@configurations typo #1410 [david@ruppconsulting.com]
+
+* Return PostgreSQL columns in the order they were declared #1374 [perlguy@gmail.com]
+
+* Allow before/after update hooks to work on models using optimistic locking
+
+* Eager loading of dependent has_one associations won't delete the association #1212
+
+* Added a second parameter to the build and create method for has_one that controls whether the existing association should be replaced (which means nullifying its foreign key as well). By default this is true, but false can be passed to prevent it.
+
+* Using transactional fixtures now causes the data to be loaded only once.
+
+* Added fixture accessor methods that can be used when instantiated fixtures are disabled.
+
+ fixtures :web_sites
+
+ def test_something
+ assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+ end
+
+* Added DoubleRenderError exception that'll be raised if render* is called twice #518 [Nicholas Seckar]
+
+* Fixed exceptions occuring after render has been called #1096 [Nicholas Seckar]
+
+* CHANGED: validates_presence_of now uses Errors#add_on_blank, which will make " " fail the validation where it didn't before #1309
+
+* Added Errors#add_on_blank which works like Errors#add_on_empty, but uses Object#blank? instead
+
+* Added the :if option to all validations that can either use a block or a method pointer to determine whether the validation should be run or not. #1324 [Duane Johnson/jhosteny]. Examples:
+
+ Conditional validations such as the following are made possible:
+ validates_numericality_of :income, :if => :employed?
+
+ Conditional validations can also solve the salted login generator problem:
+ validates_confirmation_of :password, :if => :new_password?
+
+ Using blocks:
+ validates_presence_of :username, :if => Proc.new { |user| user.signup_step > 1 }
+
+* Fixed use of construct_finder_sql when using :join #1288 [dwlt@dwlt.net]
+
+* Fixed that :delete_sql in has_and_belongs_to_many associations couldn't access record properties #1299 [Rick Olson]
+
+* Fixed that clone would break when an aggregate had the same name as one of its attributes #1307 [Jeremy Kemper]
+
+* Changed that destroying an object will only freeze the attributes hash, which keeps the object from having attributes changed (as that wouldn't make sense), but allows for the querying of associations after it has been destroyed.
+
+* Changed the callbacks such that observers are notified before the in-object callbacks are triggered. Without this change, it wasn't possible to act on the whole object in something like a before_destroy observer without having the objects own callbacks (like deleting associations) called first.
+
+* Added option for passing an array to the find_all version of the dynamic finders and have it evaluated as an IN fragment. Example:
+
+ # SELECT * FROM topics WHERE title IN ('First', 'Second')
+ Topic.find_all_by_title(["First", "Second"])
+
+* Added compatibility with camelCase column names for dynamic finders #533 [Dee.Zsombor]
+
+* Fixed extraneous comma in count() function that made it not work with joins #1156 [jarkko/Dee.Zsombor]
+
+* Fixed incompatibility with Base#find with an array of ids that would fail when using eager loading #1186 [Alisdair McDiarmid]
+
+* Fixed that validate_length_of lost :on option when :within was specified #1195 [jhosteny@mac.com]
+
+* Added encoding and min_messages options for PostgreSQL #1205 [shugo]. Configuration example:
+
+ development:
+ adapter: postgresql
+ database: rails_development
+ host: localhost
+ username: postgres
+ password:
+ encoding: UTF8
+ min_messages: ERROR
+
+* Fixed acts_as_list where deleting an item that was removed from the list would ruin the positioning of other list items #1197 [Jamis Buck]
+
+* Added validates_exclusion_of as a negative of validates_inclusion_of
+
+* Optimized counting of has_many associations by setting the association to empty if the count is 0 so repeated calls doesn't trigger database calls
+
+
+*1.10.1* (20th April, 2005)
+
+* Fixed frivilous database queries being triggered with eager loading on empty associations and other things
+
+* Fixed order of loading in eager associations
+
+* Fixed stray comma when using eager loading and ordering together from has_many associations #1143
+
+
+*1.10.0* (19th April, 2005)
+
+* Added eager loading of associations as a way to solve the N+1 problem more gracefully without piggy-back queries. Example:
+
+ for post in Post.find(:all, :limit => 100)
+ puts "Post: " + post.title
+ puts "Written by: " + post.author.name
+ puts "Last comment on: " + post.comments.first.created_on
+ end
+
+ This used to generate 301 database queries if all 100 posts had both author and comments. It can now be written as:
+
+ for post in Post.find(:all, :limit => 100, :include => [ :author, :comments ])
+
+ ...and the number of database queries needed is now 1.
+
+* Added new unified Base.find API and deprecated the use of find_first and find_all. See the documentation for Base.find. Examples:
+
+ Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(1, 5, 6, :conditions => "administrator = 1", :order => "created_on DESC")
+ Person.find(:first, :order => "created_on DESC", :offset => 5)
+ Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ Person.find(:all, :offset => 10, :limit => 10)
+
+* Added acts_as_nested_set #1000 [wschenk]. Introduction:
+
+ This acts provides Nested Set functionality. Nested Set is similiar to Tree, but with
+ the added feature that you can select the children and all of it's descendants with
+ a single query. A good use case for this is a threaded post system, where you want
+ to display every reply to a comment without multiple selects.
+
+* Added Base.save! that attempts to save the record just like Base.save but will raise a RecordInvalid exception instead of returning false if the record is not valid [After much pestering from Dave Thomas]
+
+* Fixed PostgreSQL usage of fixtures with regards to public schemas and table names with dots #962 [gnuman1@gmail.com]
+
+* Fixed that fixtures were being deleted in the same order as inserts causing FK errors #890 [andrew.john.peters@gmail.com]
+
+* Fixed loading of fixtures in to be in the right order (or PostgreSQL would bark) #1047 [stephenh@chase3000.com]
+
+* Fixed page caching for non-vhost applications living underneath the root #1004 [Ben Schumacher]
+
+* Fixes a problem with the SQL Adapter which was resulting in IDENTITY_INSERT not being set to ON when it should be #1104 [adelle]
+
+* Added the option to specify the acceptance string in validates_acceptance_of #1106 [caleb@aei-tech.com]
+
+* Added insert_at(position) to acts_as_list #1083 [DeLynnB]
+
+* Removed the default order by id on has_and_belongs_to_many queries as it could kill performance on large sets (you can still specify by hand with :order)
+
+* Fixed that Base.silence should restore the old logger level when done, not just set it to DEBUG #1084 [yon@milliped.com]
+
+* Fixed boolean saving on Oracle #1093 [mparrish@pearware.org]
+
+* Moved build_association and create_association for has_one and belongs_to out of deprecation as they work when the association is nil unlike association.build and association.create, which require the association to be already in place #864
+
+* Added rollbacks of transactions if they're active as the dispatcher is killed gracefully (TERM signal) #1054 [Leon Bredt]
+
+* Added quoting of column names for fixtures #997 [jcfischer@gmail.com]
+
+* Fixed counter_sql when no records exist in database for PostgreSQL (would give error, not 0) #1039 [Caleb Tennis]
+
+* Fixed that benchmarking times for rendering included db runtimes #987 [skaes@web.de]
+
+* Fixed boolean queries for t/f fields in PostgreSQL #995 [dave@cherryville.org]
+
+* Added that model.items.delete(child) will delete the child, not just set the foreign key to nil, if the child is dependent on the model #978 [Jeremy Kemper]
+
+* Fixed auto-stamping of dates (created_on/updated_on) for PostgreSQL #985 [dave@cherryville.org]
+
+* Fixed Base.silence/benchmark to only log if a logger has been configured #986 [skaes@web.de]
+
+* Added a join parameter as the third argument to Base.find_first and as the second to Base.count #426, #988 [skaes@web.de]
+
+* Fixed bug in Base#hash method that would treat records with the same string-based id as different [Dave Thomas]
+
+* Renamed DateHelper#distance_of_time_in_words_to_now to DateHelper#time_ago_in_words (old method name is still available as a deprecated alias)
+
+
+*1.9.1* (27th March, 2005)
+
+* Fixed that Active Record objects with float attribute could not be cloned #808
+
+* Fixed that MissingSourceFile's wasn't properly detected in production mode #925 [Nicholas Seckar]
+
+* Fixed that :counter_cache option would look for a line_items_count column for a LineItem object instead of lineitems_count
+
+* Fixed that AR exists?() would explode on postgresql if the passed id did not match the PK type #900 [Scott Barron]
+
+* Fixed the MS SQL adapter to work with the new limit/offset approach and with binary data (still suffering from 7KB limit, though) #901 [delynnb]
+
+
+*1.9.0* (22th March, 2005)
+
+* Added adapter independent limit clause as a two-element array with the first being the limit, the second being the offset #795 [Sam Stephenson]. Example:
+
+ Developer.find_all nil, 'id ASC', 5 # return the first five developers
+ Developer.find_all nil, 'id ASC', [3, 8] # return three developers, starting from #8 and forward
+
+ This doesn't yet work with the DB2 or MS SQL adapters. Patches to make that happen are encouraged.
+
+* Added alias_method :to_param, :id to Base, such that Active Record objects to be used as URL parameters in Action Pack automatically #812 [Nicholas Seckar/Sam Stephenson]
+
+* Improved the performance of the OCI8 adapter for Oracle #723 [pilx/gjenkins]
+
+* Added type conversion before saving a record, so string-based values like "10.0" aren't left for the database to convert #820 [dave@cherryville.org]
+
+* Added with additional settings for working with transactional fixtures and pre-loaded test databases #865 [mindel]
+
+* Fixed acts_as_list to trigger remove_from_list on destroy after the fact, not before, so a unique position can be maintained #871 [Alisdair McDiarmid]
+
+* Added the possibility of specifying fixtures in multiple calls #816 [kim@tinker.com]
+
+* Added Base.exists?(id) that'll return true if an object of the class with the given id exists #854 [stian@grytoyr.net]
+
+* Added optionally allow for nil or empty strings with validates_numericality_of #801 [Sebastian Kanthak]
+
+* Fixed problem with using slashes in validates_format_of regular expressions #801 [Sebastian Kanthak]
+
+* Fixed that SQLite3 exceptions are caught and reported properly #823 [yerejm]
+
+* Added that all types of after_find/after_initialized callbacks are triggered if the explicit implementation is present, not only the explicit implementation itself
+
+* Fixed that symbols can be used on attribute assignment, like page.emails.create(:subject => data.subject, :body => data.body)
+
+
+*1.8.0* (7th March, 2005)
+
+* Added ActiveRecord::Base.colorize_logging to control whether to use colors in logs or not (on by default)
+
+* Added support for timestamp with time zone in PostgreSQL #560 [Scott Barron]
+
+* Added MultiparameterAssignmentErrors and AttributeAssignmentError exceptions #777 [demetrius]. Documentation:
+
+ * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
+ +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
+ objects that should be inspected to determine which attributes triggered the errors.
+ * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
+ You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+
+* Fixed that postgresql adapter would fails when reading bytea fields with null value #771 [rodrigo k]
+
+* Added transactional fixtures that uses rollback to undo changes to fixtures instead of DELETE/INSERT -- it's much faster. See documentation under Fixtures #760 [Jeremy Kemper]
+
+* Added destruction of dependent objects in has_one associations when a new assignment happens #742 [mindel]. Example:
+
+ class Account < ActiveRecord::Base
+ has_one :credit_card, :dependent => true
+ end
+ class CreditCard < ActiveRecord::Base
+ belongs_to :account
+ end
+
+ account.credit_card # => returns existing credit card, lets say id = 12
+ account.credit_card = CreditCard.create("number" => "123")
+ account.save # => CC with id = 12 is destroyed
+
+
+* Added validates_numericality_of #716 [skanthak/c.r.mcgrath]. Docuemntation:
+
+ Validates whether the value of the specified attribute is numeric by trying to convert it to
+ a float with Kernel.Float (if integer is false) or applying it to the regular expression
+ /^[\+\-]?\d+$/ (if integer is set to true).
+
+ class Person < ActiveRecord::Base
+ validates_numericality_of :value, :on => :create
+ end
+
+ Configuration options:
+ * message - A custom error message (default is: "is not a number")
+ * on Specifies when this validation is active (default is :save, other options :create, :update)
+ * only_integer Specifies whether the value has to be an integer, e.g. an integral value (default is false)
+
+
+* Fixed that HasManyAssociation#count was using :finder_sql rather than :counter_sql if it was available #445 [Scott Barron]
+
+* Added better defaults for composed_of, so statements like composed_of :time_zone, :mapping => %w( time_zone time_zone ) can be written without the mapping part (it's now assumed)
+
+* Added MacroReflection#macro which will return a symbol describing the macro used (like :composed_of or :has_many) #718, #248 [james@slashetc.com]
+
+
+*1.7.0* (24th February, 2005)
+
+* Changed the auto-timestamping feature to use ActiveRecord::Base.default_timezone instead of entertaining the parallel ActiveRecord::Base.timestamps_gmt method. The latter is now deprecated and will throw a warning on use (but still work) #710 [Jamis Buck]
+
+* Added a OCI8-based Oracle adapter that has been verified to work with Oracle 8 and 9 #629 [Graham Jenkins]. Usage notes:
+
+ 1. Key generation uses a sequence "rails_sequence" for all tables. (I couldn't find a simple
+ and safe way of passing table-specific sequence information to the adapter.)
+ 2. Oracle uses DATE or TIMESTAMP datatypes for both dates and times. Consequently I have had to
+ resort to some hacks to get data converted to Date or Time in Ruby.
+ If the column_name ends in _at (like created_at, updated_at) it's created as a Ruby Time. Else if the
+ hours/minutes/seconds are 0, I make it a Ruby Date. Else it's a Ruby Time.
+ This is nasty - but if you use Duck Typing you'll probably not care very much.
+ In 9i it's tempting to map DATE to Date and TIMESTAMP to Time but I don't think that is
+ valid - too many databases use DATE for both.
+ Timezones and sub-second precision on timestamps are not supported.
+ 3. Default values that are functions (such as "SYSDATE") are not supported. This is a
+ restriction of the way active record supports default values.
+ 4. Referential integrity constraints are not fully supported. Under at least
+ some circumstances, active record appears to delete parent and child records out of
+ sequence and out of transaction scope. (Or this may just be a problem of test setup.)
+
+ The OCI8 driver can be retrieved from http://rubyforge.org/projects/ruby-oci8/
+
+* Added option :schema_order to the PostgreSQL adapter to support the use of multiple schemas per database #697 [YuriSchimke]
+
+* Optimized the SQL used to generate has_and_belongs_to_many queries by listing the join table first #693 [yerejm]
+
+* Fixed that when using validation macros with a custom message, if you happened to use single quotes in the message string you would get a parsing error #657 [tonka]
+
+* Fixed that Active Record would throw Broken Pipe errors with FCGI when the MySQL connection timed out instead of reconnecting #428 [Nicholas Seckar]
+
+* Added options to specify an SSL connection for MySQL. Define the following attributes in the connection config (config/database.yml in Rails) to use it: sslkey, sslcert, sslca, sslcapath, sslcipher. To use SSL with no client certs, just set :sslca = '/dev/null'. http://dev.mysql.com/doc/mysql/en/secure-connections.html #604 [daniel@nightrunner.com]
+
+* Added automatic dropping/creating of test tables for running the unit tests on all databases #587 [adelle@bullet.net.au]
+
+* Fixed that find_by_* would fail when column names had numbers #670 [demetrius]
+
+* Fixed the SQL Server adapter on a bunch of issues #667 [DeLynn]
+
+ 1. Created a new columns method that is much cleaner.
+ 2. Corrected a problem with the select and select_all methods
+ that didn't account for the LIMIT clause being passed into raw SQL statements.
+ 3. Implemented the string_to_time method in order to create proper instances of the time class.
+ 4. Added logic to the simplified_type method that allows the database to specify the scale of float data.
+ 5. Adjusted the quote_column_name to account for the fact that MS SQL is bothered by a forward slash in the data string.
+
+* Fixed that the dynamic finder like find_all_by_something_boolean(false) didn't work #649 [lmarlow@yahoo.com]
+
+* Added validates_each that validates each specified attribute against a block #610 [Jeremy Kemper]. Example:
+
+ class Person < ActiveRecord::Base
+ validates_each :first_name, :last_name do |record, attr|
+ record.errors.add attr, 'starts with z.' if attr[0] == ?z
+ end
+ end
+
+* Added :allow_nil as an explicit option for validates_length_of, so unless that's set to true having the attribute as nil will also return an error if a range is specified as :within #610 [Jeremy Kemper]
+
+* Added that validates_* now accept blocks to perform validations #618 [Tim Bates]. Example:
+
+ class Person < ActiveRecord::Base
+ validate { |person| person.errors.add("title", "will never be valid") if SHOULD_NEVER_BE_VALID }
+ end
+
+* Addded validation for validate all the associated objects before declaring failure with validates_associated #618 [Tim Bates]
+
+* Added keyword-style approach to defining the custom relational bindings #545 [Jamis Buck]. Example:
+
+ class Project < ActiveRecord::Base
+ primary_key "sysid"
+ table_name "XYZ_PROJECT"
+ inheritance_column { original_inheritance_column + "_id" }
+ end
+
+* Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
+
+
+*1.6.0* (January 25th, 2005)
+
+* Added that has_many association build and create methods can take arrays of record data like Base#create and Base#build to build/create multiple records at once.
+
+* Added that Base#delete and Base#destroy both can take an array of ids to delete/destroy #336
+
+* Added the option of supplying an array of attributes to Base#create, so that multiple records can be created at once.
+
+* Added the option of supplying an array of ids and attributes to Base#update, so that multiple records can be updated at once (inspired by #526/Duane Johnson). Example
+
+ people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
+ Person.update(people.keys, people.values)
+
+* Added ActiveRecord::Base.timestamps_gmt that can be set to true to make the automated timestamping use GMT instead of local time #520 [Scott Baron]
+
+* Added that update_all calls sanitize_sql on its updates argument, so stuff like MyRecord.update_all(['time = ?', Time.now]) works #519 [notahat]
+
+* Fixed that the dynamic finders didn't treat nil as a "IS NULL" but rather "= NULL" case #515 [Demetrius]
+
+* Added bind-named arrays for interpolating a group of ids or strings in conditions #528 [Jeremy Kemper]
+
+* Added that has_and_belongs_to_many associations with additional attributes also can be created between unsaved objects and only committed to the database when Base#save is called on the associator #524 [Eric Anderson]
+
+* Fixed that records fetched with piggy-back attributes or through rich has_and_belongs_to_many associations couldn't be saved due to the extra attributes not part of the table #522 [Eric Anderson]
+
+* Added mass-assignment protection for the inheritance column -- regardless of a custom column is used or not
+
+* Fixed that association proxies would fail === tests like PremiumSubscription === @account.subscription
+
+* Fixed that column aliases didn't work as expected with the new MySql411 driver #507 [Demetrius]
+
+* Fixed that find_all would produce invalid sql when called sequentialy #490 [Scott Baron]
+
+
+*1.5.1* (January 18th, 2005)
+
+* Fixed that the belongs_to and has_one proxy would fail a test like 'if project.manager' -- this unfortunately also means that you can't call methods like project.manager.build unless there already is a manager on the project #492 [Tim Bates]
+
+* Fixed that the Ruby/MySQL adapter wouldn't connect if the password was empty #503 [Pelle]
+
+
+*1.5.0* (January 17th, 2005)
+
+* Fixed that unit tests for MySQL are now run as the "rails" user instead of root #455 [Eric Hodel]
+
+* Added validates_associated that enables validation of objects in an unsaved association #398 [Tim Bates]. Example:
+
+ class Book < ActiveRecord::Base
+ has_many :pages
+ belongs_to :library
+
+ validates_associated :pages, :library
+ end
+
+* Added support for associating unsaved objects #402 [Tim Bates]. Rules that govern this addition:
+
+ == Unsaved objects and associations
+
+ You can manipulate objects and associations before they are saved to the database, but there is some special behaviour you should be
+ aware of, mostly involving the saving of associated objects.
+
+ === 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 (new_record? == true).
+ * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns false and the assignment
+ is cancelled.
+ * If you wish to assign an object to a has_one association without saving it, use the #association.build method (documented below).
+ * Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does
+ not save the parent either.
+
+ === Collections
+
+ * Adding an object to a collection (has_many or has_and_belongs_to_many) automatically saves that object, except if the parent object
+ (the owner of the collection) is not yet stored in the database.
+ * If saving any of the objects being added to a collection (via #push or similar) fails, then #push returns false.
+ * You can add an object to a collection without automatically saving it by using the #collection.build method (documented below).
+ * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
+
+* Added replace to associations, so you can do project.manager.replace(new_manager) or project.milestones.replace(new_milestones) #402 [Tim Bates]
+
+* Added build and create methods to has_one and belongs_to associations, so you can now do project.manager.build(attributes) #402 [Tim Bates]
+
+* Added that if a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks defined as methods on the model, which are called last. #402 [Tim Bates]
+
+* Fixed that Base#== wouldn't work for multiple references to the same unsaved object #402 [Tim Bates]
+
+* Fixed binary support for PostgreSQL #444 [alex@byzantine.no]
+
+* Added a differenciation between AssociationCollection#size and -length. Now AssociationCollection#size returns the size of the
+ collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and calling collection.size if it has. If
+ it's more likely than not that the collection does have a size larger than zero and you need to fetch that collection afterwards,
+ it'll take one less SELECT query if you use length.
+
+* Added Base#attributes that returns a hash of all the attributes with their names as keys and clones of their objects as values #433 [atyp.de]
+
+* Fixed that foreign keys named the same as the association would cause stack overflow #437 [Eric Anderson]
+
+* Fixed default scope of acts_as_list from "1" to "1 = 1", so it'll work in PostgreSQL (among other places) #427 [Alexey]
+
+* Added Base#reload that reloads the attributes of an object from the database #422 [Andreas Schwarz]
+
+* Added SQLite3 compatibility through the sqlite3-ruby adapter by Jamis Buck #381 [Jeremy Kemper]
+
+* Added support for the new protocol spoken by MySQL 4.1.1+ servers for the Ruby/MySQL adapter that ships with Rails #440 [Matt Mower]
+
+* Added that Observers can use the observes class method instead of overwriting self.observed_class().
+
+ Before:
+ class ListSweeper < ActiveRecord::Base
+ def self.observed_class() [ List, Item ]
+ end
+
+ After:
+ class ListSweeper < ActiveRecord::Base
+ observes List, Item
+ end
+
+* Fixed that conditions in has_many and has_and_belongs_to_many should be interpolated just like the finder_sql is
+
+* Fixed Base#update_attribute to be indifferent to whether a string or symbol is used to describe the name
+
+* Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it easier to flip a switch or flag.
+
+ Before: topic.update_attribute(:approved, !approved?)
+ After : topic.toggle!(:approved)
+
+* Added Base#increment!(attribute) and Base#decrement!(attribute) that also saves the records. Example:
+
+ page.views # => 1
+ page.increment!(:views) # executes an UPDATE statement
+ page.views # => 2
+
+ page.increment(:views).increment!(:views)
+ page.views # => 4
+
+* Added Base#increment(attribute) and Base#decrement(attribute) that encapsulates the += 1 and -= 1 patterns.
+
+
+*1.4.0* (January 4th, 2005)
+
+* Added automated optimistic locking if the field lock_version is present. Each update to the
+ record increments the lock_version column and the locking facilities ensure that records instantiated twice
+ will let the last one saved raise a StaleObjectError if the first was also updated. Example:
+
+ p1 = Person.find(1)
+ p2 = Person.find(1)
+
+ p1.first_name = "Michael"
+ p1.save
+
+ p2.first_name = "should fail"
+ p2.save # Raises a ActiveRecord::StaleObjectError
+
+ You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
+ or otherwise apply the business logic needed to resolve the conflict.
+
+ #384 [Michael Koziarski]
+
+* Added dynamic attribute-based finders as a cleaner way of getting objects by simple queries without turning to SQL.
+ They work by appending the name of an attribute to find_by_, so you get finders like Person.find_by_user_name,
+ Payment.find_by_transaction_id. So instead of writing Person.find_first(["user_name = ?", user_name]), you just do
+ Person.find_by_user_name(user_name).
+
+ It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
+ Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing
+ Person.find_first(["user_name = ? AND password = ?", user_name, password]), you just do
+ Person.find_by_user_name_and_password(user_name, password).
+
+ While primarily a construct for easier find_firsts, it can also be used as a construct for find_all by using calls like
+ Payment.find_all_by_amount(50) that is turned into Payment.find_all(["amount = ?", 50]). This is something not as equally useful,
+ though, as it's not possible to specify the order in which the objects are returned.
+
+* Added block-style for callbacks #332 [Jeremy Kemper].
+
+ Before:
+ before_destroy(Proc.new{ |record| Person.destroy_all "firm_id = #{record.id}" })
+
+ After:
+ before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
+
+* Added :counter_cache option to acts_as_tree that works just like the one you can define on belongs_to #371 [Josh]
+
+* Added Base.default_timezone accessor that determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates
+ and times from the database. This is set to :local by default.
+
+* Added the possibility for adapters to overwrite add_limit! to implement a different limiting scheme than "LIMIT X" used by MySQL, PostgreSQL, and SQLite.
+
+* Added the possibility of having objects with acts_as_list created before their scope is available or...
+
+* Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://raa.ruby-lang.org/project/ruby-db2/) #386 [Maik Schmidt]
+
+* Added the final touches to the Microsoft SQL Server adapter by Joey Gibson that makes it suitable for actual use #394 [DeLynn Barry]
+
+* Added that Base#find takes an optional options hash, including :conditions. Base#find_on_conditions deprecated in favor of #find with :conditions #407 [Jeremy Kemper]
+
+* Added HasManyAssociation#count that works like Base#count #413 [intinig]
+
+* Fixed handling of binary content in blobs and similar fields for Ruby/MySQL and SQLite #409 [xal]
+
+* Fixed a bug in the Ruby/MySQL that caused binary content to be escaped badly and come back mangled #405 [Tobias Luetke]
+
+* Fixed that the const_missing autoload assumes the requested constant is set by require_association and calls const_get to retrieve it.
+ If require_association did not set the constant then const_get will call const_missing, resulting in an infinite loop #380 [Jeremy Kemper]
+
+* Fixed broken transactions that were actually only running object-level and not db level transactions [andreas]
+
+* Fixed that validates_uniqueness_of used 'id' instead of defined primary key #406
+
+* Fixed that the overwritten respond_to? method didn't take two parameters like the original #391
+
+* Fixed quoting in validates_format_of that would allow some rules to pass regardless of input #390 [Dmitry V. Sabanin]
+
+
+*1.3.0* (December 23, 2004)
+
+* Added a require_association hook on const_missing that makes it possible to use any model class without requiring it first. This makes STI look like:
+
+ before:
+ require_association 'person'
+ class Employee < Person
+ end
+
+ after:
+ class Employee < Person
+ end
+
+ This also reduces the usefulness of Controller.model in Action Pack to currently only being for documentation purposes.
+
+* Added that Base.update_all and Base.delete_all return an integer of the number of affected rows #341
+
+* Added scope option to validation_uniqueness #349 [Kent Sibilev]
+
+* Added respondence to *_before_type_cast for all attributes to return their string-state before they were type casted by the column type.
+ This is helpful for getting "100,000" back on a integer-based validation where the value would normally be "100".
+
+* Added allow_nil options to validates_inclusion_of so that validation is only triggered if the attribute is not nil [what-a-day]
+
+* Added work-around for PostgreSQL and the problem of getting fixtures to be created from id 1 on each test case.
+ This only works for auto-incrementing primary keys called "id" for now #359 [Scott Baron]
+
+* Added Base#clear_association_cache to empty all the cached associations #347 [Tobias Luetke]
+
+* Added more informative exceptions in establish_connection #356 [Jeremy Kemper]
+
+* Added Base#update_attributes that'll accept a hash of attributes and save the record (returning true if it passed validation, false otherwise).
+
+ Before:
+ person.attributes = @params["person"]
+ person.save
+
+ Now:
+ person.update_attributes(@params["person"])
+
+* Added Base.destroy and Base.delete to remove records without holding a reference to them first.
+
+* Added that query benchmarking will only happen if its going to be logged anyway #344
+
+* Added higher_item and lower_item as public methods for acts_as_list #342 [Tobias Luetke]
+
+* Fixed that options[:counter_sql] was overwritten with interpolated sql rather than original sql #355 [Jeremy Kemper]
+
+* Fixed that overriding an attribute's accessor would be disregarded by add_on_empty and add_on_boundary_breaking because they simply used
+ the attributes[] hash instead of checking for @base.respond_to?(attr.to_s). [Marten]
+
+* Fixed that Base.table_name would expect a parameter when used in has_and_belongs_to_many joins [Anna Lissa Cruz]
+
+* Fixed that nested transactions now work by letting the outer most transaction have the responsibilty of starting and rolling back the transaction.
+ If any of the inner transactions swallow the exception raised, though, the transaction will not be rolled back. So always let the transaction
+ bubble up even when you've dealt with local issues. Closes #231 and #340.
+
+* Fixed validates_{confirmation,acceptance}_of to only happen when the virtual attributes are not nil #348 [dpiddy@gmail.com]
+
+* Changed the interface on AbstractAdapter to require that adapters return the number of affected rows on delete and update operations.
+
+* Fixed the automated timestamping feature when running under Rails' development environment that resets the inheritable attributes on each request.
+
+
+
+*1.2.0*
+
+* Added Base.validates_inclusion_of that validates whether the value of the specified attribute is available in a particular enumerable
+ object. [what-a-day]
+
+ class Person < ActiveRecord::Base
+ validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
+ validates_inclusion_of :age, :in=>0..99
+ end
+
+* Added acts_as_list that can decorates an existing class with methods like move_higher/lower, move_to_top/bottom. [Tobias Luetke] Example:
+
+ class TodoItem < ActiveRecord::Base
+ acts_as_list :scope => :todo_list_id
+ belongs_to :todo_list
+ end
+
+* Added acts_as_tree that can decorates an existing class with a many to many relationship with itself. Perfect for categories in
+ categories and the likes. [Tobias Luetke]
+
+* Added that Active Records will automatically record creation and/or update timestamps of database objects if fields of the names
+ created_at/created_on or updated_at/updated_on are present. [Tobias Luetke]
+
+* Added Base.default_error_messages as a hash of all the error messages used in the validates_*_of so they can be changed in one place [Tobias Luetke]
+
+* Added automatic transaction block around AssociationCollection.<<, AssociationCollection.delete, and AssociationCollection.destroy_all
+
+* Fixed that Base#find will return an array if given an array -- regardless of the number of elements #270 [Marten]
+
+* Fixed that has_and_belongs_to_many would generate bad sql when naming conventions differed from using vanilla "id" everywhere [RedTerror]
+
+* Added a better exception for when a type column is used in a table without the intention of triggering single-table inheritance. Example:
+
+ ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'bad_class!'.
+ This error is raised because the column 'type' is reserved for storing the class in case of inheritance.
+ Please rename this column if you didn't intend it to be used for storing the inheritance class or
+ overwrite Company.inheritance_column to use another column for that information.
+
+* Added that single-table inheritance will only kick in if the inheritance_column (by default "type") is present. Otherwise, inheritance won't
+ have any magic side effects.
+
+* Added the possibility of marking fields as being in error without adding a message (using nil) to it that'll get displayed wth full_messages #208 [mjobin]
+
+* Fixed Base.errors to be indifferent as to whether strings or symbols are used. Examples:
+
+ Before:
+ errors.add(:name, "must be shorter") if name.size > 10
+ errors.on(:name) # => "must be shorter"
+ errors.on("name") # => nil
+
+ After:
+ errors.add(:name, "must be shorter") if name.size > 10
+ errors.on(:name) # => "must be shorter"
+ errors.on("name") # => "must be shorter"
+
+* Added Base.validates_format_of that Validates whether the value of the specified attribute is of the correct form by matching
+ it against the regular expression provided. [Marcel]
+
+ class Person < ActiveRecord::Base
+ validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/, :on => :create
+ end
+
+* Added Base.validates_length_of that delegates to add_on_boundary_breaking #312 [Tobias Luetke]. Example:
+
+ Validates that the specified attribute matches the length restrictions supplied in either:
+
+ - configuration[:minimum]
+ - configuration[:maximum]
+ - configuration[:is]
+ - configuration[:within] (aka. configuration[:in])
+
+ Only one option can be used at a time.
+
+ class Person < ActiveRecord::Base
+ validates_length_of :first_name, :maximum=>30
+ validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
+ validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
+ validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
+ validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
+ end
+
+* Added Base.validate_presence as an alternative to implementing validate and doing errors.add_on_empty yourself.
+
+* Added Base.validates_uniqueness_of that alidates whether the value of the specified attributes are unique across the system.
+ Useful for making sure that only one user can be named "davidhh".
+
+ class Person < ActiveRecord::Base
+ validates_uniqueness_of :user_name
+ end
+
+ When the record is created, a check is performed to make sure that no record exist in the database with the given value for the specified
+ attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+
+
+* Added Base.validates_confirmation_of that encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
+
+ Model:
+ class Person < ActiveRecord::Base
+ validates_confirmation_of :password
+ end
+
+ View:
+ <%= password_field "person", "password" %>
+ <%= password_field "person", "password_confirmation" %>
+
+ The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
+ It exists only as an in-memory variable for validating the password. This check is performed both on create and update.
+
+
+* Added Base.validates_acceptance_of that encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
+
+ class Person < ActiveRecord::Base
+ validates_acceptance_of :terms_of_service
+ end
+
+ The terms_of_service attribute is entirely virtual. No database column is needed. This check is performed both on create and update.
+
+ NOTE: The agreement is considered valid if it's set to the string "1". This makes it easy to relate it to an HTML checkbox.
+
+
+* Added validation macros to make the stackable just like the lifecycle callbacks. Examples:
+
+ class Person < ActiveRecord::Base
+ validate { |record| record.errors.add("name", "too short") unless name.size > 10 }
+ validate { |record| record.errors.add("name", "too long") unless name.size < 20 }
+ validate_on_create :validate_password
+
+ private
+ def validate_password
+ errors.add("password", "too short") unless password.size > 6
+ end
+ end
+
+* Added the option for sanitizing find_by_sql and the offset parts in regular finds [Sam Stephenson]. Examples:
+
+ Project.find_all ["category = ?", category_name], "created ASC", ["? OFFSET ?", 15, 20]
+ Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
+
+* Fixed value quoting in all generated SQL statements, so that integers are not surrounded in quotes and that all sanitation are happening
+ through the database's own quoting routine. This should hopefully make it lots easier for new adapters that doesn't accept '1' for integer
+ columns.
+
+* Fixed has_and_belongs_to_many guessing of foreign key so that keys are generated correctly for models like SomeVerySpecialClient
+ [Florian Weber]
+
+* Added counter_sql option for has_many associations [Jeremy Kemper]. Documentation:
+
+ :counter_sql - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
+ specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
+
+* Fixed that methods wrapped in callbacks still return their original result #260 [Jeremy Kemper]
+
+* Fixed the Inflector to handle the movie/movies pair correctly #261 [Scott Baron]
+
+* Added named bind-style variable interpolation #281 [Michael Koziarski]. Example:
+
+ Person.find(["id = :id and first_name = :first_name", { :id => 5, :first_name = "bob' or 1=1" }])
+
+* Added bind-style variable interpolation for the condition arrays that uses the adapter's quote method [Michael Koziarski]
+
+ Before:
+ find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])]
+ find_first([ "firm_id = %s", firm_id ])] # unsafe!
+
+ After:
+ find_first([ "user_name = ? AND password = ?", user_name, password ])]
+ find_first([ "firm_id = ?", firm_id ])]
+
+* Added CSV format for fixtures #272 [what-a-day]. (See the new and expanded documentation on fixtures for more information)
+
+* Fixed fixtures using primary key fields called something else than "id" [dave]
+
+* Added proper handling of time fields that are turned into Time objects with the dummy date of 2000/1/1 [HariSeldon]
+
+* Added reverse order of deleting fixtures, so referential keys can be maintained #247 [Tim Bates]
+
+* Added relative path search for sqlite dbfiles in database.yml (if RAILS_ROOT is defined) #233 [Jeremy Kemper]
+
+* Added option to establish_connection where you'll be able to leave out the parameter to have it use the RAILS_ENV environment variable
+
+* Fixed problems with primary keys and postgresql sequences (#230) [Tim Bates]
+
+* Added reloading for associations under cached environments like FastCGI and mod_ruby. This makes it possible to use those environments for development.
+ This is turned on by default, but can be turned off with ActiveRecord::Base.reload_dependencies = false in production environments.
+
+ NOTE: This will only have an effect if you let the associations manage the requiring of model classes. All libraries loaded through
+ require will be "forever" cached. You can, however, use ActiveRecord::Base.load_or_require("library") to get this behavior outside of the
+ auto-loading associations.
+
+* Added ERB capabilities to the fixture files for dynamic fixture generation. You don't need to do anything, just include ERB blocks like:
+
+ david:
+ id: 1
+ name: David
+
+ jamis:
+ id: 2
+ name: Jamis
+
+ <% for digit in 3..10 %>
+ dev_<%= digit %>:
+ id: <%= digit %>
+ name: fixture_<%= digit %>
+ <% end %>
+
+* Changed the yaml fixture searcher to look in the root of the fixtures directory, so when you before could have something like:
+
+ fixtures/developers/fixtures.yaml
+ fixtures/accounts/fixtures.yaml
+
+ ...you now need to do:
+
+ fixtures/developers.yaml
+ fixtures/accounts.yaml
+
+* Changed the fixture format from:
+
+ name: david
+ data:
+ id: 1
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+ ---
+ name: steve
+ data:
+ id: 2
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+
+ ...to:
+
+ david:
+ id: 1
+ name: David Heinemeier Hansson
+ birthday: 1979-10-15
+ profession: Systems development
+
+ steve:
+ id: 2
+ name: Steve Ross Kellock
+ birthday: 1974-09-27
+ profession: guy with keyboard
+
+ The change is NOT backwards compatible. Fixtures written in the old YAML style needs to be rewritten!
+
+* All associations will now attempt to require the classes that they associate to. Relieving the need for most explicit 'require' statements.
+
+
+*1.1.0* (34)
+
+* Added automatic fixture setup and instance variable availability. Fixtures can also be automatically
+ instantiated in instance variables relating to their names using the following style:
+
+ class FixturesTest < Test::Unit::TestCase
+ fixtures :developers # you can add more with comma separation
+
+ def test_developers
+ assert_equal 3, @developers.size # the container for all the fixtures is automatically set
+ assert_kind_of Developer, @david # works like @developers["david"].find
+ assert_equal "David Heinemeier Hansson", @david.name
+ end
+ end
+
+* Added HasAndBelongsToManyAssociation#push_with_attributes(object, join_attributes) that can create associations in the join table with additional
+ attributes. This is really useful when you have information that's only relevant to the join itself, such as a "added_on" column for an association
+ between post and category. The added attributes will automatically be injected into objects retrieved through the association similar to the piggy-back
+ approach:
+
+ post.categories.push_with_attributes(category, :added_on => Date.today)
+ post.categories.first.added_on # => Date.today
+
+ NOTE: The categories table doesn't have a added_on column, it's the categories_post join table that does!
+
+* Fixed that :exclusively_dependent and :dependent can't be activated at the same time on has_many associations [Jeremy Kemper]
+
+* Fixed that database passwords couldn't be all numeric [Jeremy Kemper]
+
+* Fixed that calling id would create the instance variable for new_records preventing them from being saved correctly [Jeremy Kemper]
+
+* Added sanitization feature to HasManyAssociation#find_all so it works just like Base.find_all [Sam Stephenson/bitsweat]
+
+* Added that you can pass overlapping ids to find without getting duplicated records back [Jeremy Kemper]
+
+* Added that Base.benchmark returns the result of the block [Jeremy Kemper]
+
+* Fixed problem with unit tests on Windows with SQLite [paterno]
+
+* Fixed that quotes would break regular non-yaml fixtures [Dmitry Sabanin/daft]
+
+* Fixed fixtures on windows with line endings cause problems under unix / mac [Tobias Luetke]
+
+* Added HasAndBelongsToManyAssociation#find(id) that'll search inside the collection and find the object or record with that id
+
+* Added :conditions option to has_and_belongs_to_many that works just like the one on all the other associations
+
+* Added AssociationCollection#clear to remove all associations from has_many and has_and_belongs_to_many associations without destroying the records [geech]
+
+* Added type-checking and remove in 1-instead-of-N sql statements to AssociationCollection#delete [geech]
+
+* Added a return of self to AssociationCollection#<< so appending can be chained, like project << Milestone.create << Milestone.create [geech]
+
+* Added Base#hash and Base#eql? which means that all of the equality using features of array and other containers now works:
+
+ [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
+
+* Added :uniq as an option to has_and_belongs_to_many which will automatically ensure that AssociateCollection#uniq is called
+ before pulling records out of the association. This is especially useful for three-way (and above) has_and_belongs_to_many associations.
+
+* Added AssociateCollection#uniq which is especially useful for has_and_belongs_to_many associations that can include duplicates,
+ which is common on associations that also use metadata. Usage: post.categories.uniq
+
+* Fixed respond_to? to use a subclass specific hash instead of an Active Record-wide one
+
+* Fixed has_and_belongs_to_many to treat associations between classes in modules properly [Florian Weber]
+
+* Added a NoMethod exception to be raised when query and writer methods are called for attributes that doesn't exist [geech]
+
+* Added a more robust version of Fixtures that throws meaningful errors when on formatting issues [geech]
+
+* Added Base#transaction as a compliment to Base.transaction for prettier use in instance methods [geech]
+
+* Improved the speed of respond_to? by placing the dynamic methods lookup table in a hash [geech]
+
+* Added that any additional fields added to the join table in a has_and_belongs_to_many association
+ will be placed as attributes when pulling records out through has_and_belongs_to_many associations.
+ This is helpful when have information about the association itself that you want available on retrival.
+
+* Added better loading exception catching and RubyGems retries to the database adapters [alexeyv]
+
+* Fixed bug with per-model transactions [daniel]
+
+* Fixed Base#transaction so that it returns the result of the last expression in the transaction block [alexeyv]
+
+* Added Fixture#find to find the record corresponding to the fixture id. The record
+ class name is guessed by using Inflector#classify (also new) on the fixture directory name.
+
+ Before: Document.find(@documents["first"]["id"])
+ After : @documents["first"].find
+
+* Fixed that the table name part of column names ("TABLE.COLUMN") wasn't removed properly [Andreas Schwarz]
+
+* Fixed a bug with Base#size when a finder_sql was used that didn't capitalize SELECT and FROM [geech]
+
+* Fixed quoting problems on SQLite by adding quote_string to the AbstractAdapter that can be overwritten by the concrete
+ adapters for a call to the dbm. [Andreas Schwarz]
+
+* Removed RubyGems backup strategy for requiring SQLite-adapter -- if people want to use gems, they're already doing it with AR.
+
+
+*1.0.0 (35)*
+
+* Added OO-style associations methods [Florian Weber]. Examples:
+
+ Project#milestones_count => Project#milestones.size
+ Project#build_to_milestones => Project#milestones.build
+ Project#create_for_milestones => Project#milestones.create
+ Project#find_in_milestones => Project#milestones.find
+ Project#find_all_in_milestones => Project#milestones.find_all
+
+* Added serialize as a new class method to control when text attributes should be YAMLized or not. This means that automated
+ serialization of hashes, arrays, and so on WILL NO LONGER HAPPEN (#10). You need to do something like this:
+
+ class User < ActiveRecord::Base
+ serialize :settings
+ end
+
+ This will assume that settings is a text column and will now YAMLize any object put in that attribute. You can also specify
+ an optional :class_name option that'll raise an exception if a serialized object is retrieved as a descendent of a class not in
+ the hierarchy. Example:
+
+ class User < ActiveRecord::Base
+ serialize :settings, :class_name => "Hash"
+ end
+
+ user = User.create("settings" => %w( one two three ))
+ User.find(user.id).settings # => raises SerializationTypeMismatch
+
+* Added the option to connect to a different database for one model at a time. Just call establish_connection on the class
+ you want to have connected to another database than Base. This will automatically also connect decendents of that class
+ to the different database [Renald Buter].
+
+* Added transactional protection for Base#save. Validations can now check for values knowing that it happens in a transaction and callbacks
+ can raise exceptions knowing that the save will be rolled back. [Suggested by Alexey Verkhovsky]
+
+* Added column name quoting so reserved words, such as "references", can be used as column names [Ryan Platte]
+
+* Added the possibility to chain the return of what happened inside a logged block [geech]:
+
+ This now works:
+ log { ... }.map { ... }
+
+ Instead of doing:
+ result = []
+ log { result = ... }
+ result.map { ... }
+
+* Added "socket" option for the MySQL adapter, so you can change it to something else than "/tmp/mysql.sock" [Anna Lissa Cruz]
+
+* Added respond_to? answers for all the attribute methods. So if Person has a name attribute retrieved from the table schema,
+ person.respond_to? "name" will return true.
+
+* Added Base.benchmark which can be used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
+ Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
+
+ Project.benchmark("Creating project") do
+ project = Project.create("name" => "stuff")
+ project.create_manager("name" => "David")
+ project.milestones << Milestone.find_all
+ end
+
+* Added logging of invalid SQL statements [Suggested by Daniel Von Fange]
+
+* Added alias Errors#[] for Errors#on, so you can now say person.errors["name"] to retrieve the errors for name [Andreas Schwarz]
+
+* Added RubyGems require attempt if sqlite-ruby is not available through regular methods.
+
+* Added compatibility with 2.x series of sqlite-ruby drivers. [Jamis Buck]
+
+* Added type safety for association assignments, so a ActiveRecord::AssociationTypeMismatch will be raised if you attempt to
+ assign an object that's not of the associated class. This cures the problem with nil giving id = 4 and fixnums giving id = 1 on
+ mistaken association assignments. [Reported by Andreas Schwarz]
+
+* Added the option to keep many fixtures in one single YAML document [what-a-day]
+
+* Added the class method "inheritance_column" that can be overwritten to return the name of an alternative column than "type" for storing
+ the type for inheritance hierarchies. [Dave Steinberg]
+
+* Added [] and []= as an alternative way to access attributes when the regular methods have been overwritten [Dave Steinberg]
+
+* Added the option to observer more than one class at the time by specifying observed_class as an array
+
+* Added auto-id propagation support for tables with arbitrary primary keys that have autogenerated sequences associated with them
+ on PostgreSQL. [Dave Steinberg]
+
+* Changed that integer and floats set to "" through attributes= remain as NULL. This was especially a problem for scaffolding and postgresql. (#49)
+
+* Changed the MySQL Adapter to rely on MySQL for its defaults for socket, host, and port [Andreas Schwarz]
+
+* Changed ActionControllerError to decent from StandardError instead of Exception. It can now be caught by a generic rescue.
+
+* Changed class inheritable attributes to not use eval [Caio Chassot]
+
+* Changed Errors#add to now use "invalid" as the default message instead of true, which means full_messages work with those [Marcel Molina Jr]
+
+* Fixed spelling on Base#add_on_boundry_breaking to Base#add_on_boundary_breaking (old naming still works) [Marcel Molina Jr.]
+
+* Fixed that entries in the has_and_belongs_to_many join table didn't get removed when an associated object was destroyed.
+
+* Fixed unnecessary calls to SET AUTOCOMMIT=0/1 for MySQL adapter [Andreas Schwarz]
+
+* Fixed PostgreSQL defaults are now handled gracefully [Dave Steinberg]
+
+* Fixed increment/decrement_counter are now atomic updates [Andreas Schwarz]
+
+* Fixed the problems the Inflector had turning Attachment into attuchments and Cases into Casis [radsaq/Florian Gross]
+
+* Fixed that cloned records would point attribute references on the parent object [Andreas Schwarz]
+
+* Fixed SQL for type call on inheritance hierarchies [Caio Chassot]
+
+* Fixed bug with typed inheritance [Florian Weber]
+
+* Fixed a bug where has_many collection_count wouldn't use the conditions specified for that association
+
+
+*0.9.5*
+
+* Expanded the table_name guessing rules immensely [Florian Green]. Documentation:
+
+ Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
+ directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
+ to guess the table name from even when called on Reply. The guessing rules are as follows:
+ * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
+ * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies",
+ so a Category class becomes a categories table.
+ * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
+ * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
+ * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
+ * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table.
+ * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table.
+ * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table.
+ * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table.
+ * Class name ends in an "s": No additional characters are added or removed.
+ * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table.
+ * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
+ Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
+ So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
+
+ You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
+ "mice" table. Example:
+
+ class Mouse < ActiveRecord::Base
+ def self.table_name() "mice" end
+ end
+
+ This conversion is now done through an external class called Inflector residing in lib/active_record/support/inflector.rb.
+
+* Added find_all_in_collection to has_many defined collections. Works like this:
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ end
+
+ firm.id # => 1
+ firm.find_all_in_clients "revenue > 1000" # SELECT * FROM clients WHERE firm_id = 1 AND revenue > 1000
+
+ [Requested by Dave Thomas]
+
+* Fixed finders for inheritance hierarchies deeper than one level [Florian Weber]
+
+* Added add_on_boundry_breaking to errors to accompany add_on_empty as a default validation method. It's used like this:
+
+ class Person < ActiveRecord::Base
+ protected
+ def validation
+ errors.add_on_boundry_breaking "password", 3..20
+ end
+ end
+
+ This will add an error to the tune of "is too short (minimum is 3 characters)" or "is too long (minimum is 20 characters)" if
+ the password is outside the boundry. The messages can be changed by passing a third and forth parameter as message strings.
+
+* Implemented a clone method that works properly with AR. It returns a clone of the record that
+ hasn't been assigned an id yet and is treated as a new record.
+
+* Allow for domain sockets in PostgreSQL by not assuming localhost when no host is specified [Scott Barron]
+
+* Fixed that bignums are saved properly instead of attempted to be YAMLized [Andreas Schwartz]
+
+* Fixed a bug in the GEM where the rdoc options weren't being passed according to spec [Chad Fowler]
+
+* Fixed a bug with the exclusively_dependent option for has_many
+
+
+*0.9.4*
+
+* Correctly guesses the primary key when the class is inside a module [Dave Steinberg].
+
+* Added [] and []= as alternatives to read_attribute and write_attribute [Dave Steinberg]
+
+* has_and_belongs_to_many now accepts an :order key to determine in which order the collection is returned [radsaq].
+
+* The ids passed to find and find_on_conditions are now automatically sanitized.
+
+* Added escaping of plings in YAML content.
+
+* Multi-parameter assigns where all the parameters are empty will now be set to nil instead of a new instance of their class.
+
+* Proper type within an inheritance hierarchy is now ensured already at object initialization (instead of first at create)
+
+
+*0.9.3*
+
+* Fixed bug with using a different primary key name together with has_and_belongs_to_many [Investigation by Scott]
+
+* Added :exclusively_dependent option to the has_many association macro. The doc reads:
+
+ If set to true all the associated object are deleted in one SQL statement without having their
+ before_destroy callback run. This should only be used on associations that depend solely on
+ this class and don't need to do any clean-up in before_destroy. The upside is that it's much
+ faster, especially if there's a counter_cache involved.
+
+* Added :port key to connection options, so the PostgreSQL and MySQL adapters can connect to a database server
+ running on another port than the default.
+
+* Converted the new natural singleton methods that prevented AR objects from being saved by PStore
+ (and hence be placed in a Rails session) to a module. [Florian Weber]
+
+* Fixed the use of floats (was broken since 0.9.0+)
+
+* Fixed PostgreSQL adapter so default values are displayed properly when used in conjunction with
+ Action Pack scaffolding.
+
+* Fixed booleans support for PostgreSQL (use real true/false on boolean fields instead of 0/1 on tinyints) [radsaq]
+
+
+*0.9.2*
+
+* Added static method for instantly updating a record
+
+* Treat decimal and numeric as Ruby floats [Andreas Schwartz]
+
+* Treat chars as Ruby strings (fixes problem for Action Pack form helpers too)
+
+* Removed debugging output accidently left in (which would screw web applications)
+
+
+*0.9.1*
+
+* Added MIT license
+
+* Added natural object-style assignment for has_and_belongs_to_many associations. Consider the following model:
+
+ class Event < ActiveRecord::Base
+ has_one_and_belongs_to_many :sponsors
+ end
+
+ class Sponsor < ActiveRecord::Base
+ has_one_and_belongs_to_many :sponsors
+ end
+
+ Earlier, you'd have to use synthetic methods for creating associations between two objects of the above class:
+
+ roskilde_festival.add_to_sponsors(carlsberg)
+ roskilde_festival.remove_from_sponsors(carlsberg)
+
+ nike.add_to_events(world_cup)
+ nike.remove_from_events(world_cup)
+
+ Now you can use regular array-styled methods:
+
+ roskilde_festival.sponsors << carlsberg
+ roskilde_festival.sponsors.delete(carlsberg)
+
+ nike.events << world_cup
+ nike.events.delete(world_cup)
+
+* Added delete method for has_many associations. Using this will nullify an association between the has_many and the belonging
+ object by setting the foreign key to null. Consider this model:
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ You could do something like:
+
+ funny_comment.has_post? # => true
+ announcement.comments.delete(funny_comment)
+ funny_comment.has_post? # => false
+
+
+*0.9.0*
+
+* Active Record is now thread safe! (So you can use it with Cerise and WEBrick applications)
+ [Implementation idea by Michael Neumann, debugging assistance by Jamis Buck]
+
+* Improved performance by roughly 400% on a basic test case of pulling 100 records and querying one attribute.
+ This brings the tax for using Active Record instead of "riding on the metal" (using MySQL-ruby C-driver directly) down to ~50%.
+ Done by doing lazy type conversions and caching column information on the class-level.
+
+* Added callback objects and procs as options for implementing the target for callback macros.
+
+* Added "counter_cache" option to belongs_to that automates the usage of increment_counter and decrement_counter. Consider:
+
+ class Post < ActiveRecord::Base
+ has_many :comments
+ end
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post
+ end
+
+ Iterating over 100 posts like this:
+
+ <% for post in @posts %>
+ <%= post.title %> has <%= post.comments_count %> comments
+ <% end %>
+
+ Will generate 100 SQL count queries -- one for each call to post.comments_count. If you instead add a "comments_count" int column
+ to the posts table and rewrite the comments association macro with:
+
+ class Comment < ActiveRecord::Base
+ belongs_to :post, :counter_cache => true
+ end
+
+ Those 100 SQL count queries will be reduced to zero. Beware that counter caching is only appropriate for objects that begin life
+ with the object it's specified to belong with and is destroyed like that as well. Typically objects where you would also specify
+ :dependent => true. If your objects switch from one belonging to another (like a post that can be move from one category to another),
+ you'll have to manage the counter yourself.
+
+* Added natural object-style assignment for has_one and belongs_to associations. Consider the following model:
+
+ class Project < ActiveRecord::Base
+ has_one :manager
+ end
+
+ class Manager < ActiveRecord::Base
+ belongs_to :project
+ end
+
+ Earlier, assignments would work like following regardless of which way the assignment told the best story:
+
+ active_record.manager_id = david.id
+
+ Now you can do it either from the belonging side:
+
+ david.project = active_record
+
+ ...or from the having side:
+
+ active_record.manager = david
+
+ If the assignment happens from the having side, the assigned object is automatically saved. So in the example above, the
+ project_id attribute on david would be set to the id of active_record, then david would be saved.
+
+* Added natural object-style assignment for has_many associations [Florian Weber]. Consider the following model:
+
+ class Project < ActiveRecord::Base
+ has_many :milestones
+ end
+
+ class Milestone < ActiveRecord::Base
+ belongs_to :project
+ end
+
+ Earlier, assignments would work like following regardless of which way the assignment told the best story:
+
+ deadline.project_id = active_record.id
+
+ Now you can do it either from the belonging side:
+
+ deadline.project = active_record
+
+ ...or from the having side:
+
+ active_record.milestones << deadline
+
+ The milestone is automatically saved with the new foreign key.
+
+* API CHANGE: Attributes for text (or blob or similar) columns will now have unknown classes stored using YAML instead of using
+ to_s. (Known classes that won't be yamelized are: String, NilClass, TrueClass, FalseClass, Fixnum, Date, and Time).
+ Likewise, data pulled out of text-based attributes will be attempted converged using Yaml if they have the "--- " header.
+ This was primarily done to be enable the storage of hashes and arrays without wrapping them in aggregations, so now you can do:
+
+ user = User.find(1)
+ user.preferences = { "background" => "black", "display" => large }
+ user.save
+
+ User.find(1).preferences # => { "background" => "black", "display" => large }
+
+ Please note that this method should only be used when you don't care about representing the object in proper columns in
+ the database. A money object consisting of an amount and a currency is still a much better fit for a value object done through
+ aggregations than this new option.
+
+* POSSIBLE CODE BREAKAGE: As a consequence of the lazy type conversions, it's a bad idea to reference the @attributes hash
+ directly (it always was, but now it's paramount that you don't). If you do, you won't get the type conversion. So to implement
+ new accessors for existing attributes, use read_attribute(attr_name) and write_attribute(attr_name, value) instead. Like this:
+
+ class Song < ActiveRecord::Base
+ # Uses an integer of seconds to hold the length of the song
+
+ def length=(minutes)
+ write_attribute("length", minutes * 60)
+ end
+
+ def length
+ read_attribute("length") / 60
+ end
+ end
+
+ The clever kid will notice that this opens a door to sidestep the automated type conversion by using @attributes directly.
+ This is not recommended as read/write_attribute may be granted additional responsibilities in the future, but if you think
+ you know what you're doing and aren't afraid of future consequences, this is an option.
+
+* Applied a few minor bug fixes reported by Daniel Von Fange.
+
+
+*0.8.4*
+
+_Reflection_
+
+* Added ActiveRecord::Reflection with a bunch of methods and classes for reflecting in aggregations and associations.
+
+* Added Base.columns and Base.content_columns which returns arrays of column description (type, default, etc) objects.
+
+* Added Base#attribute_names which returns an array of names for the attributes available on the object.
+
+* Added Base#column_for_attribute(name) which returns the column description object for the named attribute.
+
+
+_Misc_
+
+* Added multi-parameter assignment:
+
+ # Instantiate objects for all attribute classes that needs more than one constructor parameter. This is done
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
+ # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
+ # s for String, and a for Array.
+
+ This is incredibly useful for assigning dates from HTML drop-downs of month, year, and day.
+
+* Fixed bug with custom primary key column name and Base.find on multiple parameters.
+
+* Fixed bug with dependent option on has_one associations if there was no associated object.
+
+
+*0.8.3*
+
+_Transactions_
+
+* Added transactional protection for destroy (important for the new :dependent option) [Suggested by Carl Youngblood]
+
+* Fixed so transactions are ignored on MyISAM tables for MySQL (use InnoDB to get transactions)
+
+* Changed transactions so only exceptions will cause a rollback, not returned false.
+
+
+_Mapping_
+
+* Added support for non-integer primary keys [Aredridel/earlier work by Michael Neumann]
+
+ User.find "jdoe"
+ Product.find "PDKEY-INT-12"
+
+* Added option to specify naming method for primary key column. ActiveRecord::Base.primary_key_prefix_type can either
+ be set to nil, :table_name, or :table_name_with_underscore. :table_name will assume that Product class has a primary key
+ of "productid" and :table_name_with_underscore will assume "product_id". The default nil will just give "id".
+
+* Added an overwriteable primary_key method that'll instruct AR to the name of the
+ id column [Aredridele/earlier work by Guan Yang]
+
+ class Project < ActiveRecord::Base
+ def self.primary_key() "project_id" end
+ end
+
+* Fixed that Active Records can safely associate inside and out of modules.
+
+ class MyApplication::Account < ActiveRecord::Base
+ has_many :clients # will look for MyApplication::Client
+ has_many :interests, :class_name => "Business::Interest" # will look for Business::Interest
+ end
+
+* Fixed that Active Records can safely live inside modules [Aredridel]
+
+ class MyApplication::Account < ActiveRecord::Base
+ end
+
+
+_Misc_
+
+* Added freeze call to value object assignments to ensure they remain immutable [Spotted by Gavin Sinclair]
+
+* Changed interface for specifying observed class in observers. Was OBSERVED_CLASS constant, now is
+ observed_class() class method. This is more consistant with things like self.table_name(). Works like this:
+
+ class AuditObserver < ActiveRecord::Observer
+ def self.observed_class() Account end
+ def after_update(account)
+ AuditTrail.new(account, "UPDATED")
+ end
+ end
+
+ [Suggested by Gavin Sinclair]
+
+* Create new Active Record objects by setting the attributes through a block. Like this:
+
+ person = Person.new do |p|
+ p.name = 'Freddy'
+ p.age = 19
+ end
+
+ [Suggested by Gavin Sinclair]
+
+
+*0.8.2*
+
+* Added inheritable callback queues that can ensure that certain callback methods or inline fragments are
+ run throughout the entire inheritance hierarchy. Regardless of whether a descendent overwrites the callback
+ method:
+
+ class Topic < ActiveRecord::Base
+ before_destroy :destroy_author, 'puts "I'm an inline fragment"'
+ end
+
+ Learn more in link:classes/ActiveRecord/Callbacks.html
+
+* Added :dependent option to has_many and has_one, which will automatically destroy associated objects when
+ the holder is destroyed:
+
+ class Album < ActiveRecord::Base
+ has_many :tracks, :dependent => true
+ end
+
+ All the associated tracks are destroyed when the album is.
+
+* Added Base.create as a factory that'll create, save, and return a new object in one step.
+
+* Automatically convert strings in config hashes to symbols for the _connection methods. This allows you
+ to pass the argument hashes directly from yaml. (Luke)
+
+* Fixed the install.rb to include simple.rb [Spotted by Kevin Bullock]
+
+* Modified block syntax to better follow our code standards outlined in
+ http://www.rubyonrails.org/CodingStandards
+
+
+*0.8.1*
+
+* Added object-level transactions [Thanks to Austin Ziegler for Transaction::Simple]
+
+* Changed adapter-specific connection methods to use centralized ActiveRecord::Base.establish_connection,
+ which is parametized through a config hash with symbol keys instead of a regular parameter list.
+ This will allow for database connections to be opened in a more generic fashion. (Luke)
+
+ NOTE: This requires all *_connections to be updated! Read more in:
+ http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000081
+
+* Fixed SQLite adapter so objects fetched from has_and_belongs_to_many have proper attributes
+ (t.name is now name). [Spotted by Garrett Rooney]
+
+* Fixed SQLite adapter so dates are returned as Date objects, not Time objects [Spotted by Gavin Sinclair]
+
+* Fixed requirement of date class, so date conversions are succesful regardless of whether you
+ manually require date or not.
+
+
+*0.8.0*
+
+* Added transactions
+
+* Changed Base.find to also accept either a list (1, 5, 6) or an array of ids ([5, 7])
+ as parameter and then return an array of objects instead of just an object
+
+* Fixed method has_collection? for has_and_belongs_to_many macro to behave as a
+ collection, not an association
+
+* Fixed SQLite adapter so empty or nil values in columns of datetime, date, or time type
+ aren't treated as current time [Spotted by Gavin Sinclair]
+
+
+*0.7.6*
+
+* Fixed the install.rb to create the lib/active_record/support directory [Spotted by Gavin Sinclair]
+* Fixed that has_association? would always return true [Spotted by Daniel Von Fange]
diff --git a/vendor/rails/activerecord/README b/vendor/rails/activerecord/README
new file mode 100644
index 0000000..d68eb28
--- /dev/null
+++ b/vendor/rails/activerecord/README
@@ -0,0 +1,351 @@
+= Active Record -- Object-relation mapping put on rails
+
+Active Record connects business objects and database tables to create a persistable
+domain model where logic and data are presented in one wrapping. It's an implementation
+of the object-relational mapping (ORM) pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html]
+by the same name as described by Martin Fowler:
+
+ "An object that wraps a row in a database table or view, encapsulates
+ the database access, and adds domain logic on that data."
+
+Active Record's main contribution to the pattern is to relieve the original of two stunting problems:
+lack of associations and inheritance. By adding a simple domain language-like set of macros to describe
+the former and integrating the Single Table Inheritance pattern for the latter, Active Record narrows the
+gap of functionality between the data mapper and active record approach.
+
+A short rundown of the major features:
+
+* Automated mapping between classes and tables, attributes and columns.
+
+ class Product < ActiveRecord::Base; end
+
+ ...is automatically mapped to the table named "products", such as:
+
+ CREATE TABLE products (
+ id int(11) NOT NULL auto_increment,
+ name varchar(255),
+ PRIMARY KEY (id)
+ );
+
+ ...which again gives Product#name and Product#name=(new_name)
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Associations between objects controlled by simple meta-programming macros.
+
+ class Firm < ActiveRecord::Base
+ has_many :clients
+ has_one :account
+ belongs_to :conglomorate
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
+
+
+* Aggregations of value objects controlled by simple meta-programming macros.
+
+ class Account < ActiveRecord::Base
+ composed_of :balance, :class_name => "Money",
+ :mapping => %w(balance amount)
+ composed_of :address,
+ :mapping => [%w(address_street street), %w(address_city city)]
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
+
+
+* Validation rules that can differ for new or existing objects.
+
+ class Account < ActiveRecord::Base
+ validates_presence_of :subdomain, :name, :email_address, :password
+ validates_uniqueness_of :subdomain
+ validates_acceptance_of :terms_of_service, :on => :create
+ validates_confirmation_of :password, :email_address, :on => :create
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Validations.html]
+
+* Callbacks as methods or queues on the entire lifecycle (instantiation, saving, destroying, validating, etc).
+
+ class Person < ActiveRecord::Base
+ def before_destroy # is called just before Person#destroy
+ CreditCard.find(credit_card_id).destroy
+ end
+ end
+
+ class Account < ActiveRecord::Base
+ after_find :eager_load, 'self.class.announce(#{id})'
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Callbacks.html]
+
+
+* Observers for the entire lifecycle
+
+ class CommentObserver < ActiveRecord::Observer
+ def after_create(comment) # is called just after Comment#save
+ Notifications.deliver_new_comment("david@loudthinking.com", comment)
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Observer.html]
+
+
+* Inheritance hierarchies
+
+ class Company < ActiveRecord::Base; end
+ class Firm < Company; end
+ class Client < Company; end
+ class PriorityClient < Client; end
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Transactions
+
+ # Database transaction
+ Account.transaction do
+ david.withdrawal(100)
+ mary.deposit(100)
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
+
+
+* Reflections on columns, associations, and aggregations
+
+ reflection = Firm.reflect_on_association(:clients)
+ reflection.klass # => Client (class)
+ Firm.columns # Returns an array of column descriptors for the firms table
+
+ {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
+
+
+* Direct manipulation (instead of service invocation)
+
+ So instead of (Hibernate[http://www.hibernate.org/] example):
+
+ long pkId = 1234;
+ DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
+ // something interesting involving a cat...
+ sess.save(cat);
+ sess.flush(); // force the SQL INSERT
+
+ Active Record lets you:
+
+ pkId = 1234
+ cat = Cat.find(pkId)
+ # something even more interesting involving the same cat...
+ cat.save
+
+ {Learn more}[link:classes/ActiveRecord/Base.html]
+
+
+* Database abstraction through simple adapters (~100 lines) with a shared connector
+
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite", :database => "dbfile")
+
+ ActiveRecord::Base.establish_connection(
+ :adapter => "mysql",
+ :host => "localhost",
+ :username => "me",
+ :password => "secret",
+ :database => "activerecord"
+ )
+
+ {Learn more}[link:classes/ActiveRecord/Base.html#M000081] and read about the built-in support for
+ MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html], PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], SQLite[link:classes/ActiveRecord/ConnectionAdapters/SQLiteAdapter.html], Oracle[link:classes/ActiveRecord/ConnectionAdapters/OracleAdapter.html], SQLServer[link:classes/ActiveRecord/ConnectionAdapters/SQLServerAdapter.html], and DB2[link:classes/ActiveRecord/ConnectionAdapters/DB2Adapter.html].
+
+
+* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
+
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
+ ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
+
+
+* Database agnostic schema management with Migrations
+
+ class AddSystemSettings < ActiveRecord::Migration
+ def self.up
+ create_table :system_settings do |t|
+ t.string :name
+ t.string :label
+ t.text :value
+ t.string :type
+ t.integer :position
+ end
+
+ SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ end
+
+ def self.down
+ drop_table :system_settings
+ end
+ end
+
+ {Learn more}[link:classes/ActiveRecord/Migration.html]
+
+== Simple example (1/2): Defining tables and classes (using MySQL)
+
+Data definitions are specified only in the database. Active Record queries the database for
+the column names (that then serves to determine which attributes are valid) on regular
+object instantiation through the new constructor and relies on the column names in the rows
+with the finders.
+
+ # CREATE TABLE companies (
+ # id int(11) unsigned NOT NULL auto_increment,
+ # client_of int(11),
+ # name varchar(255),
+ # type varchar(100),
+ # PRIMARY KEY (id)
+ # )
+
+Active Record automatically links the "Company" object to the "companies" table
+
+ class Company < ActiveRecord::Base
+ has_many :people, :class_name => "Person"
+ end
+
+ class Firm < Company
+ has_many :clients
+
+ def people_with_all_clients
+ clients.inject([]) { |people, client| people + client.people }
+ end
+ end
+
+The foreign_key is only necessary because we didn't use "firm_id" in the data definition
+
+ class Client < Company
+ belongs_to :firm, :foreign_key => "client_of"
+ end
+
+ # CREATE TABLE people (
+ # id int(11) unsigned NOT NULL auto_increment,
+ # name text,
+ # company_id text,
+ # PRIMARY KEY (id)
+ # )
+
+Active Record will also automatically link the "Person" object to the "people" table
+
+ class Person < ActiveRecord::Base
+ belongs_to :company
+ end
+
+== Simple example (2/2): Using the domain
+
+Picking a database connection for all the Active Records
+
+ ActiveRecord::Base.establish_connection(
+ :adapter => "mysql",
+ :host => "localhost",
+ :username => "me",
+ :password => "secret",
+ :database => "activerecord"
+ )
+
+Create some fixtures
+
+ firm = Firm.new("name" => "Next Angle")
+ # SQL: INSERT INTO companies (name, type) VALUES("Next Angle", "Firm")
+ firm.save
+
+ client = Client.new("name" => "37signals", "client_of" => firm.id)
+ # SQL: INSERT INTO companies (name, client_of, type) VALUES("37signals", 1, "Firm")
+ client.save
+
+Lots of different finders
+
+ # SQL: SELECT * FROM companies WHERE id = 1
+ next_angle = Company.find(1)
+
+ # SQL: SELECT * FROM companies WHERE id = 1 AND type = 'Firm'
+ next_angle = Firm.find(1)
+
+ # SQL: SELECT * FROM companies WHERE id = 1 AND name = 'Next Angle'
+ next_angle = Company.find(:first, :conditions => "name = 'Next Angle'")
+
+ next_angle = Firm.find_by_sql("SELECT * FROM companies WHERE id = 1").first
+
+The supertype, Company, will return subtype instances
+
+ Firm === next_angle
+
+All the dynamic methods added by the has_many macro
+
+ next_angle.clients.empty? # true
+ next_angle.clients.size # total number of clients
+ all_clients = next_angle.clients
+
+Constrained finds makes access security easier when ID comes from a web-app
+
+ # SQL: SELECT * FROM companies WHERE client_of = 1 AND type = 'Client' AND id = 2
+ thirty_seven_signals = next_angle.clients.find(2)
+
+Bi-directional associations thanks to the "belongs_to" macro
+
+ thirty_seven_signals.firm.nil? # true
+
+
+== Philosophy
+
+Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
+object-relational mapping. The prime directive for this mapping has been to minimize
+the amount of code needed to build a real-world domain model. This is made possible
+by relying on a number of conventions that make it easy for Active Record to infer
+complex relations and structures from a minimal amount of explicit direction.
+
+Convention over Configuration:
+* No XML-files!
+* Lots of reflection and run-time extension
+* Magic is not inherently a bad word
+
+Admit the Database:
+* Lets you drop down to SQL for odd cases and performance
+* Doesn't attempt to duplicate or replace data definitions
+
+
+== Download
+
+The latest version of Active Record can be found at
+
+* http://rubyforge.org/project/showfiles.php?group_id=182
+
+Documentation can be found at
+
+* http://ar.rubyonrails.com
+
+
+== Installation
+
+The prefered method of installing Active Record is through its GEM file. You'll need to have
+RubyGems[http://rubygems.rubyforge.org/wiki/wiki.pl] installed for that, though. If you have,
+then use:
+
+ % [sudo] gem install activerecord-1.10.0.gem
+
+You can also install Active Record the old-fashioned way with the following command:
+
+ % [sudo] ruby install.rb
+
+from its distribution directory.
+
+
+== License
+
+Active Record is released under the MIT license.
+
+
+== Support
+
+The Active Record homepage is http://www.rubyonrails.com. You can find the Active Record
+RubyForge page at http://rubyforge.org/projects/activerecord. And as Jim from Rake says:
+
+ Feel free to submit commits or feature requests. If you send a patch,
+ remember to update the corresponding unit tests. If fact, I prefer
+ new feature to be submitted in the form of new unit tests.
+
+For other information, feel free to ask on the rubyonrails-talk
+(http://groups.google.com/group/rubyonrails-talk) mailing list.
diff --git a/vendor/rails/activerecord/RUNNING_UNIT_TESTS b/vendor/rails/activerecord/RUNNING_UNIT_TESTS
new file mode 100644
index 0000000..39fc867
--- /dev/null
+++ b/vendor/rails/activerecord/RUNNING_UNIT_TESTS
@@ -0,0 +1,36 @@
+== Creating the test database
+
+The default names for the test databases are "activerecord_unittest" and
+"activerecord_unittest2". If you want to use another database name then be sure
+to update the connection adapter setups you want to test with in
+test/connections//connection.rb.
+When you have the database online, you can import the fixture tables with
+the test/schema/*.sql files.
+
+Make sure that you create database objects with the same user that you specified in
+connection.rb otherwise (on Postgres, at least) tests for default values will fail.
+
+== Running with Rake
+
+The easiest way to run the unit tests is through Rake. The default task runs
+the entire test suite for all the adapters. You can also run the suite on just
+one adapter by using the tasks test_mysql, test_sqlite, test_postgresql or any
+of the other test_ tasks. For more information, checkout the full array of rake
+tasks with "rake -T"
+
+Rake can be found at http://rake.rubyforge.org
+
+== Running by hand
+
+Unit tests are located in test/cases directory. If you only want to run a single test suite,
+you can do so with:
+
+ rake test_mysql TEST=test/cases/base_test.rb
+
+That'll run the base suite using the MySQL-Ruby adapter. Some tests rely on the schema
+being initialized - you can initialize the schema with:
+
+ rake test_mysql TEST=test/cases/aaa_create_tables_test.rb
+
+
+
diff --git a/vendor/rails/activerecord/Rakefile b/vendor/rails/activerecord/Rakefile
new file mode 100644
index 0000000..fc068b1
--- /dev/null
+++ b/vendor/rails/activerecord/Rakefile
@@ -0,0 +1,247 @@
+require 'rubygems'
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rake/contrib/sshpublisher'
+
+require File.join(File.dirname(__FILE__), 'lib', 'active_record', 'version')
+require File.expand_path(File.dirname(__FILE__)) + "/test/config"
+
+PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
+PKG_NAME = 'activerecord'
+PKG_VERSION = ActiveRecord::VERSION::STRING + PKG_BUILD
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+
+RELEASE_NAME = "REL #{PKG_VERSION}"
+
+RUBY_FORGE_PROJECT = "activerecord"
+RUBY_FORGE_USER = "webster132"
+
+MYSQL_DB_USER = 'rails'
+
+PKG_FILES = FileList[
+ "lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
+].exclude(/\bCVS\b|~$/)
+
+
+desc 'Run mysql, sqlite, and postgresql tests by default'
+task :default => :test
+
+desc 'Run mysql, sqlite, and postgresql tests'
+task :test => %w(test_mysql test_sqlite test_sqlite3 test_postgresql)
+
+for adapter in %w( mysql postgresql sqlite sqlite3 firebird db2 oracle sybase openbase frontbase )
+ Rake::TestTask.new("test_#{adapter}") { |t|
+ t.libs << "test" << "test/connections/native_#{adapter}"
+ adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z]+/]
+ t.test_files=Dir.glob( "test/cases/**/*_test{,_#{adapter_short}}.rb" ).sort
+ t.verbose = true
+ }
+
+ namespace adapter do
+ task :test => "test_#{adapter}"
+ end
+end
+
+namespace :mysql do
+ desc 'Build the MySQL test databases'
+ task :build_databases do
+ %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest )
+ %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 )
+ end
+
+ desc 'Drop the MySQL test databases'
+ task :drop_databases do
+ %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest )
+ %x( mysqladmin --user=#{MYSQL_DB_USER} -f drop activerecord_unittest2 )
+ end
+
+ desc 'Rebuild the MySQL test databases'
+ task :rebuild_databases => [:drop_databases, :build_databases]
+end
+
+task :build_mysql_databases => 'mysql:build_databases'
+task :drop_mysql_databases => 'mysql:drop_databases'
+task :rebuild_mysql_databases => 'mysql:rebuild_databases'
+
+
+namespace :postgresql do
+ desc 'Build the PostgreSQL test databases'
+ task :build_databases do
+ %x( createdb activerecord_unittest )
+ %x( createdb activerecord_unittest2 )
+ end
+
+ desc 'Drop the PostgreSQL test databases'
+ task :drop_databases do
+ %x( dropdb activerecord_unittest )
+ %x( dropdb activerecord_unittest2 )
+ end
+
+ desc 'Rebuild the PostgreSQL test databases'
+ task :rebuild_databases => [:drop_databases, :build_databases]
+end
+
+task :build_postgresql_databases => 'postgresql:build_databases'
+task :drop_postgresql_databases => 'postgresql:drop_databases'
+task :rebuild_postgresql_databases => 'postgresql:rebuild_databases'
+
+
+namespace :frontbase do
+ desc 'Build the FrontBase test databases'
+ task :build_databases => :rebuild_frontbase_databases
+
+ desc 'Rebuild the FrontBase test databases'
+ task :rebuild_databases do
+ build_frontbase_database = Proc.new do |db_name, sql_definition_file|
+ %(
+ STOP DATABASE #{db_name};
+ DELETE DATABASE #{db_name};
+ CREATE DATABASE #{db_name};
+
+ CONNECT TO #{db_name} AS SESSION_NAME USER _SYSTEM;
+ SET COMMIT FALSE;
+
+ CREATE USER RAILS;
+ CREATE SCHEMA RAILS AUTHORIZATION RAILS;
+ COMMIT;
+
+ SET SESSION AUTHORIZATION RAILS;
+ SCRIPT '#{sql_definition_file}';
+
+ COMMIT;
+
+ DISCONNECT ALL;
+ )
+ end
+ create_activerecord_unittest = build_frontbase_database['activerecord_unittest', File.join(SCHEMA_ROOT, 'frontbase.sql')]
+ create_activerecord_unittest2 = build_frontbase_database['activerecord_unittest2', File.join(SCHEMA_ROOT, 'frontbase2.sql')]
+ execute_frontbase_sql = Proc.new do |sql|
+ system(<<-SHELL)
+ /Library/FrontBase/bin/sql92 <<-SQL
+ #{sql}
+ SQL
+ SHELL
+ end
+ execute_frontbase_sql[create_activerecord_unittest]
+ execute_frontbase_sql[create_activerecord_unittest2]
+ end
+end
+
+task :build_frontbase_databases => 'frontbase:build_databases'
+task :rebuild_frontbase_databases => 'frontbase:rebuild_databases'
+
+
+# Generate the RDoc documentation
+
+Rake::RDocTask.new { |rdoc|
+ rdoc.rdoc_dir = 'doc'
+ rdoc.title = "Active Record -- Object-relation mapping put on rails"
+ rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
+ rdoc.options << '--charset' << 'utf-8'
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
+ rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+ rdoc.rdoc_files.exclude('lib/active_record/vendor/*')
+ rdoc.rdoc_files.include('dev-utils/*.rb')
+}
+
+# Enhance rdoc task to copy referenced images also
+task :rdoc do
+ FileUtils.mkdir_p "doc/files/examples/"
+ FileUtils.copy "examples/associations.png", "doc/files/examples/associations.png"
+end
+
+
+# Create compressed packages
+
+dist_dirs = [ "lib", "test", "examples" ]
+
+spec = Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = PKG_NAME
+ s.version = PKG_VERSION
+ s.summary = "Implements the ActiveRecord pattern for ORM."
+ s.description = %q{Implements the ActiveRecord pattern (Fowler, PoEAA) for ORM. It ties database tables and classes together for business objects, like Customer or Subscription, that can find, save, and destroy themselves without resorting to manual SQL.}
+
+ s.files = [ "Rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG" ]
+ dist_dirs.each do |dir|
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
+ end
+
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
+
+ s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite"
+ s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite"
+ s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite3"
+ s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite3"
+ s.require_path = 'lib'
+ s.autorequire = 'active_record'
+
+ s.has_rdoc = true
+ s.extra_rdoc_files = %w( README )
+ s.rdoc_options.concat ['--main', 'README']
+
+ s.author = "David Heinemeier Hansson"
+ s.email = "david@loudthinking.com"
+ s.homepage = "http://www.rubyonrails.org"
+ s.rubyforge_project = "activerecord"
+end
+
+Rake::GemPackageTask.new(spec) do |p|
+ p.gem_spec = spec
+ p.need_tar = true
+ p.need_zip = true
+end
+
+task :lines do
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
+
+ for file_name in FileList["lib/active_record/**/*.rb"]
+ next if file_name =~ /vendor/
+ f = File.open(file_name)
+
+ while line = f.gets
+ lines += 1
+ next if line =~ /^\s*$/
+ next if line =~ /^\s*#/
+ codelines += 1
+ end
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
+
+ total_lines += lines
+ total_codelines += codelines
+
+ lines, codelines = 0, 0
+ end
+
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
+end
+
+
+# Publishing ------------------------------------------------------
+
+desc "Publish the beta gem"
+task :pgem => [:package] do
+ Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
+ `ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
+end
+
+desc "Publish the API documentation"
+task :pdoc => [:rdoc] do
+ Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ar", "doc").upload
+end
+
+desc "Publish the release files to RubyForge."
+task :release => [ :package ] do
+ require 'rubyforge'
+ require 'rake/contrib/rubyforgepublisher'
+
+ packages = %w( gem tgz zip ).collect{ |ext| "pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}" }
+
+ rubyforge = RubyForge.new
+ rubyforge.login
+ rubyforge.add_release(PKG_NAME, PKG_NAME, "REL #{PKG_VERSION}", *packages)
+end
diff --git a/vendor/rails/activerecord/examples/associations.png b/vendor/rails/activerecord/examples/associations.png
new file mode 100644
index 0000000..661c7a8
Binary files /dev/null and b/vendor/rails/activerecord/examples/associations.png differ
diff --git a/vendor/rails/activerecord/install.rb b/vendor/rails/activerecord/install.rb
new file mode 100644
index 0000000..c87398b
--- /dev/null
+++ b/vendor/rails/activerecord/install.rb
@@ -0,0 +1,30 @@
+require 'rbconfig'
+require 'find'
+require 'ftools'
+
+include Config
+
+# this was adapted from rdoc's install.rb by ways of Log4r
+
+$sitedir = CONFIG["sitelibdir"]
+unless $sitedir
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
+ if !$sitedir
+ $sitedir = File.join($libdir, "site_ruby")
+ elsif $sitedir !~ Regexp.quote(version)
+ $sitedir = File.join($sitedir, version)
+ end
+end
+
+# the actual gruntwork
+Dir.chdir("lib")
+
+Find.find("active_record", "active_record.rb") { |f|
+ if f[-3..-1] == ".rb"
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
+ else
+ File::makedirs(File.join($sitedir, *f.split(/\//)))
+ end
+}
diff --git a/vendor/rails/activerecord/lib/active_record.rb b/vendor/rails/activerecord/lib/active_record.rb
new file mode 100644
index 0000000..d4f7170
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record.rb
@@ -0,0 +1,82 @@
+#--
+# Copyright (c) 2004-2008 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+$:.unshift(File.dirname(__FILE__)) unless
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+unless defined? ActiveSupport
+ active_support_path = File.dirname(__FILE__) + "/../../activesupport/lib"
+ if File.exist?(active_support_path)
+ $:.unshift active_support_path
+ require 'active_support'
+ else
+ require 'rubygems'
+ gem 'activesupport'
+ require 'active_support'
+ end
+end
+
+require 'active_record/base'
+require 'active_record/named_scope'
+require 'active_record/observer'
+require 'active_record/query_cache'
+require 'active_record/validations'
+require 'active_record/callbacks'
+require 'active_record/reflection'
+require 'active_record/associations'
+require 'active_record/association_preload'
+require 'active_record/aggregations'
+require 'active_record/transactions'
+require 'active_record/timestamp'
+require 'active_record/locking/optimistic'
+require 'active_record/locking/pessimistic'
+require 'active_record/migration'
+require 'active_record/schema'
+require 'active_record/calculations'
+require 'active_record/serialization'
+require 'active_record/attribute_methods'
+require 'active_record/dirty'
+
+ActiveRecord::Base.class_eval do
+ extend ActiveRecord::QueryCache
+ include ActiveRecord::Validations
+ include ActiveRecord::Locking::Optimistic
+ include ActiveRecord::Locking::Pessimistic
+ include ActiveRecord::AttributeMethods
+ include ActiveRecord::Dirty
+ include ActiveRecord::Callbacks
+ include ActiveRecord::Observing
+ include ActiveRecord::Timestamp
+ include ActiveRecord::Associations
+ include ActiveRecord::NamedScope
+ include ActiveRecord::AssociationPreload
+ include ActiveRecord::Aggregations
+ include ActiveRecord::Transactions
+ include ActiveRecord::Reflection
+ include ActiveRecord::Calculations
+ include ActiveRecord::Serialization
+end
+
+require 'active_record/connection_adapters/abstract_adapter'
+
+require 'active_record/schema_dumper'
diff --git a/vendor/rails/activerecord/lib/active_record/aggregations.rb b/vendor/rails/activerecord/lib/active_record/aggregations.rb
new file mode 100644
index 0000000..a5d3a50
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/aggregations.rb
@@ -0,0 +1,189 @@
+module ActiveRecord
+ module Aggregations # :nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ def clear_aggregation_cache #:nodoc:
+ self.class.reflect_on_all_aggregations.to_a.each do |assoc|
+ instance_variable_set "@#{assoc.name}", nil
+ end unless self.new_record?
+ end
+
+ # Active Record implements aggregation through a macro-like class method called +composed_of+ for representing attributes
+ # as value objects. It expresses relationships like "Account [is] composed of Money [among other things]" or "Person [is]
+ # composed of [an] address". Each call to the macro adds a description of how the value objects are created from the
+ # attributes of the entity object (when the entity is initialized either as a new object or from finding an existing object)
+ # and how it can be turned back into attributes (when the entity is saved to the database). Example:
+ #
+ # class Customer < ActiveRecord::Base
+ # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
+ # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ # end
+ #
+ # The customer class now has the following methods to manipulate the value objects:
+ # * Customer#balance, Customer#balance=(money)
+ # * Customer#address, Customer#address=(address)
+ #
+ # These methods will operate with value objects like the ones described below:
+ #
+ # class Money
+ # include Comparable
+ # attr_reader :amount, :currency
+ # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
+ #
+ # def initialize(amount, currency = "USD")
+ # @amount, @currency = amount, currency
+ # end
+ #
+ # def exchange_to(other_currency)
+ # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
+ # Money.new(exchanged_amount, other_currency)
+ # end
+ #
+ # def ==(other_money)
+ # amount == other_money.amount && currency == other_money.currency
+ # end
+ #
+ # def <=>(other_money)
+ # if currency == other_money.currency
+ # amount <=> amount
+ # else
+ # amount <=> other_money.exchange_to(currency).amount
+ # end
+ # end
+ # end
+ #
+ # class Address
+ # attr_reader :street, :city
+ # def initialize(street, city)
+ # @street, @city = street, city
+ # end
+ #
+ # def close_to?(other_address)
+ # city == other_address.city
+ # end
+ #
+ # def ==(other_address)
+ # city == other_address.city && street == other_address.street
+ # end
+ # end
+ #
+ # Now it's possible to access attributes from the database through the value objects instead. If you choose to name the
+ # composition the same as the attribute's name, it will be the only way to access that attribute. That's the case with our
+ # +balance+ attribute. You interact with the value objects just like you would any other attribute, though:
+ #
+ # customer.balance = Money.new(20) # sets the Money value object and the attribute
+ # customer.balance # => Money value object
+ # customer.balance.exchanged_to("DKK") # => Money.new(120, "DKK")
+ # customer.balance > Money.new(10) # => true
+ # customer.balance == Money.new(20) # => true
+ # customer.balance < Money.new(5) # => false
+ #
+ # Value objects can also be composed of multiple attributes, such as the case of Address. The order of the mappings will
+ # determine the order of the parameters. Example:
+ #
+ # customer.address_street = "Hyancintvej"
+ # customer.address_city = "Copenhagen"
+ # customer.address # => Address.new("Hyancintvej", "Copenhagen")
+ # customer.address = Address.new("May Street", "Chicago")
+ # customer.address_street # => "May Street"
+ # customer.address_city # => "Chicago"
+ #
+ # == Writing value objects
+ #
+ # Value objects are immutable and interchangeable objects that represent a given value, such as a Money object representing
+ # $5. Two Money objects both representing $5 should be equal (through methods such as == and <=> from Comparable if ranking
+ # makes sense). This is unlike entity objects where equality is determined by identity. An entity class such as Customer can
+ # easily have two different objects that both have an address on Hyancintvej. Entity identity is determined by object or
+ # relational unique identifiers (such as primary keys). Normal ActiveRecord::Base classes are entity objects.
+ #
+ # It's also important to treat the value objects as immutable. Don't allow the Money object to have its amount changed after
+ # creation. Create a new Money object with the new value instead. This is exemplified by the Money#exchanged_to method that
+ # returns a new value object instead of changing its own values. Active Record won't persist value objects that have been
+ # changed through means other than the writer method.
+ #
+ # The immutable requirement is enforced by Active Record by freezing any object assigned as a value object. Attempting to
+ # change it afterwards will result in a ActiveSupport::FrozenObjectError.
+ #
+ # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects
+ # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
+ #
+ # == Finding records by a value object
+ #
+ # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance
+ # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and
+ # +balance_currency+ equal to "USD":
+ #
+ # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")})
+ #
+ module ClassMethods
+ # Adds reader and writer methods for manipulating a value object:
+ # composed_of :address adds address and address=(new_address) methods.
+ #
+ # Options are:
+ # * :class_name - specify the class name of the association. Use it only if that name can't be inferred
+ # from the part id. So composed_of :address will by default be linked to the Address class, but
+ # if the real class name is CompanyAddress, you'll have to specify it with this option.
+ # * :mapping - specifies a number of mapping arrays (attribute, parameter) that bind an attribute name
+ # to a constructor parameter on the value class.
+ # * :allow_nil - specifies that the aggregate object will not be instantiated when all mapped
+ # attributes are +nil+. Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes.
+ # This defaults to +false+.
+ #
+ # An optional block can be passed to convert the argument that is passed to the writer method into an instance of
+ # :class_name. The block will only be called if the argument is not already an instance of :class_name.
+ #
+ # Option examples:
+ # composed_of :temperature, :mapping => %w(reading celsius)
+ # composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) {|balance| balance.to_money }
+ # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
+ # composed_of :gps_location
+ # composed_of :gps_location, :allow_nil => true
+ #
+ def composed_of(part_id, options = {}, &block)
+ options.assert_valid_keys(:class_name, :mapping, :allow_nil)
+
+ name = part_id.id2name
+ class_name = options[:class_name] || name.camelize
+ mapping = options[:mapping] || [ name, name ]
+ mapping = [ mapping ] unless mapping.first.is_a?(Array)
+ allow_nil = options[:allow_nil] || false
+
+ reader_method(name, class_name, mapping, allow_nil)
+ writer_method(name, class_name, mapping, allow_nil, block)
+
+ create_reflection(:composed_of, part_id, options, self)
+ end
+
+ private
+ def reader_method(name, class_name, mapping, allow_nil)
+ module_eval do
+ define_method(name) do |*args|
+ force_reload = args.first || false
+ if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
+ instance_variable_set("@#{name}", class_name.constantize.new(*mapping.collect {|pair| read_attribute(pair.first)}))
+ end
+ instance_variable_get("@#{name}")
+ end
+ end
+
+ end
+
+ def writer_method(name, class_name, mapping, allow_nil, conversion)
+ 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
+ part = conversion.call(part) unless part.is_a?(class_name.constantize) || conversion.nil?
+ mapping.each { |pair| self[pair.first] = part.send(pair.last) }
+ instance_variable_set("@#{name}", part.freeze)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/association_preload.rb b/vendor/rails/activerecord/lib/active_record/association_preload.rb
new file mode 100644
index 0000000..a3d1f12
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/association_preload.rb
@@ -0,0 +1,277 @@
+module ActiveRecord
+ module AssociationPreload #:nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+
+ # Loads the named associations for the activerecord record (or records) given
+ # preload_options is passed only one level deep: don't pass to the child associations when associations is a Hash
+ protected
+ def preload_associations(records, associations, preload_options={})
+ records = [records].flatten.compact.uniq
+ return if records.empty?
+ case associations
+ when Array then associations.each {|association| preload_associations(records, association, preload_options)}
+ when Symbol, String then preload_one_association(records, associations.to_sym, preload_options)
+ when Hash then
+ associations.each do |parent, child|
+ raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
+ preload_associations(records, parent, preload_options)
+ reflection = reflections[parent]
+ parents = records.map {|record| record.send(reflection.name)}.flatten
+ unless parents.empty? || parents.first.nil?
+ parents.first.class.preload_associations(parents, child)
+ end
+ end
+ end
+ end
+
+ private
+
+ def preload_one_association(records, association, preload_options={})
+ class_to_reflection = {}
+ # Not all records have the same class, so group then preload
+ # group on the reflection itself so that if various subclass share the same association then we do not split them
+ # unncessarily
+ records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
+ raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
+ send("preload_#{reflection.macro}_association", records, reflection, preload_options)
+ end
+ end
+
+ def add_preloaded_records_to_collection(parent_records, reflection_name, associated_record)
+ parent_records.each do |parent_record|
+ association_proxy = parent_record.send(reflection_name)
+ association_proxy.loaded
+ association_proxy.target.push(*[associated_record].flatten)
+ end
+ end
+
+ def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
+ parent_records.each do |parent_record|
+ association_proxy = parent_record.send(reflection_name)
+ association_proxy.loaded
+ association_proxy.target = associated_record
+ end
+ end
+
+ def set_association_collection_records(id_to_record_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)
+ end
+ end
+
+ def set_association_single_records(id_to_record_map, reflection_name, associated_records, key)
+ seen_keys = {}
+ associated_records.each do |associated_record|
+ #this is a has_one or belongs_to: there should only be one record.
+ #Unfortunately we can't (in portable way) ask the database for 'all records where foo_id in (x,y,z), but please
+ # only one row per distinct foo_id' so this where we enforce that
+ next if seen_keys[associated_record[key].to_s]
+ seen_keys[associated_record[key].to_s] = true
+ mapped_records = id_to_record_map[associated_record[key].to_s]
+ mapped_records.each do |mapped_record|
+ mapped_record.send("set_#{reflection_name}_target", associated_record)
+ end
+ end
+ end
+
+ def construct_id_map(records)
+ id_to_record_map = {}
+ ids = []
+ records.each do |record|
+ ids << record.id
+ mapped_records = (id_to_record_map[record.id.to_s] ||= [])
+ mapped_records << record
+ end
+ ids.uniq!
+ return id_to_record_map, ids
+ end
+
+ def preload_has_and_belongs_to_many_association(records, reflection, preload_options={})
+ table_name = reflection.klass.quoted_table_name
+ id_to_record_map, ids = construct_id_map(records)
+ records.each {|record| record.send(reflection.name).loaded}
+ options = reflection.options
+
+ conditions = "t0.#{reflection.primary_key_name} IN (?)"
+ conditions << append_conditions(options, preload_options)
+
+ associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
+ :include => options[:include],
+ :joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
+ :select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as _parent_record_id",
+ :order => options[:order])
+
+ set_association_collection_records(id_to_record_map, reflection.name, associated_records, '_parent_record_id')
+ end
+
+ def preload_has_one_association(records, reflection, preload_options={})
+ id_to_record_map, ids = construct_id_map(records)
+ options = reflection.options
+ if options[:through]
+ records.each {|record| record.send(reflection.name) && record.send(reflection.name).loaded}
+ through_records = preload_through_records(records, reflection, options[:through])
+ through_reflection = reflections[options[:through]]
+ through_primary_key = through_reflection.primary_key_name
+ unless through_records.empty?
+ source = reflection.source_reflection.name
+ through_records.first.class.preload_associations(through_records, source)
+ through_records.each do |through_record|
+ add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
+ end
+ else
+ records.each {|record| record.send("set_#{reflection.name}_target", nil)}
+
+ set_association_single_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options), reflection.primary_key_name)
+ end
+ end
+
+ def preload_has_many_association(records, reflection, preload_options={})
+ id_to_record_map, ids = construct_id_map(records)
+ records.each {|record| record.send(reflection.name).loaded}
+ options = reflection.options
+
+ if options[:through]
+ through_records = preload_through_records(records, reflection, options[:through])
+ through_reflection = reflections[options[:through]]
+ through_primary_key = through_reflection.primary_key_name
+ unless through_records.empty?
+ source = reflection.source_reflection.name
+ #add conditions from reflection!
+ through_records.first.class.preload_associations(through_records, source, reflection.options)
+ through_records.each do |through_record|
+ add_preloaded_records_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
+ reflection.name, through_record.send(source))
+ end
+ end
+ else
+ set_association_collection_records(id_to_record_map, reflection.name, find_associated_records(ids, reflection, preload_options),
+ reflection.primary_key_name)
+ end
+ end
+
+ def preload_through_records(records, reflection, through_association)
+ through_reflection = reflections[through_association]
+ through_primary_key = through_reflection.primary_key_name
+
+ if reflection.options[:source_type]
+ interface = reflection.source_reflection.options[:foreign_type]
+ preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]}
+
+ records.compact!
+ records.first.class.preload_associations(records, through_association, preload_options)
+
+ # Dont cache the association - we would only be caching a subset
+ through_records = []
+ records.each do |record|
+ proxy = record.send(through_association)
+
+ if proxy.respond_to?(:target)
+ through_records << proxy.target
+ proxy.reset
+ else # this is a has_one :through reflection
+ through_records << proxy if proxy
+ end
+ end
+ through_records.flatten!
+ else
+ records.first.class.preload_associations(records, through_association)
+ through_records = records.map {|record| record.send(through_association)}.flatten
+ end
+ through_records.compact!
+ through_records
+ end
+
+ # FIXME: quoting
+ def preload_belongs_to_association(records, reflection, preload_options={})
+ options = reflection.options
+ primary_key_name = reflection.primary_key_name
+
+ if options[:polymorphic]
+ polymorph_type = options[:foreign_type]
+ klasses_and_ids = {}
+
+ # Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
+ records.each do |record|
+ if klass = record.send(polymorph_type)
+ klass_id = record.send(primary_key_name)
+ if klass_id
+ id_map = klasses_and_ids[klass] ||= {}
+ id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
+ id_list_for_klass_id << record
+ end
+ end
+ end
+ klasses_and_ids = klasses_and_ids.to_a
+ else
+ id_map = {}
+ records.each do |record|
+ key = record.send(primary_key_name)
+ if key
+ mapped_records = (id_map[key.to_s] ||= [])
+ mapped_records << record
+ end
+ end
+ klasses_and_ids = [[reflection.klass.name, id_map]]
+ end
+
+ klasses_and_ids.each do |klass_and_id|
+ klass_name, id_map = *klass_and_id
+ klass = klass_name.constantize
+
+ table_name = klass.quoted_table_name
+ primary_key = klass.primary_key
+ conditions = "#{table_name}.#{primary_key} IN (?)"
+ conditions << append_conditions(options, preload_options)
+ associated_records = klass.find(:all, :conditions => [conditions, id_map.keys.uniq],
+ :include => options[:include],
+ :select => options[:select],
+ :joins => options[:joins],
+ :order => options[:order])
+ set_association_single_records(id_map, reflection.name, associated_records, primary_key)
+ end
+ end
+
+ def find_associated_records(ids, reflection, preload_options)
+ options = reflection.options
+ table_name = reflection.klass.quoted_table_name
+
+ if interface = reflection.options[:as]
+ conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} IN (?) and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} = '#{self.base_class.name.demodulize}'"
+ else
+ foreign_key = reflection.primary_key_name
+ conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} IN (?)"
+ end
+
+ conditions << append_conditions(options, preload_options)
+
+ reflection.klass.find(:all,
+ :select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
+ :include => preload_options[:include] || options[:include],
+ :conditions => [conditions, ids],
+ :joins => options[:joins],
+ :group => preload_options[:group] || options[:group],
+ :order => preload_options[:order] || options[:order])
+ end
+
+
+ def interpolate_sql_for_preload(sql)
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
+ end
+
+ def append_conditions(options, preload_options)
+ sql = ""
+ sql << " AND (#{interpolate_sql_for_preload(sanitize_sql(options[:conditions]))})" if options[:conditions]
+ sql << " AND (#{sanitize_sql preload_options[:conditions]})" if preload_options[:conditions]
+ sql
+ end
+
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations.rb b/vendor/rails/activerecord/lib/active_record/associations.rb
new file mode 100644
index 0000000..a3d1bbb
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations.rb
@@ -0,0 +1,1994 @@
+require 'active_record/associations/association_proxy'
+require 'active_record/associations/association_collection'
+require 'active_record/associations/belongs_to_association'
+require 'active_record/associations/belongs_to_polymorphic_association'
+require 'active_record/associations/has_one_association'
+require 'active_record/associations/has_many_association'
+require 'active_record/associations/has_many_through_association'
+require 'active_record/associations/has_and_belongs_to_many_association'
+require 'active_record/associations/has_one_through_association'
+
+module ActiveRecord
+ class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name, reflection)
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
+ end
+ end
+
+ class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name, reflection, source_reflection)
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
+ end
+ end
+
+ class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc:
+ def initialize(owner_class_name, reflection, source_reflection)
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
+ end
+ end
+
+ class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ through_reflection = reflection.through_reflection
+ source_reflection_names = reflection.source_reflection_names
+ source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
+ end
+ end
+
+ class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ through_reflection = reflection.through_reflection
+ source_reflection = reflection.source_reflection
+ super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
+ end
+ end
+
+ class HasManyThroughCantAssociateThroughHasManyReflection < ActiveRecordError #:nodoc:
+ def initialize(owner, reflection)
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
+ end
+ end
+ class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
+ def initialize(owner, reflection)
+ super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
+ end
+ end
+
+ class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc:
+ def initialize(owner, reflection)
+ super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
+ end
+ end
+
+ class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
+ end
+ end
+
+ class ReadOnlyAssociation < ActiveRecordError #:nodoc:
+ def initialize(reflection)
+ super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
+ end
+ end
+
+ module Associations # :nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ # 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 unless self.new_record?
+ end
+
+ # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
+ # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own attr*
+ # methods. Example:
+ #
+ # class Project < ActiveRecord::Base
+ # belongs_to :portfolio
+ # has_one :project_manager
+ # has_many :milestones
+ # has_and_belongs_to_many :categories
+ # end
+ #
+ # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
+ # * Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?
+ # * Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,
+ # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),
+ # Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),
+ # Project#milestones.build, Project#milestones.create
+ # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),
+ # Project#categories.delete(category1)
+ #
+ # === A word of warning
+ #
+ # Don't create associations that have the same name as instance methods of ActiveRecord::Base. Since the association
+ # adds a method with that name to its model, it will override the inherited method and break things.
+ # For instance, +attributes+ and +connection+ would be bad choices for association names.
+ #
+ # == Auto-generated methods
+ #
+ # === Singular associations (one-to-one)
+ # | | belongs_to |
+ # generated methods | belongs_to | :polymorphic | has_one
+ # ----------------------------------+------------+--------------+---------
+ # #other | X | X | X
+ # #other=(other) | X | X | X
+ # #build_other(attributes={}) | X | | X
+ # #create_other(attributes={}) | X | | X
+ # #other.create!(attributes={}) | | | X
+ # #other.nil? | X | X |
+ #
+ # ===Collection associations (one-to-many / many-to-many)
+ # | | | has_many
+ # generated methods | habtm | has_many | :through
+ # ----------------------------------+-------+----------+----------
+ # #others | X | X | X
+ # #others=(other,other,...) | X | X | X
+ # #other_ids | X | X | X
+ # #other_ids=(id,id,...) | X | X | X
+ # #others<< | X | X | X
+ # #others.push | X | X | X
+ # #others.concat | X | X | X
+ # #others.build(attributes={}) | X | X | X
+ # #others.create(attributes={}) | X | X | X
+ # #others.create!(attributes={}) | X | X | X
+ # #others.size | X | X | X
+ # #others.length | X | X | X
+ # #others.count | X | X | X
+ # #others.sum(args*,&block) | X | X | X
+ # #others.empty? | X | X | X
+ # #others.clear | X | X | X
+ # #others.delete(other,other,...) | X | X | X
+ # #others.delete_all | X | X |
+ # #others.destroy_all | X | X | X
+ # #others.find(*args) | X | X | X
+ # #others.find_first | X | |
+ # #others.uniq | X | X | X
+ # #others.reset | X | X | X
+ #
+ # == Cardinality and associations
+ #
+ # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many
+ # relationships between models. Each model uses an association to describe its role in
+ # the relation. The +belongs_to+ association is always used in the model that has
+ # the foreign key.
+ #
+ # === One-to-one
+ #
+ # Use +has_one+ in the base, and +belongs_to+ in the associated model.
+ #
+ # class Employee < ActiveRecord::Base
+ # has_one :office
+ # end
+ # class Office < ActiveRecord::Base
+ # belongs_to :employee # foreign key - employee_id
+ # end
+ #
+ # === One-to-many
+ #
+ # Use +has_many+ in the base, and +belongs_to+ in the associated model.
+ #
+ # class Manager < ActiveRecord::Base
+ # has_many :employees
+ # end
+ # class Employee < ActiveRecord::Base
+ # belongs_to :manager # foreign key - manager_id
+ # end
+ #
+ # === Many-to-many
+ #
+ # There are two ways to build a many-to-many relationship.
+ #
+ # The first way uses a +has_many+ association with the :through option and a join model, so
+ # there are two stages of associations.
+ #
+ # class Assignment < ActiveRecord::Base
+ # belongs_to :programmer # foreign key - programmer_id
+ # belongs_to :project # foreign key - project_id
+ # end
+ # class Programmer < ActiveRecord::Base
+ # has_many :assignments
+ # has_many :projects, :through => :assignments
+ # end
+ # class Project < ActiveRecord::Base
+ # has_many :assignments
+ # has_many :programmers, :through => :assignments
+ # end
+ #
+ # For the second way, use +has_and_belongs_to_many+ in both models. This requires a join table
+ # that has no corresponding model or primary key.
+ #
+ # class Programmer < ActiveRecord::Base
+ # has_and_belongs_to_many :projects # foreign keys in the join table
+ # end
+ # class Project < ActiveRecord::Base
+ # has_and_belongs_to_many :programmers # foreign keys in the join table
+ # end
+ #
+ # Choosing which way to build a many-to-many relationship is not always simple.
+ # If you need to work with the relationship model as its own entity,
+ # use has_many :through. Use +has_and_belongs_to_many+ when working with legacy schemas or when
+ # you never work directly with the relationship itself.
+ #
+ # == Is it a +belongs_to+ or +has_one+ association?
+ #
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the table for the class
+ # declaring the +belongs_to+ relationship. Example:
+ #
+ # class User < ActiveRecord::Base
+ # # I reference an account.
+ # belongs_to :account
+ # end
+ #
+ # class Account < ActiveRecord::Base
+ # # One user references me.
+ # has_one :user
+ # end
+ #
+ # The tables for these classes could look something like:
+ #
+ # CREATE TABLE users (
+ # id int(11) NOT NULL auto_increment,
+ # account_id int(11) default NULL,
+ # name varchar default NULL,
+ # PRIMARY KEY (id)
+ # )
+ #
+ # CREATE TABLE accounts (
+ # id int(11) NOT NULL auto_increment,
+ # name varchar default NULL,
+ # PRIMARY KEY (id)
+ # )
+ #
+ # == Unsaved objects and associations
+ #
+ # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
+ # aware of, mostly involving the saving of associated objects.
+ #
+ # === 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 (new_record? == true).
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
+ # is cancelled.
+ # * If you wish to assign an object to a +has_one+ association without saving it, use the association.build method (documented below).
+ # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
+ # does not save the parent either.
+ #
+ # === Collections
+ #
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
+ # (the owner of the collection) is not yet stored in the database.
+ # * If saving any of the objects being added to a collection (via push or similar) fails, then push returns +false+.
+ # * You can add an object to a collection without automatically saving it by using the collection.build method (documented below).
+ # * All unsaved (new_record? == true) members of the collection are automatically saved when the parent is saved.
+ #
+ # === Association callbacks
+ #
+ # Similar to the normal callbacks that hook into the lifecycle of an Active Record object, you can also define callbacks that get
+ # triggered when you add an object to or remove an object from an association collection. Example:
+ #
+ # class Project
+ # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
+ #
+ # def evaluate_velocity(developer)
+ # ...
+ # end
+ # end
+ #
+ # It's possible to stack callbacks by passing them as an array. Example:
+ #
+ # class Project
+ # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
+ # end
+ #
+ # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
+ #
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
+ # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
+ #
+ # === Association extensions
+ #
+ # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
+ # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
+ # Example:
+ #
+ # class Account < ActiveRecord::Base
+ # has_many :people do
+ # def find_or_create_by_name(name)
+ # first_name, last_name = name.split(" ", 2)
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
+ # end
+ # end
+ # end
+ #
+ # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
+ # person.first_name # => "David"
+ # person.last_name # => "Heinemeier Hansson"
+ #
+ # If you need to share the same extensions between many associations, you can use a named extension module. Example:
+ #
+ # module FindOrCreateByNameExtension
+ # def find_or_create_by_name(name)
+ # first_name, last_name = name.split(" ", 2)
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
+ # end
+ # end
+ #
+ # class Account < ActiveRecord::Base
+ # has_many :people, :extend => FindOrCreateByNameExtension
+ # end
+ #
+ # class Company < ActiveRecord::Base
+ # has_many :people, :extend => FindOrCreateByNameExtension
+ # end
+ #
+ # If you need to use multiple named extension modules, you can specify an array of modules with the :extend option.
+ # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
+ # those earlier in the array. Example:
+ #
+ # class Account < ActiveRecord::Base
+ # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
+ # end
+ #
+ # Some extensions can only be made to work with knowledge of the association proxy's internals.
+ # Extensions can access relevant state using accessors on the association proxy:
+ #
+ # * +proxy_owner+ - Returns the object the association is part of.
+ # * +proxy_reflection+ - Returns the reflection object that describes the association.
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
+ #
+ # === Association Join Models
+ #
+ # Has Many associations can be configured with the :through option to use an explicit join model to retrieve the data. This
+ # operates similarly to a +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
+ # callbacks, and extra attributes on the join model. Consider the following schema:
+ #
+ # class Author < ActiveRecord::Base
+ # has_many :authorships
+ # has_many :books, :through => :authorships
+ # end
+ #
+ # class Authorship < ActiveRecord::Base
+ # belongs_to :author
+ # belongs_to :book
+ # end
+ #
+ # @author = Author.find :first
+ # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to.
+ # @author.books # selects all books by using the Authorship join model
+ #
+ # You can also go through a +has_many+ association on the join model:
+ #
+ # class Firm < ActiveRecord::Base
+ # has_many :clients
+ # has_many :invoices, :through => :clients
+ # end
+ #
+ # class Client < ActiveRecord::Base
+ # belongs_to :firm
+ # has_many :invoices
+ # end
+ #
+ # class Invoice < ActiveRecord::Base
+ # belongs_to :client
+ # end
+ #
+ # @firm = Firm.find :first
+ # @firm.clients.collect { |c| c.invoices }.flatten # select all invoices for all clients of the firm
+ # @firm.invoices # selects all invoices by going through the Client join model.
+ #
+ # === Polymorphic Associations
+ #
+ # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
+ # specify an interface that a +has_many+ association must adhere to.
+ #
+ # class Asset < ActiveRecord::Base
+ # belongs_to :attachable, :polymorphic => true
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
+ # end
+ #
+ # @asset.attachable = @post
+ #
+ # This works by using a type column in addition to a foreign key to specify the associated record. In the Asset example, you'd need
+ # an +attachable_id+ integer column and an +attachable_type+ string column.
+ #
+ # Using polymorphic associations in combination with single table inheritance (STI) is a little tricky. In order
+ # for the associations to work as expected, ensure that you store the base model for the STI models in the
+ # type column of the polymorphic association. To continue with the asset example above, suppose there are guest posts
+ # and member posts that use the posts table for STI. In this case, there must be a +type+ column in the posts table.
+ #
+ # class Asset < ActiveRecord::Base
+ # belongs_to :attachable, :polymorphic => true
+ #
+ # def attachable_type=(sType)
+ # super(sType.to_s.classify.constantize.base_class.to_s)
+ # end
+ # end
+ #
+ # class Post < ActiveRecord::Base
+ # # because we store "Post" in attachable_type now :dependent => :destroy will work
+ # has_many :assets, :as => :attachable, :dependent => :destroy
+ # end
+ #
+ # class GuestPost < Post
+ # end
+ #
+ # class MemberPost < Post
+ # end
+ #
+ # == Caching
+ #
+ # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
+ # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
+ # worrying too much about performance at the first go. Example:
+ #
+ # project.milestones # fetches milestones from the database
+ # project.milestones.size # uses the milestone cache
+ # project.milestones.empty? # uses the milestone cache
+ # project.milestones(true).size # fetches milestones from the database
+ # project.milestones # uses the milestone cache
+ #
+ # == Eager loading of associations
+ #
+ # Eager loading is a way to find objects of a certain class and a number of named associations. This is
+ # one of the easiest ways of to prevent the dreaded 1+N problem in which fetching 100 posts that each need to display their author
+ # triggers 101 database queries. Through the use of eager loading, the 101 queries can be reduced to 2. Example:
+ #
+ # class Post < ActiveRecord::Base
+ # belongs_to :author
+ # has_many :comments
+ # end
+ #
+ # Consider the following loop using the class above:
+ #
+ # for post in Post.all
+ # puts "Post: " + post.title
+ # puts "Written by: " + post.author.name
+ # puts "Last comment on: " + post.comments.first.created_on
+ # end
+ #
+ # To iterate over these one hundred posts, we'll generate 201 database queries. Let's first just optimize it for retrieving the author:
+ #
+ # for post in Post.find(:all, :include => :author)
+ #
+ # This references the name of the +belongs_to+ association that also used the :author symbol. After loading the posts, find
+ # will collect the +author_id+ from each one and load all the referenced authors with one query. Doing so will cut down the number of queries from 201 to 102.
+ #
+ # We can improve upon the situation further by referencing both associations in the finder with:
+ #
+ # for post in Post.find(:all, :include => [ :author, :comments ])
+ #
+ # This will load all comments with a single query. This reduces the total number of queries to 3. More generally the number of queries
+ # will be 1 plus the number of associations named (except if some of the associations are polymorphic +belongs_to+ - see below).
+ #
+ # To include a deep hierarchy of associations, use a hash:
+ #
+ # for post in Post.find(:all, :include => [ :author, { :comments => { :author => :gravatar } } ])
+ #
+ # That'll grab not only all the comments but all their authors and gravatar pictures. You can mix and match
+ # symbols, arrays and hashes in any combination to describe the associations you want to load.
+ #
+ # All of this power shouldn't fool you into thinking that you can pull out huge amounts of data with no performance penalty just because you've reduced
+ # the number of queries. The database still needs to send all the data to Active Record and it still needs to be processed. So it's no
+ # catch-all for performance problems, but it's a great way to cut down on the number of queries in a situation as the one described above.
+ #
+ # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case
+ # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example
+ #
+ # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true])
+ #
+ # will result in a single SQL query with joins along the lines of: LEFT OUTER JOIN comments ON comments.post_id = posts.id and
+ # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions like this can have unintended consequences.
+ # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole
+ # and not just to the association. You must disambiguate column references for this fallback to happen, for example
+ # :order => "author.name DESC" will work but :order => "name DESC" will not.
+ #
+ # If you do want eagerload only some members of an association it is usually more natural to :include an association
+ # which has conditions defined on it:
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
+ # end
+ #
+ # Post.find(:all, :include => :approved_comments)
+ #
+ # will load posts and eager load the +approved_comments+ association, which contains only those comments that have been approved.
+ #
+ # When eager loaded, conditions are interpolated in the context of the model class, not the model instance. Conditions are lazily interpolated
+ # before the actual model exists.
+ #
+ # Eager loading is supported with polymorphic associations.
+ #
+ # class Address < ActiveRecord::Base
+ # belongs_to :addressable, :polymorphic => true
+ # end
+ #
+ # A call that tries to eager load the addressable model
+ #
+ # Address.find(:all, :include => :addressable)
+ #
+ # will execute one query to load the addresses and load the addressables with one query per addressable type.
+ # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of
+ # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback
+ # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent
+ # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query.
+ #
+ # == Table Aliasing
+ #
+ # Active Record uses table aliasing in the case that a table is referenced multiple times in a join. If a table is referenced only once,
+ # the standard table name is used. The second time, the table is aliased as #{reflection_name}_#{parent_table_name}. Indexes are appended
+ # for any more successive uses of the table name.
+ #
+ # Post.find :all, :joins => :comments
+ # # => SELECT ... FROM posts INNER JOIN comments ON ...
+ # Post.find :all, :joins => :special_comments # STI
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
+ # Post.find :all, :joins => [:comments, :special_comments] # special_comments is the reflection name, posts is the parent table name
+ # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
+ #
+ # Acts as tree example:
+ #
+ # TreeMixin.find :all, :joins => :children
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # TreeMixin.find :all, :joins => {:children => :parent}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # TreeMixin.find :all, :joins => {:children => {:parent => :children}}
+ # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
+ # INNER JOIN parents_mixins ...
+ # INNER JOIN mixins childrens_mixins_2
+ #
+ # Has and Belongs to Many join tables use the same idea, but add a _join suffix:
+ #
+ # Post.find :all, :joins => :categories
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # Post.find :all, :joins => {:categories => :posts}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # Post.find :all, :joins => {:categories => {:posts => :categories}}
+ # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
+ # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
+ # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
+ #
+ # If you wish to specify your own custom joins using a :joins option, those table names will take precedence over the eager associations:
+ #
+ # Post.find :all, :joins => :comments, :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
+ # Post.find :all, :joins => [:comments, :special_comments], :joins => "inner join comments ..."
+ # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
+ # INNER JOIN comments special_comments_posts ...
+ # INNER JOIN comments ...
+ #
+ # Table aliases are automatically truncated according to the maximum length of table identifiers according to the specific database.
+ #
+ # == Modules
+ #
+ # By default, associations will look for objects within the current module scope. Consider:
+ #
+ # module MyApplication
+ # module Business
+ # class Firm < ActiveRecord::Base
+ # has_many :clients
+ # end
+ #
+ # class Company < ActiveRecord::Base; end
+ # end
+ # end
+ #
+ # When Firm#clients is called, it will in turn call MyApplication::Business::Company.find(firm.id). If you want to associate
+ # with a class in another module scope, this can be done by specifying the complete class name. Example:
+ #
+ # module MyApplication
+ # module Business
+ # class Firm < ActiveRecord::Base; end
+ # end
+ #
+ # module Billing
+ # class Account < ActiveRecord::Base
+ # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
+ # end
+ # end
+ # end
+ #
+ # == Type safety with ActiveRecord::AssociationTypeMismatch
+ #
+ # If you attempt to assign an object to an association that doesn't match the inferred or specified :class_name, you'll
+ # get an ActiveRecord::AssociationTypeMismatch.
+ #
+ # == Options
+ #
+ # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
+ # possible.
+ module ClassMethods
+ # Adds the following methods for retrieval and query of collections of associated objects:
+ # +collection+ is replaced with the symbol passed as the first argument, so
+ # has_many :clients would add among others clients.empty?.
+ # * collection(force_reload = false) - Returns an array of all the associated objects.
+ # An empty array is returned if none are found.
+ # * collection<<(object, ...) - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
+ # * collection.delete(object, ...) - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
+ # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
+ # * collection=objects - Replaces the collections content by deleting and adding objects as appropriate.
+ # * collection_singular_ids - Returns an array of the associated objects' ids
+ # * collection_singular_ids=ids - Replace the collection with the objects identified by the primary keys in +ids+
+ # * collection.clear - Removes every object from the collection. This destroys the associated objects if they
+ # are associated with :dependent => :destroy, deletes them directly from the database if :dependent => :delete_all,
+ # otherwise sets their foreign keys to +NULL+.
+ # * collection.empty? - Returns +true+ if there are no associated objects.
+ # * collection.size - Returns the number of associated objects.
+ # * collection.find - Finds an associated object according to the same rules as Base.find.
+ # * collection.build(attributes = {}, ...) - Returns one or more new objects of the collection type that have been instantiated
+ # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
+ # associated object already exists, not if it's +nil+!
+ # * collection.create(attributes = {}) - Returns a new object of the collection 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).
+ # *Note:* This only works if an associated object already exists, not if it's +nil+!
+ #
+ # Example: A Firm class declares has_many :clients, which will add:
+ # * Firm#clients (similar to Clients.find :all, :conditions => "firm_id = #{id}")
+ # * Firm#clients<<
+ # * Firm#clients.delete
+ # * Firm#clients=
+ # * Firm#client_ids
+ # * Firm#client_ids=
+ # * Firm#clients.clear
+ # * Firm#clients.empty? (similar to firm.clients.size == 0)
+ # * Firm#clients.size (similar to Client.count "firm_id = #{id}")
+ # * Firm#clients.find (similar to Client.find(id, :conditions => "firm_id = #{id}"))
+ # * Firm#clients.build (similar to Client.new("firm_id" => id))
+ # * Firm#clients.create (similar to c = Client.new("firm_id" => id); c.save; c)
+ # The declaration can also include an options hash to specialize the behavior of the association.
+ #
+ # Options are:
+ # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So has_many :products will by default be linked to the Product class, but
+ # if the real class name is SpecialProduct, you'll have to specify it with this option.
+ # * :conditions - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
+ # SQL fragment, such as price > 5 AND name LIKE 'B%'. Record creations from the association are scoped if a hash
+ # is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create
+ # or @blog.posts.build.
+ # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # such as last_name, first_name DESC.
+ # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
+ # as the default :foreign_key.
+ # * :dependent - If set to :destroy all the associated objects are destroyed
+ # alongside this object by calling their +destroy+ method. If set to :delete_all all associated
+ # objects are deleted *without* calling their +destroy+ method. If set to :nullify all associated
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
+ # the :through option.
+ # * :finder_sql - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
+ # * :counter_sql - Specify a complete SQL statement to fetch the size of the association. If :finder_sql is
+ # specified but not :counter_sql, :counter_sql will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
+ # * :extend - Specify a named module for extending the proxy. See "Association extensions".
+ # * :include - Specify second-order associations that should be eager loaded when the collection is loaded.
+ # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # * :limit - An integer determining the limit on the number of rows that should be returned.
+ # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # * :select - By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
+ # * :as - Specifies a polymorphic interface (See belongs_to).
+ # * :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
+ # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to
+ # or has_many association on the join model.
+ # * :source - Specifies the source association name used by has_many :through queries. Only use it if the name cannot be
+ # inferred from the association. has_many :subscribers, :through => :subscriptions will look for either :subscribers or
+ # :subscriber on Subscription, unless a :source is given.
+ # * :source_type - Specifies type of the source association used by has_many :through queries where the source
+ # association is a polymorphic +belongs_to+.
+ # * :uniq - If true, duplicates will be omitted from the collection. Useful in conjunction with :through.
+ # * :readonly - If true, all the associated objects are readonly through the association.
+ #
+ # Option examples:
+ # has_many :comments, :order => "posted_on"
+ # has_many :comments, :include => :author
+ # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
+ # has_many :tracks, :order => "position", :dependent => :destroy
+ # has_many :comments, :dependent => :nullify
+ # has_many :tags, :as => :taggable
+ # has_many :reports, :readonly => true
+ # has_many :subscribers, :through => :subscriptions, :source => :user
+ # has_many :subscribers, :class_name => "Person", :finder_sql =>
+ # 'SELECT DISTINCT people.* ' +
+ # 'FROM people p, post_subscriptions ps ' +
+ # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
+ # 'ORDER BY p.first_name'
+ def has_many(association_id, options = {}, &extension)
+ reflection = create_has_many_reflection(association_id, options, &extension)
+
+ configure_dependency_for_has_many(reflection)
+
+ add_multiple_associated_save_callbacks(reflection.name)
+ add_association_callbacks(reflection.name, reflection.options)
+
+ if options[:through]
+ collection_accessor_methods(reflection, HasManyThroughAssociation)
+ else
+ collection_accessor_methods(reflection, HasManyAssociation)
+ end
+ end
+
+ # Adds the following methods for retrieval and query of a single associated object:
+ # +association+ is replaced with the symbol passed as the first argument, so
+ # has_one :manager would add among others manager.nil?.
+ # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found.
+ # * association=(associate) - Assigns the associate object, extracts the primary key, sets it as the foreign key,
+ # and saves the associate object.
+ # * association.nil? - Returns +true+ if there is no associated object.
+ # * 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. Note: This ONLY works if
+ # an association already exists. It will NOT work if the association is +nil+.
+ # * 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).
+ #
+ # Example: An Account class declares has_one :beneficiary, which will add:
+ # * Account#beneficiary (similar to Beneficiary.find(:first, :conditions => "account_id = #{id}"))
+ # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save)
+ # * Account#beneficiary.nil?
+ # * Account#build_beneficiary (similar to Beneficiary.new("account_id" => id))
+ # * Account#create_beneficiary (similar to b = Beneficiary.new("account_id" => id); b.save; b)
+ #
+ # The declaration can also include an options hash to specialize the behavior of the association.
+ #
+ # Options are:
+ # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So has_one :manager will by default be linked to the Manager class, but
+ # if the real class name is Person, you'll have to specify it with this option.
+ # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # SQL fragment, such as rank = 5.
+ # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # such as last_name, first_name DESC.
+ # * :dependent - If set to :destroy, the associated object is destroyed when this object is. If set to
+ # :delete, the associated object is deleted *without* calling its destroy method. If set to :nullify, the associated
+ # object's foreign key is set to +NULL+. Also, association is assigned.
+ # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
+ # as the default :foreign_key.
+ # * :include - Specify second-order associations that should be eager loaded when this object is loaded.
+ # * :as - Specifies a polymorphic interface (See belongs_to).
+ # * :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # * :through: Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key
+ # are ignored, as the association uses the source reflection. You can only use a :through query through a
+ # has_one or belongs_to association on the join model.
+ # * :source - Specifies the source association name used by has_one :through queries. Only use it if the name cannot be
+ # inferred from the association. has_one :favorite, :through => :favorites will look for a
+ # :favorite on Favorite, unless a :source is given.
+ # * :source_type - Specifies type of the source association used by has_one :through queries where the source
+ # association is a polymorphic +belongs_to+.
+ # * :readonly - If true, the associated object is readonly through the association.
+ #
+ # Option examples:
+ # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
+ # has_one :last_comment, :class_name => "Comment", :order => "posted_on"
+ # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
+ # has_one :attachment, :as => :attachable
+ # has_one :boss, :readonly => :true
+ # has_one :club, :through => :membership
+ # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable
+ 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)
+
+ ivar = "@#{reflection.name}"
+
+ method_name = "has_one_after_save_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+
+ if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
+ association["#{reflection.primary_key_name}"] = id
+ association.save(true)
+ end
+ end
+ after_save method_name
+
+ add_single_associated_save_callbacks(reflection.name)
+ association_accessor_methods(reflection, HasOneAssociation)
+ association_constructor_method(:build, reflection, HasOneAssociation)
+ association_constructor_method(:create, reflection, HasOneAssociation)
+
+ configure_dependency_for_has_one(reflection)
+ end
+ end
+
+ # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
+ # +association+ is replaced with the symbol passed as the first argument, so
+ # belongs_to :author would add among others author.nil?.
+ # * association(force_reload = false) - Returns the associated object. +nil+ is returned if none is found.
+ # * association=(associate) - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
+ # * association.nil? - Returns +true+ if there is no associated object.
+ # * 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.
+ # * 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).
+ #
+ # Example: A Post class declares belongs_to :author, which will add:
+ # * Post#author (similar to Author.find(author_id))
+ # * Post#author=(author) (similar to post.author_id = author.id)
+ # * Post#author? (similar to post.author == some_author)
+ # * Post#author.nil?
+ # * Post#build_author (similar to post.author = Author.new)
+ # * Post#create_author (similar to post.author = Author.new; post.author.save; post.author)
+ # The declaration can also include an options hash to specialize the behavior of the association.
+ #
+ # Options are:
+ # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So has_one :author will by default be linked to the Author class, but
+ # if the real class name is Person, you'll have to specify it with this option.
+ # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # SQL fragment, such as authorized = 1.
+ # * :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of the association with an "_id" suffix. So a class that defines a belongs_to :person association will use
+ # "person_id" as the default :foreign_key. Similarly, belongs_to :favorite_person, :class_name => "Person"
+ # will use a foreign key of "favorite_person_id".
+ # * :dependent - If set to :destroy, the associated object is destroyed when this object is. If set to
+ # :delete, the associated object is deleted *without* calling its destroy method. This option should not be specified when
+ # belongs_to is used in conjunction with a has_many relationship on another class because of the potential to leave
+ # orphaned records behind.
+ # * :counter_cache - Caches the number of belonging objects on the associate class through the use of +increment_counter+
+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
+ # destroyed. This requires that a column named #{table_name}_count (such as +comments_count+ for a belonging Comment class)
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
+ # a column name instead of a +true+/+false+ value to this option (e.g., :counter_cache => :my_custom_counter.)
+ # When creating a counter cache column, the database statement or migration must specify a default value of 0, failing to do
+ # this results in a counter with +NULL+ value, which will never increment.
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
+ # * :include - Specify second-order associations that should be eager loaded when this object is loaded.
+ # * :polymorphic - Specify this association is a polymorphic association by passing +true+.
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
+ # to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end).
+ # * :readonly - If true, the associated object is readonly through the association.
+ #
+ # Option examples:
+ # belongs_to :firm, :foreign_key => "client_of"
+ # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
+ # :conditions => 'discounts > #{payments_count}'
+ # belongs_to :attachable, :polymorphic => true
+ # belongs_to :project, :readonly => true
+ # belongs_to :post, :counter_cache => true
+ def belongs_to(association_id, options = {})
+ reflection = create_belongs_to_reflection(association_id, options)
+
+ ivar = "@#{reflection.name}"
+
+ if reflection.options[:polymorphic]
+ association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
+
+ method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+
+ if association && association.target
+ if association.new_record?
+ association.save(true)
+ end
+
+ if association.updated?
+ self["#{reflection.primary_key_name}"] = association.id
+ self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
+ end
+ end
+ end
+ before_save method_name
+ else
+ association_accessor_methods(reflection, BelongsToAssociation)
+ association_constructor_method(:build, reflection, BelongsToAssociation)
+ association_constructor_method(:create, reflection, BelongsToAssociation)
+
+ method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+
+ if !association.nil?
+ if association.new_record?
+ association.save(true)
+ end
+
+ if association.updated?
+ self["#{reflection.primary_key_name}"] = association.id
+ end
+ end
+ end
+ before_save method_name
+ end
+
+ # Create the callbacks to update counter cache
+ if options[:counter_cache]
+ cache_column = options[:counter_cache] == true ?
+ "#{self.to_s.underscore.pluralize}_count" :
+ options[:counter_cache]
+
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ end
+ after_create method_name
+
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
+ end
+ before_destroy method_name
+
+ module_eval(
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
+ )
+ end
+
+ configure_dependency_for_belongs_to(reflection)
+ end
+
+ # Associates two classes via an intermediate join table. Unless the join table is explicitly specified as
+ # an option, it is guessed using the lexical order of the class names. So a join between Developer and Project
+ # will give the default join table name of "developers_projects" because "D" outranks "P". Note that this precedence
+ # is calculated using the < operator for String. This means that if the strings are of different lengths,
+ # and the strings are equal when compared up to the shortest length, then the longer string is considered of higher
+ # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
+ # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
+ # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
+ # custom :join_table option if you need to.
+ #
+ # 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+ is replaced with the symbol passed as the first argument, so
+ # has_and_belongs_to_many :categories would add among others categories.empty?.
+ # * collection(force_reload = false) - Returns an array of all the associated objects.
+ # An empty array is returned if none are found.
+ # * collection<<(object, ...) - Adds one or more objects to the collection by creating associations in the join table
+ # (collection.push and collection.concat are aliases to this method).
+ # * collection.delete(object, ...) - Removes one or more objects from the collection by removing their associations from the join table.
+ # This does not destroy the objects.
+ # * collection=objects - Replaces the collection's content by deleting and adding objects as appropriate.
+ # * collection_singular_ids - Returns an array of the associated objects' ids.
+ # * collection_singular_ids=ids - Replace the collection by the objects identified by the primary keys in +ids+.
+ # * collection.clear - Removes every object from the collection. This does not destroy the objects.
+ # * collection.empty? - Returns +true+ if there are no associated objects.
+ # * collection.size - Returns the number of associated objects.
+ # * collection.find(id) - Finds an associated object responding to the +id+ and that
+ # meets the condition that it has to be associated with this object.
+ # * collection.build(attributes = {}) - Returns a new object of the collection type that has been instantiated
+ # with +attributes+ and linked to this object through the join table, but has not yet been saved.
+ # * collection.create(attributes = {}) - Returns a new object of the collection type that has been instantiated
+ # with +attributes+, linked to this object through the join table, and that has already been saved (if it passed the validation).
+ #
+ # Example: A Developer class declares has_and_belongs_to_many :projects, which will add:
+ # * Developer#projects
+ # * Developer#projects<<
+ # * Developer#projects.delete
+ # * Developer#projects=
+ # * Developer#project_ids
+ # * Developer#project_ids=
+ # * Developer#projects.clear
+ # * Developer#projects.empty?
+ # * Developer#projects.size
+ # * Developer#projects.find(id)
+ # * Developer#projects.build (similar to Project.new("project_id" => id))
+ # * Developer#projects.create (similar to c = Project.new("project_id" => id); c.save; c)
+ # The declaration may include an options hash to specialize the behavior of the association.
+ #
+ # Options are:
+ # * :class_name - Specify the class name of the association. Use it only if that name can't be inferred
+ # from the association name. So has_and_belongs_to_many :projects will by default be linked to the
+ # Project class, but if the real class name is SuperProject, you'll have to specify it with this option.
+ # * :join_table - Specify the name of the join table if the default based on lexical order isn't what you want.
+ # WARNING: If you're overwriting the table name of either class, the +table_name+ method MUST be declared underneath any
+ # +has_and_belongs_to_many+ declaration in order to work.
+ # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
+ # will use "person_id" as the default :foreign_key.
+ # * :association_foreign_key - Specify the association foreign key used for the association. By default this is
+ # guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
+ # the +has_and_belongs_to_many+ association will use "project_id" as the default :association_foreign_key.
+ # * :conditions - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
+ # SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used.
+ # has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create
+ # or @blog.posts.build.
+ # * :order - Specify the order in which the associated objects are returned as an ORDER BY SQL fragment,
+ # such as last_name, first_name DESC
+ # * :uniq - If true, duplicate associated objects will be ignored by accessors and query methods.
+ # * :finder_sql - Overwrite the default generated SQL statement used to fetch the association with a manual statement
+ # * :delete_sql - Overwrite the default generated SQL statement used to remove links between the associated
+ # classes with a manual statement.
+ # * :insert_sql - Overwrite the default generated SQL statement used to add links between the associated classes
+ # with a manual statement.
+ # * :extend - Anonymous module for extending the proxy, see "Association extensions".
+ # * :include - Specify second-order associations that should be eager loaded when the collection is loaded.
+ # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # * :limit - An integer determining the limit on the number of rows that should be returned.
+ # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
+ # * :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
+ # * :readonly - If true, all the associated objects are readonly through the association.
+ #
+ # Option examples:
+ # has_and_belongs_to_many :projects
+ # has_and_belongs_to_many :projects, :include => [ :milestones, :manager ]
+ # has_and_belongs_to_many :nations, :class_name => "Country"
+ # has_and_belongs_to_many :categories, :join_table => "prods_cats"
+ # has_and_belongs_to_many :categories, :readonly => true
+ # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
+ # '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)
+
+ add_multiple_associated_save_callbacks(reflection.name)
+ collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
+
+ # Don't use a before_destroy callback since users' before_destroy
+ # callbacks will be executed after the association is wiped out.
+ old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
+ class_eval <<-end_eval unless method_defined?(old_method)
+ alias_method :#{old_method}, :destroy_without_callbacks
+ def destroy_without_callbacks
+ #{reflection.name}.clear
+ #{old_method}
+ end
+ end_eval
+
+ add_association_callbacks(reflection.name, options)
+ end
+
+ private
+ # Generate a join table name from two provided tables names.
+ # The order of names in join name is determined by lexical precedence.
+ # join_table_name("members", "clubs")
+ # => "clubs_members"
+ # join_table_name("members", "special_clubs")
+ # => "members_special_clubs"
+ def join_table_name(first_table_name, second_table_name)
+ if first_table_name < second_table_name
+ join_table = "#{first_table_name}_#{second_table_name}"
+ else
+ join_table = "#{second_table_name}_#{first_table_name}"
+ end
+
+ table_name_prefix + join_table + table_name_suffix
+ end
+
+ def association_accessor_methods(reflection, association_proxy_class)
+ ivar = "@#{reflection.name}"
+
+ define_method(reflection.name) do |*params|
+ force_reload = params.first unless params.empty?
+
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ if association.nil? || force_reload
+ association = association_proxy_class.new(self, reflection)
+ retval = association.reload
+ if retval.nil? and association_proxy_class == BelongsToAssociation
+ instance_variable_set(ivar, nil)
+ return nil
+ end
+ instance_variable_set(ivar, association)
+ end
+
+ association.target.nil? ? nil : association
+ end
+
+ define_method("#{reflection.name}=") do |new_value|
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ if association.nil? || association.target != new_value
+ association = association_proxy_class.new(self, reflection)
+ end
+
+ if association_proxy_class == HasOneThroughAssociation
+ association.create_through_record(new_value)
+ self.send(reflection.name, new_value)
+ else
+ association.replace(new_value)
+ end
+
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
+ end
+
+ define_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
+ instance_variable_set(ivar, association)
+ end
+ end
+
+ def collection_reader_method(reflection, association_proxy_class)
+ define_method(reflection.name) do |*params|
+ ivar = "@#{reflection.name}"
+
+ force_reload = params.first unless params.empty?
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ unless association.respond_to?(:loaded?)
+ association = association_proxy_class.new(self, reflection)
+ instance_variable_set(ivar, association)
+ end
+
+ association.reload if force_reload
+
+ association
+ end
+
+ define_method("#{reflection.name.to_s.singularize}_ids") do
+ send(reflection.name).map(&:id)
+ end
+ end
+
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
+ collection_reader_method(reflection, association_proxy_class)
+
+ if writer
+ define_method("#{reflection.name}=") do |new_value|
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
+ association = send(reflection.name)
+ association.replace(new_value)
+ association
+ end
+
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
+ ids = (new_value || []).reject { |nid| nid.blank? }
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
+ end
+ end
+ end
+
+ def add_single_associated_save_callbacks(association_name)
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("@#{association_name}")
+ if !association.nil?
+ errors.add "#{association_name}" unless association.target.nil? || association.valid?
+ end
+ end
+
+ validate method_name
+ end
+
+ def add_multiple_associated_save_callbacks(association_name)
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
+ ivar = "@#{association_name}"
+
+ define_method(method_name) do
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ if association.respond_to?(:loaded?)
+ if new_record?
+ association
+ elsif association.loaded?
+ association.select { |record| record.new_record? }
+ else
+ association.target.select { |record| record.new_record? }
+ end.each do |record|
+ errors.add "#{association_name}" unless record.valid?
+ end
+ end
+ end
+
+ validate method_name
+
+ method_name = "before_save_associated_records_for_#{association_name}".to_sym
+ define_method(method_name) do
+ @new_record_before_save = new_record?
+ true
+ end
+ before_save method_name
+
+ method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
+ define_method(method_name) do
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
+
+ records_to_save = if @new_record_before_save
+ association
+ elsif association.respond_to?(:loaded?) && association.loaded?
+ association.select { |record| record.new_record? }
+ elsif association.respond_to?(:loaded?) && !association.loaded?
+ association.target.select { |record| record.new_record? }
+ else
+ []
+ end
+
+ records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
+
+ # reconstruct the SQL queries now that we know the owner's id
+ association.send(:construct_sql) if association.respond_to?(:construct_sql)
+ end
+
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
+ after_create method_name
+ after_update method_name
+ end
+
+ def association_constructor_method(constructor, reflection, association_proxy_class)
+ define_method("#{constructor}_#{reflection.name}") do |*params|
+ ivar = "@#{reflection.name}"
+
+ attributees = params.first unless params.empty?
+ replace_existing = params[1].nil? ? true : params[1]
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
+
+ if association.nil?
+ association = association_proxy_class.new(self, reflection)
+ instance_variable_set(ivar, association)
+ end
+
+ if association_proxy_class == HasOneAssociation
+ association.send(constructor, attributees, replace_existing)
+ else
+ association.send(constructor, attributees)
+ end
+ end
+ end
+
+ def find_with_associations(options = {})
+ catch :invalid_query do
+ join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
+ rows = select_all_rows(options, join_dependency)
+ return join_dependency.instantiate(rows)
+ end
+ []
+ end
+
+ # See HasManyAssociation#delete_records. Dependent associations
+ # delete children, otherwise foreign key is set to NULL.
+ def configure_dependency_for_has_many(reflection)
+ if reflection.options.include?(:dependent)
+ # Add polymorphic type if the :as option is present
+ dependent_conditions = []
+ dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}"
+ dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as]
+ dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
+ dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
+
+ case reflection.options[:dependent]
+ when :destroy
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ send("#{reflection.name}").each { |o| o.destroy }
+ end
+ before_destroy method_name
+ when :delete_all
+ module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }"
+ when :nullify
+ module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }"
+ else
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
+ end
+ end
+ end
+
+ def configure_dependency_for_has_one(reflection)
+ if reflection.options.include?(:dependent)
+ case reflection.options[:dependent]
+ when :destroy
+ method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.destroy unless association.nil?
+ end
+ before_destroy method_name
+ when :delete
+ method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.class.delete(association.id) unless association.nil?
+ end
+ before_destroy method_name
+ when :nullify
+ method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil?
+ end
+ before_destroy method_name
+ else
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})"
+ end
+ end
+ end
+
+ def configure_dependency_for_belongs_to(reflection)
+ if reflection.options.include?(:dependent)
+ case reflection.options[:dependent]
+ when :destroy
+ method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.destroy unless association.nil?
+ end
+ before_destroy method_name
+ when :delete
+ method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
+ define_method(method_name) do
+ association = send("#{reflection.name}")
+ association.class.delete(association.id) unless association.nil?
+ end
+ before_destroy method_name
+ else
+ raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
+ end
+ end
+ end
+
+ def create_has_many_reflection(association_id, options, &extension)
+ options.assert_valid_keys(
+ :class_name, :table_name, :foreign_key,
+ :dependent,
+ :select, :conditions, :include, :order, :group, :limit, :offset,
+ :as, :through, :source, :source_type,
+ :uniq,
+ :finder_sql, :counter_sql,
+ :before_add, :after_add, :before_remove, :after_remove,
+ :extend, :readonly
+ )
+
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
+
+ create_reflection(:has_many, association_id, options, self)
+ end
+
+ def create_has_one_reflection(association_id, options)
+ options.assert_valid_keys(
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly
+ )
+
+ create_reflection(:has_one, association_id, options, self)
+ end
+
+ def create_has_one_through_reflection(association_id, options)
+ options.assert_valid_keys(
+ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type
+ )
+ create_reflection(:has_one, association_id, options, self)
+ end
+
+ def create_belongs_to_reflection(association_id, options)
+ options.assert_valid_keys(
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
+ :counter_cache, :extend, :polymorphic, :readonly
+ )
+
+ reflection = create_reflection(:belongs_to, association_id, options, self)
+
+ if options[:polymorphic]
+ reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
+ end
+
+ reflection
+ end
+
+ def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
+ options.assert_valid_keys(
+ :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
+ :select, :conditions, :include, :order, :group, :limit, :offset,
+ :uniq,
+ :finder_sql, :delete_sql, :insert_sql,
+ :before_add, :after_add, :before_remove, :after_remove,
+ :extend, :readonly
+ )
+
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
+
+ reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
+
+ reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
+
+ reflection
+ end
+
+ def reflect_on_included_associations(associations)
+ [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
+ end
+
+ def guard_against_unlimitable_reflections(reflections, options)
+ if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
+ raise(
+ ConfigurationError,
+ "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
+ )
+ end
+ end
+
+ def select_all_rows(options, join_dependency)
+ connection.select_all(
+ construct_finder_sql_with_included_associations(options, join_dependency),
+ "#{name} Load Including Associations"
+ )
+ end
+
+ def construct_finder_sql_with_included_associations(options, join_dependency)
+ scope = scope(:find)
+ sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
+
+ add_joins!(sql, options, scope)
+ add_conditions!(sql, options[:conditions], scope)
+ add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+
+ add_group!(sql, options[:group], scope)
+ add_order!(sql, options[:order], scope)
+ add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
+ add_lock!(sql, options, scope)
+
+ return sanitize_sql(sql)
+ end
+
+ def add_limited_ids_condition!(sql, options, join_dependency)
+ unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
+ sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) "
+ else
+ throw :invalid_query
+ end
+ end
+
+ def select_limited_ids_list(options, join_dependency)
+ pk = columns_hash[primary_key]
+
+ connection.select_all(
+ construct_finder_sql_for_association_limiting(options, join_dependency),
+ "#{name} Load IDs For Limited Eager Loading"
+ ).collect { |row| connection.quote(row[primary_key], pk) }.join(", ")
+ end
+
+ def construct_finder_sql_for_association_limiting(options, join_dependency)
+ scope = scope(:find)
+
+ # Only join tables referenced in order or conditions since this is particularly slow on the pre-query.
+ tables_from_conditions = conditions_tables(options)
+ tables_from_order = order_tables(options)
+ all_tables = tables_from_conditions + tables_from_order
+ distinct_join_associations = all_tables.uniq.map{|table|
+ join_dependency.joins_for_table_name(table)
+ }.flatten.compact.uniq
+
+ is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order)
+ sql = "SELECT "
+ if is_distinct
+ sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", options[:order])
+ else
+ sql << primary_key
+ end
+ sql << " FROM #{connection.quote_table_name table_name} "
+
+ if is_distinct
+ sql << distinct_join_associations.collect(&:association_join).join
+ add_joins!(sql, options, scope)
+ end
+
+ add_conditions!(sql, options[:conditions], scope)
+ add_group!(sql, options[:group], scope)
+
+ if options[:order] && is_distinct
+ connection.add_order_by_for_association_limiting!(sql, options)
+ else
+ add_order!(sql, options[:order], scope)
+ end
+
+ add_limit!(sql, options, scope)
+
+ return sanitize_sql(sql)
+ end
+
+ def conditions_tables(options)
+ # look in both sets of conditions
+ conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
+ case cond
+ when nil then all
+ when Array then all << cond.first
+ else all << cond
+ end
+ end
+ conditions.join(' ').scan(/([\.\w]+).?\./).flatten
+ end
+
+ def order_tables(options)
+ order = options[:order]
+ return [] unless order && order.is_a?(String)
+ order.scan(/([\.\w]+).?\./).flatten
+ end
+
+ def selects_tables(options)
+ select = options[:select]
+ return [] unless select && select.is_a?(String)
+ select.scan(/"?([\.\w]+)"?.?\./).flatten
+ end
+
+ # Checks if the conditions reference a table other than the current model table
+ def include_eager_conditions?(options, tables = nil)
+ ((tables || conditions_tables(options)) - [table_name]).any?
+ end
+
+ # Checks if the query order references a table other than the current model's table.
+ def include_eager_order?(options, tables = nil)
+ ((tables || order_tables(options)) - [table_name]).any?
+ end
+
+ def include_eager_select?(options)
+ (selects_tables(options) - [table_name]).any?
+ end
+
+ def references_eager_loaded_tables?(options)
+ include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
+ end
+
+ def using_limitable_reflections?(reflections)
+ reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
+ end
+
+ def column_aliases(join_dependency)
+ join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
+ "#{connection.quote_table_name join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
+ end
+
+ def add_association_callbacks(association_name, options)
+ callbacks = %w(before_add after_add before_remove after_remove)
+ callbacks.each do |callback_name|
+ full_callback_name = "#{callback_name}_for_#{association_name}"
+ defined_callbacks = options[callback_name.to_sym]
+ if options.has_key?(callback_name.to_sym)
+ class_inheritable_reader full_callback_name.to_sym
+ write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
+ else
+ write_inheritable_attribute(full_callback_name.to_sym, [])
+ end
+ end
+ end
+
+ def condition_word(sql)
+ sql =~ /where/i ? " AND " : "WHERE "
+ end
+
+ def create_extension_modules(association_id, block_extension, extensions)
+ if block_extension
+ extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
+
+ silence_warnings do
+ Object.const_set(extension_module_name, Module.new(&block_extension))
+ end
+ Array(extensions).push(extension_module_name.constantize)
+ else
+ Array(extensions)
+ end
+ end
+
+ class JoinDependency # :nodoc:
+ attr_reader :joins, :reflections, :table_aliases
+
+ def initialize(base, associations, joins)
+ @joins = [JoinBase.new(base, joins)]
+ @associations = associations
+ @reflections = []
+ @base_records_hash = {}
+ @base_records_in_order = []
+ @table_aliases = Hash.new { |aliases, table| aliases[table] = 0 }
+ @table_aliases[base.table_name] = 1
+ build(associations)
+ end
+
+ def join_associations
+ @joins[1..-1].to_a
+ end
+
+ def join_base
+ @joins[0]
+ end
+
+ def instantiate(rows)
+ rows.each_with_index do |row, i|
+ primary_id = join_base.record_id(row)
+ unless @base_records_hash[primary_id]
+ @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
+ end
+ construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
+ end
+ remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations)
+ return @base_records_in_order
+ end
+
+ def remove_duplicate_results!(base, records, associations)
+ case associations
+ when Symbol, String
+ reflection = base.reflections[associations]
+ if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+ records.each { |record| record.send(reflection.name).target.uniq! }
+ end
+ when Array
+ associations.each do |association|
+ remove_duplicate_results!(base, records, association)
+ end
+ when Hash
+ associations.keys.each do |name|
+ reflection = base.reflections[name]
+ is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro)
+
+ parent_records = records.map do |record|
+ descendant = record.send(reflection.name)
+ next unless descendant
+ descendant.target.uniq! if is_collection
+ descendant
+ end.flatten.compact
+
+ remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty?
+ end
+ end
+ end
+
+ def join_for_table_name(table_name)
+ @joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil
+ end
+
+ def joins_for_table_name(table_name)
+ join = join_for_table_name(table_name)
+ result = nil
+ if join && join.is_a?(JoinAssociation)
+ result = [join]
+ if join.parent && join.parent.is_a?(JoinAssociation)
+ result = joins_for_table_name(join.parent.aliased_table_name) +
+ result
+ end
+ end
+ result
+ end
+
+ protected
+ def build(associations, parent = nil)
+ parent ||= @joins.last
+ case associations
+ when Symbol, String
+ reflection = parent.reflections[associations.to_s.intern] or
+ raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
+ @reflections << reflection
+ @joins << build_join_association(reflection, parent)
+ when Array
+ associations.each do |association|
+ build(association, parent)
+ end
+ when Hash
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
+ build(name, parent)
+ build(associations[name])
+ end
+ else
+ raise ConfigurationError, associations.inspect
+ end
+ end
+
+ # overridden in InnerJoinDependency subclass
+ def build_join_association(reflection, parent)
+ JoinAssociation.new(reflection, self, parent)
+ end
+
+ def construct(parent, associations, joins, row)
+ case associations
+ when Symbol, String
+ while (join = joins.shift).reflection.name.to_s != associations.to_s
+ raise ConfigurationError, "Not Enough Associations" if joins.empty?
+ end
+ construct_association(parent, join, row)
+ when Array
+ associations.each do |association|
+ construct(parent, association, joins, row)
+ end
+ when Hash
+ associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
+ association = construct_association(parent, joins.shift, row)
+ construct(association, associations[name], joins, row) if association
+ end
+ else
+ raise ConfigurationError, associations.inspect
+ end
+ end
+
+ def construct_association(record, join, row)
+ case join.reflection.macro
+ when :has_many, :has_and_belongs_to_many
+ collection = record.send(join.reflection.name)
+ collection.loaded
+
+ return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
+ association = join.instantiate(row)
+ collection.target.push(association)
+ when :has_one
+ return if record.id.to_s != join.parent.record_id(row).to_s
+ association = join.instantiate(row) unless row[join.aliased_primary_key].nil?
+ record.send("set_#{join.reflection.name}_target", association)
+ when :belongs_to
+ return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
+ association = join.instantiate(row)
+ record.send("set_#{join.reflection.name}_target", association)
+ else
+ raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
+ end
+ return association
+ end
+
+ class JoinBase # :nodoc:
+ attr_reader :active_record, :table_joins
+ delegate :table_name, :column_names, :primary_key, :reflections, :sanitize_sql, :to => :active_record
+
+ def initialize(active_record, joins = nil)
+ @active_record = active_record
+ @cached_record = {}
+ @table_joins = joins
+ end
+
+ def aliased_prefix
+ "t0"
+ end
+
+ def aliased_primary_key
+ "#{ aliased_prefix }_r0"
+ end
+
+ def aliased_table_name
+ active_record.table_name
+ end
+
+ def column_names_with_alias
+ unless defined?(@column_names_with_alias)
+ @column_names_with_alias = []
+
+ ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
+ @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
+ end
+ end
+
+ @column_names_with_alias
+ end
+
+ def extract_record(row)
+ column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
+ end
+
+ def record_id(row)
+ row[aliased_primary_key]
+ end
+
+ def instantiate(row)
+ @cached_record[record_id(row)] ||= active_record.send(:instantiate, extract_record(row))
+ end
+ end
+
+ class JoinAssociation < JoinBase # :nodoc:
+ attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
+ delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
+
+ def initialize(reflection, join_dependency, parent = nil)
+ reflection.check_validity!
+ if reflection.options[:polymorphic]
+ raise EagerLoadPolymorphicError.new(reflection)
+ end
+
+ super(reflection.klass)
+ @join_dependency = join_dependency
+ @parent = parent
+ @reflection = reflection
+ @aliased_prefix = "t#{ join_dependency.joins.size }"
+ @parent_table_name = parent.active_record.table_name
+ @aliased_table_name = aliased_table_name_for(table_name)
+
+ if reflection.macro == :has_and_belongs_to_many
+ @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
+ end
+
+ if reflection.macro == :has_many && reflection.options[:through]
+ @aliased_join_table_name = aliased_table_name_for(reflection.through_reflection.klass.table_name, "_join")
+ end
+ end
+
+ def association_join
+ connection = reflection.active_record.connection
+ join = case reflection.macro
+ when :has_and_belongs_to_many
+ " #{join_type} %s ON %s.%s = %s.%s " % [
+ table_alias_for(options[:join_table], aliased_join_table_name),
+ connection.quote_table_name(aliased_join_table_name),
+ options[:foreign_key] || reflection.active_record.to_s.foreign_key,
+ connection.quote_table_name(parent.aliased_table_name),
+ reflection.active_record.primary_key] +
+ " #{join_type} %s ON %s.%s = %s.%s " % [
+ table_name_and_alias,
+ connection.quote_table_name(aliased_table_name),
+ klass.primary_key,
+ connection.quote_table_name(aliased_join_table_name),
+ options[:association_foreign_key] || klass.to_s.foreign_key
+ ]
+ when :has_many, :has_one
+ case
+ when reflection.macro == :has_many && reflection.options[:through]
+ through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
+
+ jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
+ first_key = second_key = as_extra = nil
+
+ if through_reflection.options[:as] # has_many :through against a polymorphic join
+ jt_foreign_key = through_reflection.options[:as].to_s + '_id'
+ jt_as_extra = " AND %s.%s = %s" % [
+ connection.quote_table_name(aliased_join_table_name),
+ connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
+ klass.quote_value(parent.active_record.base_class.name)
+ ]
+ else
+ jt_foreign_key = through_reflection.primary_key_name
+ end
+
+ case source_reflection.macro
+ when :has_many
+ if source_reflection.options[:as]
+ first_key = "#{source_reflection.options[:as]}_id"
+ second_key = options[:foreign_key] || primary_key
+ as_extra = " AND %s.%s = %s" % [
+ connection.quote_table_name(aliased_table_name),
+ connection.quote_column_name("#{source_reflection.options[:as]}_type"),
+ klass.quote_value(source_reflection.active_record.base_class.name)
+ ]
+ else
+ first_key = through_reflection.klass.base_class.to_s.foreign_key
+ second_key = options[:foreign_key] || primary_key
+ end
+
+ unless through_reflection.klass.descends_from_active_record?
+ jt_sti_extra = " AND %s.%s = %s" % [
+ connection.quote_table_name(aliased_join_table_name),
+ connection.quote_column_name(through_reflection.active_record.inheritance_column),
+ through_reflection.klass.quote_value(through_reflection.klass.name.demodulize)]
+ end
+ when :belongs_to
+ first_key = primary_key
+ if reflection.options[:source_type]
+ second_key = source_reflection.association_foreign_key
+ jt_source_extra = " AND %s.%s = %s" % [
+ connection.quote_table_name(aliased_join_table_name),
+ connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
+ klass.quote_value(reflection.options[:source_type])
+ ]
+ else
+ second_key = source_reflection.primary_key_name
+ end
+ end
+
+ " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
+ table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
+ connection.quote_table_name(parent.aliased_table_name),
+ connection.quote_column_name(parent.primary_key),
+ connection.quote_table_name(aliased_join_table_name),
+ connection.quote_column_name(jt_foreign_key),
+ jt_as_extra, jt_source_extra, jt_sti_extra
+ ] +
+ " #{join_type} %s ON (%s.%s = %s.%s%s) " % [
+ table_name_and_alias,
+ connection.quote_table_name(aliased_table_name),
+ connection.quote_column_name(first_key),
+ connection.quote_table_name(aliased_join_table_name),
+ connection.quote_column_name(second_key),
+ as_extra
+ ]
+
+ when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
+ " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [
+ table_name_and_alias,
+ connection.quote_table_name(aliased_table_name),
+ "#{reflection.options[:as]}_id",
+ connection.quote_table_name(parent.aliased_table_name),
+ parent.primary_key,
+ connection.quote_table_name(aliased_table_name),
+ "#{reflection.options[:as]}_type",
+ klass.quote_value(parent.active_record.base_class.name)
+ ]
+ else
+ foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
+ " #{join_type} %s ON %s.%s = %s.%s " % [
+ table_name_and_alias,
+ aliased_table_name,
+ foreign_key,
+ parent.aliased_table_name,
+ parent.primary_key
+ ]
+ end
+ when :belongs_to
+ " #{join_type} %s ON %s.%s = %s.%s " % [
+ table_name_and_alias,
+ connection.quote_table_name(aliased_table_name),
+ reflection.klass.primary_key,
+ connection.quote_table_name(parent.aliased_table_name),
+ options[:foreign_key] || reflection.primary_key_name
+ ]
+ else
+ ""
+ end || ''
+ join << %(AND %s.%s = %s ) % [
+ connection.quote_table_name(aliased_table_name),
+ connection.quote_column_name(klass.inheritance_column),
+ klass.quote_value(klass.name.demodulize)] unless klass.descends_from_active_record?
+
+ [through_reflection, reflection].each do |ref|
+ join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions]))} " if ref && ref.options[:conditions]
+ end
+
+ join
+ end
+
+ protected
+
+ def aliased_table_name_for(name, suffix = nil)
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
+ @join_dependency.table_aliases[name] += 1
+ end
+
+ unless @join_dependency.table_aliases[name].zero?
+ # if the table name has been used, then use an alias
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
+ table_index = @join_dependency.table_aliases[name]
+ @join_dependency.table_aliases[name] += 1
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
+ else
+ @join_dependency.table_aliases[name] += 1
+ end
+
+ name
+ end
+
+ def pluralize(table_name)
+ ActiveRecord::Base.pluralize_table_names ? table_name.to_s.pluralize : table_name
+ end
+
+ def table_alias_for(table_name, table_alias)
+ "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip
+ end
+
+ def table_name_and_alias
+ table_alias_for table_name, @aliased_table_name
+ end
+
+ def interpolate_sql(sql)
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
+ end
+
+ private
+ def join_type
+ "LEFT OUTER JOIN"
+ end
+ end
+ end
+
+ class InnerJoinDependency < JoinDependency # :nodoc:
+ protected
+ def build_join_association(reflection, parent)
+ InnerJoinAssociation.new(reflection, self, parent)
+ end
+
+ class InnerJoinAssociation < JoinAssociation
+ private
+ def join_type
+ "INNER JOIN"
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb
new file mode 100644
index 0000000..52d2a98
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/association_collection.rb
@@ -0,0 +1,365 @@
+require 'set'
+
+module ActiveRecord
+ module Associations
+ class AssociationCollection < AssociationProxy #:nodoc:
+ def initialize(owner, reflection)
+ super
+ construct_sql
+ 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(&: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
+ else
+ conditions = "#{@finder_sql}"
+ if sanitized_conditions = sanitize_sql(options[:conditions])
+ conditions << " AND (#{sanitized_conditions})"
+ end
+
+ options[:conditions] = conditions
+
+ if options[:order] && @reflection.options[:order]
+ options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
+ elsif @reflection.options[:order]
+ options[:order] = @reflection.options[:order]
+ end
+
+ # Build options specific to association
+ construct_find_options!(options)
+
+ merge_options_from_reflection!(options)
+
+ # Pass through args exactly as we received them.
+ args << options
+ @reflection.klass.find(*args)
+ end
+ end
+
+ # Fetches the first one using SQL if possible.
+ def first(*args)
+ if fetch_first_or_last_using_find? args
+ find(:first, *args)
+ else
+ load_target unless loaded?
+ @target.first(*args)
+ end
+ end
+
+ # Fetches the last one using SQL if possible.
+ def last(*args)
+ if fetch_first_or_last_using_find? args
+ find(:last, *args)
+ else
+ load_target unless loaded?
+ @target.last(*args)
+ end
+ end
+
+ def to_ary
+ load_target
+ @target.to_ary
+ end
+
+ def reset
+ reset_target!
+ @loaded = false
+ end
+
+ def build(attributes = {})
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| build(attr) }
+ else
+ build_record(attributes) { |record| set_belongs_to_association_for(record) }
+ end
+ end
+
+ # Add +records+ to this association. Returns +self+ so method calls may be chained.
+ # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
+ def <<(*records)
+ result = true
+ load_target if @owner.new_record?
+
+ @owner.transaction do
+ flatten_deeper(records).each do |record|
+ raise_on_type_mismatch(record)
+ add_record_to_target_with_callbacks(record) do |r|
+ result &&= insert_record(record) unless @owner.new_record?
+ end
+ end
+ end
+
+ result && self
+ end
+
+ alias_method :push, :<<
+ alias_method :concat, :<<
+
+ # Remove all records from this association
+ def delete_all
+ load_target
+ delete(@target)
+ reset_target!
+ end
+
+ # Calculate sum using SQL, not Enumerable
+ def sum(*args)
+ if block_given?
+ calculate(:sum, *args) { |*block_args| yield(*block_args) }
+ else
+ calculate(:sum, *args)
+ end
+ end
+
+ # Remove +records+ from this association. Does not destroy +records+.
+ def delete(*records)
+ records = flatten_deeper(records)
+ records.each { |record| raise_on_type_mismatch(record) }
+
+ @owner.transaction do
+ records.each { |record| callback(:before_remove, record) }
+
+ old_records = records.reject {|r| r.new_record? }
+ delete_records(old_records) if old_records.any?
+
+ records.each do |record|
+ @target.delete(record)
+ callback(:after_remove, record)
+ end
+ end
+ end
+
+ # Removes all records from this association. Returns +self+ so method calls may be chained.
+ def clear
+ return self if length.zero? # forces load_target if it hasn't happened already
+
+ if @reflection.options[:dependent] && @reflection.options[:dependent] == :destroy
+ destroy_all
+ else
+ delete_all
+ end
+
+ self
+ end
+
+ def destroy_all
+ @owner.transaction do
+ each { |record| record.destroy }
+ end
+
+ reset_target!
+ end
+
+ def create(attrs = {})
+ if attrs.is_a?(Array)
+ attrs.collect { |attr| create(attr) }
+ else
+ create_record(attrs) do |record|
+ yield(record) if block_given?
+ record.save
+ end
+ end
+ end
+
+ def create!(attrs = {})
+ create_record(attrs) do |record|
+ yield(record) if block_given?
+ record.save!
+ end
+ end
+
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
+ def size
+ if @owner.new_record? || (loaded? && !@reflection.options[:uniq])
+ @target.size
+ elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array)
+ unsaved_records = Array(@target.detect { |r| r.new_record? })
+ unsaved_records.size + count_records
+ else
+ count_records
+ end
+ end
+
+ # Returns the size of the collection by loading it and calling size on the array. If you want to use this method to check
+ # whether the collection is empty, use collection.length.zero? instead of collection.empty?
+ def length
+ load_target.size
+ end
+
+ def empty?
+ size.zero?
+ end
+
+ def any?
+ if block_given?
+ method_missing(:any?) { |*block_args| yield(*block_args) }
+ else
+ !empty?
+ end
+ end
+
+ def uniq(collection = self)
+ seen = Set.new
+ collection.inject([]) do |kept, record|
+ unless seen.include?(record.id)
+ kept << record
+ seen << record.id
+ end
+ kept
+ end
+ end
+
+ # Replace this collection with +other_array+
+ # This will perform a diff and delete/add only records that have changed.
+ def replace(other_array)
+ other_array.each { |val| raise_on_type_mismatch(val) }
+
+ load_target
+ other = other_array.size < 100 ? other_array : other_array.to_set
+ current = @target.size < 100 ? @target : @target.to_set
+
+ @owner.transaction do
+ delete(@target.select { |v| !other.include?(v) })
+ concat(other_array.select { |v| !current.include?(v) })
+ end
+ end
+
+ def include?(record)
+ return false unless record.is_a?(@reflection.klass)
+ load_target if @reflection.options[:finder_sql] && !loaded?
+ return @target.include?(record) if loaded?
+ exists?(record)
+ end
+
+ protected
+ def construct_find_options!(options)
+ end
+
+ def load_target
+ if !@owner.new_record? || foreign_key_present
+ begin
+ if !loaded?
+ if @target.is_a?(Array) && @target.any?
+ @target = find_target + @target.find_all {|t| t.new_record? }
+ else
+ @target = find_target
+ end
+ end
+ rescue ActiveRecord::RecordNotFound
+ reset
+ end
+ end
+
+ loaded if target
+ target
+ end
+
+ def method_missing(method, *args)
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
+ if block_given?
+ super { |*block_args| yield(*block_args) }
+ else
+ super
+ end
+ elsif @reflection.klass.scopes.include?(method)
+ @reflection.klass.scopes[method].call(self, *args)
+ else
+ with_scope(construct_scope) do
+ if block_given?
+ @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
+ else
+ @reflection.klass.send(method, *args)
+ end
+ end
+ end
+ end
+
+ # overloaded in derived Association classes to provide useful scoping depending on association type.
+ def construct_scope
+ {}
+ end
+
+ def reset_target!
+ @target = Array.new
+ end
+
+ def find_target
+ records =
+ if @reflection.options[:finder_sql]
+ @reflection.klass.find_by_sql(@finder_sql)
+ else
+ find(:all)
+ end
+
+ @reflection.options[:uniq] ? uniq(records) : records
+ end
+
+ private
+
+ def create_record(attrs)
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
+ ensure_owner_is_not_new
+ record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
+ if block_given?
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
+ else
+ add_record_to_target_with_callbacks(record)
+ end
+ end
+
+ def build_record(attrs)
+ attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
+ record = @reflection.klass.new(attrs)
+ if block_given?
+ add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
+ else
+ add_record_to_target_with_callbacks(record)
+ end
+ end
+
+ def add_record_to_target_with_callbacks(record)
+ callback(:before_add, record)
+ yield(record) if block_given?
+ @target ||= [] unless loaded?
+ @target << record
+ callback(:after_add, record)
+ record
+ end
+
+ def callback(method, record)
+ callbacks_for(method).each do |callback|
+ ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record)
+ end
+ end
+
+ def callbacks_for(callback_name)
+ full_callback_name = "#{callback_name}_for_#{@reflection.name}"
+ @owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
+ end
+
+ def ensure_owner_is_not_new
+ if @owner.new_record?
+ raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
+ end
+ end
+
+ def fetch_first_or_last_using_find?(args)
+ args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb
new file mode 100644
index 0000000..11c6424
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb
@@ -0,0 +1,224 @@
+module ActiveRecord
+ module Associations
+ # This is the root class of all association proxies:
+ #
+ # AssociationProxy
+ # BelongsToAssociation
+ # HasOneAssociation
+ # BelongsToPolymorphicAssociation
+ # AssociationCollection
+ # HasAndBelongsToManyAssociation
+ # HasManyAssociation
+ # HasManyThroughAssociation
+ # HasOneThroughAssociation
+ #
+ # Association proxies in Active Record are middlemen between the object that
+ # holds the association, known as the @owner, and the actual associated
+ # object, known as the @target. The kind of association any proxy is
+ # about is available in @reflection. That's an instance of the class
+ # ActiveRecord::Reflection::AssociationReflection.
+ #
+ # For example, given
+ #
+ # class Blog < ActiveRecord::Base
+ # has_many :posts
+ # end
+ #
+ # blog = Blog.find(:first)
+ #
+ # the association proxy in blog.posts has the object in +blog+ as
+ # @owner, the collection of its posts as @target, and
+ # the @reflection object represents a :has_many macro.
+ #
+ # This class has most of the basic instance methods removed, and delegates
+ # unknown methods to @target via method_missing. As a
+ # corner case, it even removes the +class+ method and that's why you get
+ #
+ # blog.posts.class # => Array
+ #
+ # though the object behind blog.posts is not an Array, but an
+ # ActiveRecord::Associations::HasManyAssociation.
+ #
+ # The @target object is not loaded until needed. For example,
+ #
+ # blog.posts.count
+ #
+ # 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 =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
+
+ def initialize(owner, reflection)
+ @owner, @reflection = owner, reflection
+ Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
+ reset
+ end
+
+ def proxy_owner
+ @owner
+ end
+
+ def proxy_reflection
+ @reflection
+ end
+
+ def proxy_target
+ @target
+ end
+
+ def respond_to?(symbol, include_priv = false)
+ proxy_respond_to?(symbol, include_priv) || (load_target && @target.respond_to?(symbol, include_priv))
+ end
+
+ # Explicitly proxy === because the instance method removal above
+ # doesn't catch it.
+ def ===(other)
+ load_target
+ other === @target
+ end
+
+ def aliased_table_name
+ @reflection.klass.table_name
+ end
+
+ def conditions
+ @conditions ||= interpolate_sql(sanitize_sql(@reflection.options[:conditions])) if @reflection.options[:conditions]
+ end
+ alias :sql_conditions :conditions
+
+ def reset
+ @loaded = false
+ @target = nil
+ end
+
+ def reload
+ reset
+ load_target
+ self unless @target.nil?
+ end
+
+ def loaded?
+ @loaded
+ end
+
+ def loaded
+ @loaded = true
+ end
+
+ def target
+ @target
+ end
+
+ def target=(target)
+ @target = target
+ loaded
+ end
+
+ def inspect
+ load_target
+ @target.inspect
+ end
+
+ protected
+ def dependent?
+ @reflection.options[:dependent]
+ end
+
+ def quoted_record_ids(records)
+ records.map { |record| record.quoted_id }.join(',')
+ end
+
+ def interpolate_sql_options!(options, *keys)
+ keys.each { |key| options[key] &&= interpolate_sql(options[key]) }
+ end
+
+ def interpolate_sql(sql, record = nil)
+ @owner.send(:interpolate_sql, sql, record)
+ end
+
+ def sanitize_sql(sql)
+ @reflection.klass.send(:sanitize_sql, sql)
+ end
+
+ def set_belongs_to_association_for(record)
+ if @reflection.options[:as]
+ record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
+ record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
+ else
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
+ end
+ end
+
+ def merge_options_from_reflection!(options)
+ options.reverse_merge!(
+ :group => @reflection.options[:group],
+ :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
+
+ def with_scope(*args, &block)
+ @reflection.klass.send :with_scope, *args, &block
+ end
+
+ private
+ def method_missing(method, *args)
+ if load_target
+ if block_given?
+ @target.send(method, *args) { |*block_args| yield(*block_args) }
+ else
+ @target.send(method, *args)
+ end
+ end
+ end
+
+ # Loads the target if needed and returns it.
+ #
+ # This method is abstract in the sense that it relies on +find_target+,
+ # which is expected to be provided by descendants.
+ #
+ # If the target is already loaded it is just returned. Thus, you can call
+ # +load_target+ unconditionally to get the target.
+ #
+ # 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? and (!@owner.new_record? || foreign_key_present)
+ @target = find_target
+ end
+
+ @loaded = true
+ @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.
+ def foreign_key_present
+ false
+ end
+
+ def raise_on_type_mismatch(record)
+ unless record.is_a?(@reflection.klass)
+ message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
+ raise ActiveRecord::AssociationTypeMismatch, message
+ end
+ end
+
+ # Array#flatten has problems with recursive arrays. Going one level deeper solves the majority of the problems.
+ def flatten_deeper(array)
+ array.collect { |element| element.respond_to?(:flatten) ? element.flatten : element }.flatten
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb
new file mode 100644
index 0000000..7c28cbd
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -0,0 +1,58 @@
+module ActiveRecord
+ module Associations
+ class BelongsToAssociation < AssociationProxy #:nodoc:
+ def create(attributes = {})
+ replace(@reflection.klass.create(attributes))
+ end
+
+ def build(attributes = {})
+ replace(@reflection.klass.new(attributes))
+ end
+
+ def replace(record)
+ counter_cache_name = @reflection.counter_cache_column
+
+ if record.nil?
+ if counter_cache_name && !@owner.new_record?
+ @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) 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.new_record?
+ @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 unless record.new_record?
+ @updated = true
+ end
+
+ loaded
+ record
+ end
+
+ def updated?
+ @updated
+ end
+
+ private
+ def find_target
+ @reflection.klass.find(
+ @owner[@reflection.primary_key_name],
+ :select => @reflection.options[:select],
+ :conditions => conditions,
+ :include => @reflection.options[:include],
+ :readonly => @reflection.options[:readonly]
+ )
+ end
+
+ def foreign_key_present
+ !@owner[@reflection.primary_key_name].nil?
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
new file mode 100644
index 0000000..d8146da
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
@@ -0,0 +1,49 @@
+module ActiveRecord
+ 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
+ @owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
+
+ @updated = true
+ end
+
+ loaded
+ record
+ end
+
+ def updated?
+ @updated
+ end
+
+ private
+ def find_target
+ return nil if association_class.nil?
+
+ if @reflection.options[:conditions]
+ association_class.find(
+ @owner[@reflection.primary_key_name],
+ :select => @reflection.options[:select],
+ :conditions => conditions,
+ :include => @reflection.options[:include]
+ )
+ else
+ association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
+ end
+ end
+
+ def foreign_key_present
+ !@owner[@reflection.primary_key_name].nil?
+ end
+
+ def association_class
+ @owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
new file mode 100644
index 0000000..4fa8e9d
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -0,0 +1,112 @@
+module ActiveRecord
+ module Associations
+ class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
+ def create(attributes = {})
+ create_record(attributes) { |record| insert_record(record) }
+ end
+
+ def create!(attributes = {})
+ create_record(attributes) { |record| insert_record(record, true) }
+ end
+
+ protected
+ def construct_find_options!(options)
+ options[:joins] = @join_sql
+ options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
+ options[:select] ||= (@reflection.options[:select] || '*')
+ end
+
+ def count_records
+ load_target.size
+ end
+
+ def insert_record(record, force=true)
+ if record.new_record?
+ if force
+ record.save!
+ else
+ return false unless record.save
+ end
+ end
+
+ if @reflection.options[:insert_sql]
+ @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
+ else
+ columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
+
+ attributes = columns.inject({}) do |attrs, column|
+ case column.name.to_s
+ when @reflection.primary_key_name.to_s
+ attrs[column.name] = @owner.quoted_id
+ when @reflection.association_foreign_key.to_s
+ attrs[column.name] = record.quoted_id
+ else
+ if record.has_attribute?(column.name)
+ value = @owner.send(:quote_value, record[column.name], column)
+ attrs[column.name] = value unless value.nil?
+ end
+ end
+ attrs
+ end
+
+ sql =
+ "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
+ "VALUES (#{attributes.values.join(', ')})"
+
+ @owner.connection.insert(sql)
+ end
+
+ return true
+ end
+
+ def delete_records(records)
+ if sql = @reflection.options[:delete_sql]
+ records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
+ else
+ ids = quoted_record_ids(records)
+ sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
+ @owner.connection.delete(sql)
+ end
+ end
+
+ def construct_sql
+ interpolate_sql_options!(@reflection.options, :finder_sql)
+
+ if @reflection.options[:finder_sql]
+ @finder_sql = @reflection.options[:finder_sql]
+ else
+ @finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
+ @finder_sql << " AND (#{conditions})" if conditions
+ end
+
+ @join_sql = "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
+
+ def construct_scope
+ { :find => { :conditions => @finder_sql,
+ :joins => @join_sql,
+ :readonly => false,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit] } }
+ end
+
+ # Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
+ # clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
+ # an id column. This will then overwrite the id column of the records coming back.
+ def finding_with_ambiguous_select?(select_clause)
+ !select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
+ end
+
+ private
+ def create_record(attributes, &block)
+ # Can't use Base.create because the foreign key may be a protected attribute.
+ ensure_owner_is_not_new
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr) }
+ else
+ build_record(attributes, &block)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb
new file mode 100644
index 0000000..f584a97
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_association.rb
@@ -0,0 +1,109 @@
+module ActiveRecord
+ module Associations
+ class HasManyAssociation < AssociationCollection #:nodoc:
+ # Count the number of associated records. All arguments are optional.
+ def count(*args)
+ if @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
+ elsif @reflection.options[:finder_sql]
+ @reflection.klass.count_by_sql(@finder_sql)
+ else
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
+ options[:conditions] = options[:conditions].blank? ?
+ @finder_sql :
+ @finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
+ options[:include] ||= @reflection.options[:include]
+
+ @reflection.klass.count(column_name, options)
+ end
+ end
+
+ protected
+ def count_records
+ count = if has_cached_counter?
+ @owner.send(:read_attribute, cached_counter_attribute_name)
+ elsif @reflection.options[:counter_sql]
+ @reflection.klass.count_by_sql(@counter_sql)
+ else
+ @reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
+ end
+
+ @target = [] and loaded if count == 0
+
+ if @reflection.options[:limit]
+ count = [ @reflection.options[:limit], count ].min
+ end
+
+ return count
+ end
+
+ def has_cached_counter?
+ @owner.attribute_present?(cached_counter_attribute_name)
+ end
+
+ def cached_counter_attribute_name
+ "#{@reflection.name}_count"
+ end
+
+ def insert_record(record)
+ set_belongs_to_association_for(record)
+ record.save
+ end
+
+ def delete_records(records)
+ case @reflection.options[:dependent]
+ when :destroy
+ records.each(&:destroy)
+ when :delete_all
+ @reflection.klass.delete(records.map(&:id))
+ else
+ ids = quoted_record_ids(records)
+ @reflection.klass.update_all(
+ "#{@reflection.primary_key_name} = NULL",
+ "#{@reflection.primary_key_name} = #{@owner.quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
+ )
+ end
+ end
+
+ def target_obsolete?
+ false
+ end
+
+ def construct_sql
+ case
+ when @reflection.options[:finder_sql]
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
+
+ when @reflection.options[:as]
+ @finder_sql =
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
+ @finder_sql << " AND (#{conditions})" if conditions
+
+ else
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql << " AND (#{conditions})" if conditions
+ end
+
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ else
+ @counter_sql = @finder_sql
+ end
+ end
+
+ def construct_scope
+ create_scoping = {}
+ set_belongs_to_association_for(create_scoping)
+ {
+ :find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
+ :create => create_scoping
+ }
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb
new file mode 100644
index 0000000..52ced36
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -0,0 +1,254 @@
+module ActiveRecord
+ module Associations
+ class HasManyThroughAssociation < HasManyAssociation #:nodoc:
+ def initialize(owner, reflection)
+ reflection.check_validity!
+ super
+ end
+
+ alias_method :new, :build
+
+ def create!(attrs = nil)
+ @reflection.klass.transaction do
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
+ object
+ end
+ end
+
+ def create(attrs = nil)
+ @reflection.klass.transaction do
+ self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
+ object
+ end
+ end
+
+ # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
+ # calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
+ # and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
+ def size
+ return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
+ return @target.size if loaded?
+ return count
+ end
+
+ def count(*args)
+ column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
+ if @reflection.options[:uniq]
+ # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
+ column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
+ options.merge!(:distinct => true)
+ end
+ @reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
+ end
+
+ protected
+ def construct_find_options!(options)
+ options[:select] = construct_select(options[:select])
+ options[:from] ||= construct_from
+ options[:joins] = construct_joins(options[:joins])
+ options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
+ end
+
+ def insert_record(record, force=true)
+ if record.new_record?
+ if force
+ record.save!
+ else
+ return false unless record.save
+ end
+ end
+ klass = @reflection.through_reflection.klass
+ @owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
+ end
+
+ # TODO - add dependent option support
+ def delete_records(records)
+ klass = @reflection.through_reflection.klass
+ records.each do |associate|
+ klass.delete_all(construct_join_attributes(associate))
+ end
+ end
+
+ def find_target
+ @reflection.klass.find(:all,
+ :select => construct_select,
+ :conditions => construct_conditions,
+ :from => construct_from,
+ :joins => construct_joins,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :group => @reflection.options[:group],
+ :readonly => @reflection.options[:readonly],
+ :include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
+ )
+ end
+
+ # Construct attributes for associate pointing to owner.
+ def construct_owner_attributes(reflection)
+ if as = reflection.options[:as]
+ { "#{as}_id" => @owner.id,
+ "#{as}_type" => @owner.class.base_class.name.to_s }
+ else
+ { reflection.primary_key_name => @owner.id }
+ end
+ end
+
+ # Construct attributes for :through pointing to owner and associate.
+ def construct_join_attributes(associate)
+ # TODO: revist this to allow it for deletion, supposing dependent option is supported
+ raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
+ join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
+ if @reflection.options[:source_type]
+ join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
+ end
+ join_attributes
+ end
+
+ # Associate attributes pointing to owner, quoted.
+ def construct_quoted_owner_attributes(reflection)
+ if as = reflection.options[:as]
+ { "#{as}_id" => @owner.quoted_id,
+ "#{as}_type" => reflection.klass.quote_value(
+ @owner.class.base_class.name.to_s,
+ reflection.klass.columns_hash["#{as}_type"]) }
+ else
+ { reflection.primary_key_name => @owner.quoted_id }
+ end
+ end
+
+ # Build SQL conditions from attributes, qualified by table name.
+ def construct_conditions
+ table_name = @reflection.through_reflection.quoted_table_name
+ conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
+ "#{table_name}.#{attr} = #{value}"
+ end
+ conditions << sql_conditions if sql_conditions
+ "(" + conditions.join(') AND (') + ")"
+ end
+
+ def construct_from
+ @reflection.quoted_table_name
+ end
+
+ def construct_select(custom_select = nil)
+ distinct = "DISTINCT " if @reflection.options[:uniq]
+ selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
+ end
+
+ def construct_joins(custom_joins = nil)
+ polymorphic_join = nil
+ if @reflection.source_reflection.macro == :belongs_to
+ reflection_primary_key = @reflection.klass.primary_key
+ source_primary_key = @reflection.source_reflection.primary_key_name
+ if @reflection.options[:source_type]
+ polymorphic_join = "AND %s.%s = %s" % [
+ @reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
+ @owner.class.quote_value(@reflection.options[:source_type])
+ ]
+ end
+ else
+ reflection_primary_key = @reflection.source_reflection.primary_key_name
+ source_primary_key = @reflection.klass.primary_key
+ if @reflection.source_reflection.options[:as]
+ polymorphic_join = "AND %s.%s = %s" % [
+ @reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
+ @owner.class.quote_value(@reflection.through_reflection.klass.name)
+ ]
+ end
+ end
+
+ "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
+ @reflection.through_reflection.table_name,
+ @reflection.table_name, reflection_primary_key,
+ @reflection.through_reflection.table_name, source_primary_key,
+ polymorphic_join
+ ]
+ end
+
+ def construct_scope
+ { :create => construct_owner_attributes(@reflection),
+ :find => { :from => construct_from,
+ :conditions => construct_conditions,
+ :joins => construct_joins,
+ :include => @reflection.options[:include],
+ :select => construct_select,
+ :order => @reflection.options[:order],
+ :limit => @reflection.options[:limit],
+ :readonly => @reflection.options[:readonly],
+ } }
+ end
+
+ def construct_sql
+ case
+ when @reflection.options[:finder_sql]
+ @finder_sql = interpolate_sql(@reflection.options[:finder_sql])
+
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ @finder_sql << " AND (#{conditions})" if conditions
+ else
+ @finder_sql = construct_conditions
+ end
+
+ if @reflection.options[:counter_sql]
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ elsif @reflection.options[:finder_sql]
+ # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
+ @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
+ @counter_sql = interpolate_sql(@reflection.options[:counter_sql])
+ else
+ @counter_sql = @finder_sql
+ end
+ end
+
+ def conditions
+ @conditions = build_conditions unless defined?(@conditions)
+ @conditions
+ 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
+ all = []
+
+ [association_conditions, source_conditions].each do |conditions|
+ all << interpolate_sql(sanitize_sql(conditions)) if conditions
+ end
+
+ all << through_conditions if through_conditions
+ all << build_sti_condition if uses_sti
+
+ all.map { |sql| "(#{sql})" } * ' AND '
+ end
+ end
+
+ def build_through_conditions
+ conditions = @reflection.through_reflection.options[:conditions]
+ if conditions.is_a?(Hash)
+ interpolate_sql(sanitize_sql(conditions)).gsub(
+ @reflection.quoted_table_name,
+ @reflection.through_reflection.quoted_table_name)
+ elsif conditions
+ interpolate_sql(sanitize_sql(conditions))
+ end
+ end
+
+ def build_sti_condition
+ "#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
+ end
+
+ alias_method :sql_conditions, :conditions
+
+ def has_cached_counter?
+ @owner.attribute_present?(cached_counter_attribute_name)
+ end
+
+ def cached_counter_attribute_name
+ "#{@reflection.name}_count"
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb
new file mode 100644
index 0000000..c2b3503
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/has_one_association.rb
@@ -0,0 +1,98 @@
+module ActiveRecord
+ module Associations
+ class HasOneAssociation < BelongsToAssociation #:nodoc:
+ def initialize(owner, reflection)
+ super
+ construct_sql
+ end
+
+ def create(attrs = {}, replace_existing = true)
+ new_record(replace_existing) { |klass| klass.create(attrs) }
+ end
+
+ def create!(attrs = {}, replace_existing = true)
+ new_record(replace_existing) { |klass| klass.create!(attrs) }
+ end
+
+ def build(attrs = {}, replace_existing = true)
+ new_record(replace_existing) { |klass| klass.new(attrs) }
+ end
+
+ def replace(obj, dont_save = false)
+ load_target
+
+ unless @target.nil?
+ if dependent? && !dont_save && @target != obj
+ @target.destroy unless @target.new_record?
+ @owner.clear_association_cache
+ else
+ @target[@reflection.primary_key_name] = nil
+ @target.save unless @owner.new_record? || @target.new_record?
+ end
+ end
+
+ if obj.nil?
+ @target = nil
+ else
+ raise_on_type_mismatch(obj)
+ set_belongs_to_association_for(obj)
+ @target = (AssociationProxy === obj ? obj.target : obj)
+ end
+
+ @loaded = true
+
+ unless @owner.new_record? or obj.nil? or dont_save
+ return (obj.save ? self : false)
+ else
+ return (obj.nil? ? nil : self)
+ end
+ end
+
+ private
+ def find_target
+ @reflection.klass.find(:first,
+ :conditions => @finder_sql,
+ :select => @reflection.options[:select],
+ :order => @reflection.options[:order],
+ :include => @reflection.options[:include],
+ :readonly => @reflection.options[:readonly]
+ )
+ end
+
+ def construct_sql
+ case
+ when @reflection.options[:as]
+ @finder_sql =
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
+ "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
+ else
+ @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
+ end
+ @finder_sql << " AND (#{conditions})" if conditions
+ end
+
+ def construct_scope
+ create_scoping = {}
+ set_belongs_to_association_for(create_scoping)
+ { :create => create_scoping }
+ 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 => construct_scope[:create]) { yield @reflection.klass }
+
+ if replace_existing
+ replace(record, true)
+ else
+ record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
+ self.target = record
+ end
+
+ record
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb b/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb
new file mode 100644
index 0000000..c846956
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -0,0 +1,28 @@
+module ActiveRecord
+ module Associations
+ class HasOneThroughAssociation < HasManyThroughAssociation
+
+ def create_through_record(new_value) #nodoc:
+ klass = @reflection.through_reflection.klass
+
+ current_object = @owner.send(@reflection.through_reflection.name)
+
+ if current_object
+ klass.destroy(current_object)
+ @owner.clear_association_cache
+ end
+
+ @owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
+ end
+
+ private
+ def find(*args)
+ super(args.merge(:limit => 1))
+ end
+
+ def find_target
+ super.first
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/attribute_methods.rb b/vendor/rails/activerecord/lib/active_record/attribute_methods.rb
new file mode 100644
index 0000000..fab16a4
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/attribute_methods.rb
@@ -0,0 +1,379 @@
+module ActiveRecord
+ module AttributeMethods #:nodoc:
+ DEFAULT_SUFFIXES = %w(= ? _before_type_cast)
+ ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+
+ def self.included(base)
+ base.extend ClassMethods
+ base.attribute_method_suffix(*DEFAULT_SUFFIXES)
+ base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
+ base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
+ base.time_zone_aware_attributes = false
+ base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
+ base.skip_time_zone_conversion_for_attributes = []
+ end
+
+ # Declare and check for suffixed attribute methods.
+ module ClassMethods
+ # Declares a method available for all attributes with the given suffix.
+ # Uses +method_missing+ and respond_to? to rewrite the method
+ #
+ # #{attr}#{suffix}(*args, &block)
+ #
+ # to
+ #
+ # attribute#{suffix}(#{attr}, *args, &block)
+ #
+ # An attribute#{suffix} instance method must exist and accept at least
+ # the +attr+ argument.
+ #
+ # For example:
+ #
+ # class Person < ActiveRecord::Base
+ # attribute_method_suffix '_changed?'
+ #
+ # private
+ # def attribute_changed?(attr)
+ # ...
+ # end
+ # end
+ #
+ # person = Person.find(1)
+ # person.name_changed? # => false
+ # person.name = 'Hubert'
+ # person.name_changed? # => true
+ def attribute_method_suffix(*suffixes)
+ attribute_method_suffixes.concat suffixes
+ rebuild_attribute_method_regexp
+ end
+
+ # Returns MatchData if method_name is an attribute method.
+ def match_attribute_method?(method_name)
+ rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp
+ @@attribute_method_regexp.match(method_name)
+ end
+
+
+ # Contains the names of the generated attribute methods.
+ def generated_methods #:nodoc:
+ @generated_methods ||= Set.new
+ end
+
+ def generated_methods?
+ !generated_methods.empty?
+ end
+
+ # Generates all the attribute related methods for columns in the database
+ # accessors, mutators and query methods.
+ def define_attribute_methods
+ return if generated_methods?
+ columns_hash.each do |name, column|
+ unless instance_method_already_implemented?(name)
+ if self.serialized_attributes[name]
+ define_read_method_for_serialized_attribute(name)
+ elsif create_time_zone_conversion_attribute?(name, column)
+ define_read_method_for_time_zone_conversion(name)
+ else
+ define_read_method(name.to_sym, name, column)
+ end
+ end
+
+ unless instance_method_already_implemented?("#{name}=")
+ if create_time_zone_conversion_attribute?(name, column)
+ define_write_method_for_time_zone_conversion(name)
+ else
+ define_write_method(name.to_sym)
+ end
+ end
+
+ unless instance_method_already_implemented?("#{name}?")
+ define_question_method(name)
+ end
+ end
+ end
+
+ # Checks whether the method is defined in the model or any of its subclasses
+ # that also derive from Active Record. Raises DangerousAttributeError if the
+ # method is defined by Active Record though.
+ def instance_method_already_implemented?(method_name)
+ method_name = method_name.to_s
+ return true if method_name =~ /^id(=$|\?$|$)/
+ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set
+ @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map(&:to_s).to_set
+ raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name)
+ @_defined_class_methods.include?(method_name)
+ end
+
+ alias :define_read_methods :define_attribute_methods
+
+ # +cache_attributes+ allows you to declare which converted attribute values should
+ # 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}
+ end
+
+ # Returns the attributes which are cached. By default time related columns
+ # with datatype :datetime, :timestamp, :time, :date are cached.
+ def cached_attributes
+ @cached_attributes ||=
+ columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
+ end
+
+ # Returns +true+ if the provided attribute is being cached.
+ def cache_attribute?(attr_name)
+ cached_attributes.include?(attr_name)
+ end
+
+ private
+ # Suffixes a, ?, c become regexp /(a|\?|c)$/
+ def rebuild_attribute_method_regexp
+ suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) }
+ @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze
+ end
+
+ # Default to =, ?, _before_type_cast
+ def attribute_method_suffixes
+ @@attribute_method_suffixes ||= []
+ end
+
+ def create_time_zone_conversion_attribute?(name, column)
+ time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
+ end
+
+ # Define an attribute reader method. Cope with nil column.
+ def define_read_method(symbol, attr_name, column)
+ cast_code = column.type_cast_code('v') if column
+ 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}'); ")
+ end
+
+ if cache_attribute?(attr_name)
+ access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
+ end
+ evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end"
+ end
+
+ # Define read method for serialized attribute.
+ def define_read_method_for_serialized_attribute(attr_name)
+ evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
+ end
+
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
+ def define_read_method_for_time_zone_conversion(attr_name)
+ method_body = <<-EOV
+ def #{attr_name}(reload = false)
+ cached = @attributes_cache['#{attr_name}']
+ return cached if cached && !reload
+ time = read_attribute('#{attr_name}')
+ @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
+ end
+ EOV
+ evaluate_attribute_method attr_name, method_body
+ end
+
+ # Defines a predicate method attr_name?.
+ def define_question_method(attr_name)
+ evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
+ end
+
+ def define_write_method(attr_name)
+ evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
+ end
+
+ # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
+ # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
+ def define_write_method_for_time_zone_conversion(attr_name)
+ method_body = <<-EOV
+ def #{attr_name}=(time)
+ unless time.acts_like?(:time)
+ time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
+ end
+ time = time.in_time_zone rescue nil if time
+ write_attribute(:#{attr_name}, time)
+ end
+ EOV
+ evaluate_attribute_method attr_name, method_body, "#{attr_name}="
+ end
+
+ # Evaluate the definition for an attribute related method
+ def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
+
+ unless method_name.to_s == primary_key.to_s
+ generated_methods << method_name
+ end
+
+ begin
+ class_eval(method_definition, __FILE__, __LINE__)
+ rescue SyntaxError => err
+ generated_methods.delete(attr_name)
+ if logger
+ logger.warn "Exception occurred during reader method compilation."
+ logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
+ logger.warn "#{err.message}"
+ end
+ end
+ end
+ end # ClassMethods
+
+
+ # Allows access to the object attributes, which are held in the @attributes hash, as though they
+ # were first-class methods. So a Person class with a name attribute can use Person#name and
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
+ # the completed attribute is not +nil+ or 0.
+ #
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
+ # table with a +master_id+ foreign key can instantiate master through Client#master.
+ def method_missing(method_id, *args, &block)
+ method_name = method_id.to_s
+
+ # If we haven't generated any methods yet, generate them, then
+ # see if we've created the method we're looking for.
+ if !self.class.generated_methods?
+ self.class.define_attribute_methods
+ if self.class.generated_methods.include?(method_name)
+ return self.send(method_id, *args, &block)
+ end
+ end
+
+ if self.class.primary_key.to_s == method_name
+ id
+ elsif md = self.class.match_attribute_method?(method_name)
+ attribute_name, method_type = md.pre_match, md.to_s
+ if @attributes.include?(attribute_name)
+ __send__("attribute#{method_type}", attribute_name, *args, &block)
+ else
+ super
+ end
+ elsif @attributes.include?(method_name)
+ read_attribute(method_name)
+ else
+ super
+ end
+ end
+
+ # Returns the value of the attribute identified by attr_name after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ def read_attribute(attr_name)
+ attr_name = attr_name.to_s
+ if !(value = @attributes[attr_name]).nil?
+ if column = column_for_attribute(attr_name)
+ if unserializable_attribute?(attr_name, column)
+ unserialize_attribute(attr_name)
+ else
+ column.type_cast(value)
+ end
+ else
+ value
+ end
+ else
+ nil
+ end
+ end
+
+ def read_attribute_before_type_cast(attr_name)
+ @attributes[attr_name]
+ end
+
+ # Returns true if the attribute is of a text column and marked for serialization.
+ def unserializable_attribute?(attr_name, column)
+ column.text? && self.class.serialized_attributes[attr_name]
+ end
+
+ # Returns the unserialized object of the attribute.
+ def unserialize_attribute(attr_name)
+ unserialized_object = object_from_yaml(@attributes[attr_name])
+
+ if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil?
+ @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object
+ else
+ raise SerializationTypeMismatch,
+ "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
+ end
+ end
+
+
+ # Updates the attribute identified by attr_name with the specified +value+. Empty strings for fixnum and float
+ # columns are turned into +nil+.
+ def write_attribute(attr_name, value)
+ attr_name = attr_name.to_s
+ @attributes_cache.delete(attr_name)
+ if (column = column_for_attribute(attr_name)) && column.number?
+ @attributes[attr_name] = convert_number_column_value(value)
+ else
+ @attributes[attr_name] = value
+ end
+ end
+
+
+ def query_attribute(attr_name)
+ unless value = read_attribute(attr_name)
+ false
+ else
+ column = self.class.columns_hash[attr_name]
+ if column.nil?
+ if Numeric === value || value !~ /[^0-9]/
+ !value.to_i.zero?
+ else
+ !value.blank?
+ end
+ elsif column.number?
+ !value.zero?
+ else
+ !value.blank?
+ end
+ end
+ end
+
+ # A Person object with a name attribute can ask person.respond_to?("name"),
+ # person.respond_to?("name="), and person.respond_to?("name?")
+ # which will all return +true+.
+ alias :respond_to_without_attributes? :respond_to?
+ def respond_to?(method, include_priv = false)
+ method_name = method.to_s
+ if super
+ return true
+ elsif !self.class.generated_methods?
+ self.class.define_attribute_methods
+ if self.class.generated_methods.include?(method_name)
+ return true
+ end
+ end
+
+ if @attributes.nil?
+ return super
+ elsif @attributes.include?(method_name)
+ return true
+ elsif md = self.class.match_attribute_method?(method_name)
+ return true if @attributes.include?(md.pre_match)
+ end
+ super
+ end
+
+ private
+
+ def missing_attribute(attr_name, stack)
+ raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack
+ end
+
+ # Handle *? for method_missing.
+ def attribute?(attribute_name)
+ query_attribute(attribute_name)
+ end
+
+ # Handle *= for method_missing.
+ def attribute=(attribute_name, value)
+ write_attribute(attribute_name, value)
+ end
+
+ # Handle *_before_type_cast for method_missing.
+ def attribute_before_type_cast(attribute_name)
+ read_attribute_before_type_cast(attribute_name)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/base.rb b/vendor/rails/activerecord/lib/active_record/base.rb
new file mode 100644
index 0000000..92a24ec
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/base.rb
@@ -0,0 +1,2726 @@
+require 'yaml'
+require 'set'
+
+module ActiveRecord #:nodoc:
+ # Generic Active Record exception class.
+ class ActiveRecordError < StandardError
+ end
+
+ # Raised when the single-table inheritance mechanism failes to locate the subclass
+ # (for example due to improper usage of column that +inheritance_column+ points to).
+ class SubclassNotFound < ActiveRecordError #:nodoc:
+ end
+
+ # Raised when an object assigned to an association has an incorrect type.
+ #
+ # class Ticket < ActiveRecord::Base
+ # has_many :patches
+ # end
+ #
+ # class Patch < ActiveRecord::Base
+ # belongs_to :ticket
+ # end
+ #
+ # # Comments are not patches, this assignment raises AssociationTypeMismatch.
+ # @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
+ class AssociationTypeMismatch < ActiveRecordError
+ end
+
+ # Raised when unserialized object's type mismatches one specified for serializable field.
+ class SerializationTypeMismatch < ActiveRecordError
+ end
+
+ # Raised when adapter not specified on connection (or configuration file config/database.yml misses adapter field).
+ class AdapterNotSpecified < ActiveRecordError
+ end
+
+ # Raised when Active Record cannot find database adapter specified in config/database.yml or programmatically.
+ class AdapterNotFound < ActiveRecordError
+ end
+
+ # Raised when connection to the database could not been established (for example when connection= is given a nil object).
+ class ConnectionNotEstablished < ActiveRecordError
+ end
+
+ # Raised when Active Record cannot find record by given id or set of ids.
+ class RecordNotFound < ActiveRecordError
+ end
+
+ # Raised by ActiveRecord::Base.save! and ActiveRecord::Base.create! methods when record cannot be
+ # saved because record is invalid.
+ class RecordNotSaved < ActiveRecordError
+ end
+
+ # Raised when SQL statement cannot be executed by the database (for example, it's often the case for MySQL when Ruby driver used is too old).
+ class StatementInvalid < ActiveRecordError
+ end
+
+ # Raised when number of bind variables in statement given to :condition key (for example, when using +find+ method)
+ # does not match number of expected variables.
+ #
+ # For example, in
+ #
+ # Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
+ #
+ # two placeholders are given but only one variable to fill them.
+ class PreparedStatementInvalid < ActiveRecordError
+ end
+
+ # Raised on attempt to save stale record. Record is stale when it's being saved in another query after
+ # instantiation, for example, when two users edit the same wiki page and one starts editing and saves
+ # the page before the other.
+ #
+ # Read more about optimistic locking in ActiveRecord::Locking module RDoc.
+ class StaleObjectError < ActiveRecordError
+ end
+
+ # Raised when association is being configured improperly or
+ # user tries to use offset and limit together with has_many or has_and_belongs_to_many associations.
+ class ConfigurationError < ActiveRecordError
+ end
+
+ # Raised on attempt to update record that is instantiated as read only.
+ class ReadOnlyRecord < ActiveRecordError
+ end
+
+ # Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
+ # You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
+ class Rollback < ActiveRecordError
+ end
+
+ # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
+ class DangerousAttributeError < ActiveRecordError
+ end
+
+ # Raised when you've tried to access a column which wasn't loaded by your finder.
+ # Typically this is because :select has been specified.
+ class MissingAttributeError < NoMethodError
+ end
+
+ # Raised when an error occured while doing a mass assignment to an attribute through the
+ # attributes= method. The exception has an +attribute+ property that is the name of the
+ # offending attribute.
+ class AttributeAssignmentError < ActiveRecordError
+ attr_reader :exception, :attribute
+ def initialize(message, exception, attribute)
+ @exception = exception
+ @attribute = attribute
+ @message = message
+ end
+ end
+
+ # Raised when there are multiple errors while doing a mass assignment through the +attributes+
+ # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
+ # objects, each corresponding to the error while assigning to an attribute.
+ class MultiparameterAssignmentErrors < ActiveRecordError
+ attr_reader :errors
+ def initialize(errors)
+ @errors = errors
+ end
+ end
+
+ # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
+ # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
+ # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
+ # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
+ #
+ # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
+ #
+ # == Creation
+ #
+ # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
+ # you're receiving the data from somewhere else, like an HTTP request. It works like this:
+ #
+ # user = User.new(:name => "David", :occupation => "Code Artist")
+ # user.name # => "David"
+ #
+ # You can also use block initialization:
+ #
+ # user = User.new do |u|
+ # u.name = "David"
+ # u.occupation = "Code Artist"
+ # end
+ #
+ # And of course you can just create a bare object and specify the attributes after the fact:
+ #
+ # user = User.new
+ # user.name = "David"
+ # user.occupation = "Code Artist"
+ #
+ # == Conditions
+ #
+ # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
+ # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
+ # be used for statements that don't involve tainted data. The hash form works much like the array form, except
+ # only equality and range is possible. Examples:
+ #
+ # class User < ActiveRecord::Base
+ # def self.authenticate_unsafely(user_name, password)
+ # find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
+ # end
+ #
+ # def self.authenticate_safely(user_name, password)
+ # find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
+ # end
+ #
+ # def self.authenticate_safely_simply(user_name, password)
+ # find(:first, :conditions => { :user_name => user_name, :password => password })
+ # end
+ # end
+ #
+ # The authenticate_unsafely method inserts the parameters directly into the query and is thus susceptible to SQL-injection
+ # attacks if the user_name and +password+ parameters come directly from an HTTP request. The authenticate_safely and
+ # authenticate_safely_simply both will sanitize the user_name and +password+ before inserting them in the query,
+ # which will ensure that an attacker can't escape the query and fake the login (or worse).
+ #
+ # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
+ # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
+ # the question marks with symbols and supplying a hash with values for the matching symbol keys:
+ #
+ # Company.find(:first, :conditions => [
+ # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
+ # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
+ # ])
+ #
+ # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
+ # operator. For instance:
+ #
+ # Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 })
+ # Student.find(:all, :conditions => params[:student])
+ #
+ # A range may be used in the hash to use the SQL BETWEEN operator:
+ #
+ # Student.find(:all, :conditions => { :grade => 9..12 })
+ #
+ # An array may be used in the hash to use the SQL IN operator:
+ #
+ # Student.find(:all, :conditions => { :grade => [9,11,12] })
+ #
+ # == Overwriting default accessors
+ #
+ # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
+ # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
+ # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
+ # Example:
+ #
+ # class Song < ActiveRecord::Base
+ # # Uses an integer of seconds to hold the length of the song
+ #
+ # def length=(minutes)
+ # write_attribute(:length, minutes.to_i * 60)
+ # end
+ #
+ # def length
+ # read_attribute(:length) / 60
+ # end
+ # end
+ #
+ # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
+ # read_attribute(:attribute) as a shorter form.
+ #
+ # == Attribute query methods
+ #
+ # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
+ # Query methods allow you to test whether an attribute value is present.
+ #
+ # For example, an Active Record User with the name attribute has a name? method that you can call
+ # to determine whether the user has a name:
+ #
+ # user = User.new(:name => "David")
+ # user.name? # => true
+ #
+ # anonymous = User.new(:name => "")
+ # anonymous.name? # => false
+ #
+ # == Accessing attributes before they have been typecasted
+ #
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
+ # That can be done by using the _before_type_cast accessors that all attributes have. For example, if your Account model
+ # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
+ #
+ # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
+ # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
+ # want.
+ #
+ # == Dynamic attribute-based finders
+ #
+ # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
+ # appending the name of an attribute to find_by_ or find_all_by_, so you get finders like Person.find_by_user_name,
+ # Person.find_all_by_last_name, and Payment.find_by_transaction_id. So instead of writing
+ # Person.find(:first, :conditions => ["user_name = ?", user_name]), you just do Person.find_by_user_name(user_name).
+ # And instead of writing Person.find(:all, :conditions => ["last_name = ?", last_name]), you just do Person.find_all_by_last_name(last_name).
+ #
+ # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
+ # Person.find_by_user_name_and_password or even Payment.find_by_purchaser_and_state_and_country. So instead of writing
+ # Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password]), you just do
+ # Person.find_by_user_name_and_password(user_name, password).
+ #
+ # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
+ # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
+ # actually Person.find_by_user_name(user_name, options). So you could call Payment.find_all_by_amount(50, :order => "created_on").
+ #
+ # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
+ # find_or_create_by_ and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
+ #
+ # # No 'Summer' tag exists
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
+ #
+ # # Now the 'Summer' tag does exist
+ # Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
+ #
+ # # Now 'Bob' exist and is an 'admin'
+ # User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
+ #
+ # Use the find_or_initialize_by_ finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
+ #
+ # # No 'Winter' tag exists
+ # winter = Tag.find_or_initialize_by_name("Winter")
+ # winter.new_record? # true
+ #
+ # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
+ # a list of parameters. For example:
+ #
+ # Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
+ #
+ # That will either find an existing tag named "rails", or create a new one while setting the user that created it.
+ #
+ # == Saving arrays, hashes, and other non-mappable objects in text columns
+ #
+ # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
+ #
+ # class User < ActiveRecord::Base
+ # serialize :preferences
+ # end
+ #
+ # user = User.create(:preferences => { "background" => "black", "display" => large })
+ # User.find(user.id).preferences # => { "background" => "black", "display" => large }
+ #
+ # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
+ # descendent of a class not in the hierarchy. Example:
+ #
+ # class User < ActiveRecord::Base
+ # serialize :preferences, Hash
+ # end
+ #
+ # user = User.create(:preferences => %w( one two three ))
+ # User.find(user.id).preferences # raises SerializationTypeMismatch
+ #
+ # == Single table inheritance
+ #
+ # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed
+ # by overwriting Base.inheritance_column). This means that an inheritance looking like this:
+ #
+ # class Company < ActiveRecord::Base; end
+ # class Firm < Company; end
+ # class Client < Company; end
+ # class PriorityClient < Client; end
+ #
+ # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
+ # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
+ #
+ # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
+ # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
+ #
+ # Note, all the attributes for all the cases are kept in the same table. Read more:
+ # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
+ #
+ # == Connection to multiple databases in different models
+ #
+ # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
+ # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
+ # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
+ # and Course and all of its subclasses will use this connection instead.
+ #
+ # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
+ # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
+ #
+ # == Exceptions
+ #
+ # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
+ # * AdapterNotSpecified - The configuration hash used in establish_connection didn't include an
+ # :adapter key.
+ # * AdapterNotFound - The :adapter key used in establish_connection specified a non-existent adapter
+ # (or a bad spelling of an existing one).
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
+ # * ConnectionNotEstablished+ - No connection has been established. Use establish_connection before querying.
+ # * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
+ # or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
+ # nothing was found, please check its documentation for further details.
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
+ # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
+ # attributes= method. The +errors+ property of this exception contains an array of AttributeAssignmentError
+ # objects that should be inspected to determine which attributes triggered the errors.
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the attributes= method.
+ # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
+ #
+ # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
+ # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
+ # instances in the current object space.
+ class Base
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
+ # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
+ cattr_accessor :logger, :instance_writer => false
+
+ def self.inherited(child) #:nodoc:
+ @@subclasses[self] ||= []
+ @@subclasses[self] << child
+ super
+ end
+
+ def self.reset_subclasses #:nodoc:
+ nonreloadables = []
+ subclasses.each do |klass|
+ unless Dependencies.autoloaded? klass
+ nonreloadables << klass
+ next
+ end
+ klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
+ klass.instance_methods(false).each { |m| klass.send :undef_method, m }
+ end
+ @@subclasses = {}
+ nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
+ end
+
+ @@subclasses = {}
+
+ cattr_accessor :configurations, :instance_writer => false
+ @@configurations = {}
+
+ # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
+ # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
+ # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
+ # that this is a global setting for all Active Records.
+ cattr_accessor :primary_key_prefix_type, :instance_writer => false
+ @@primary_key_prefix_type = nil
+
+ # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
+ # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
+ # for tables in a shared database. By default, the prefix is the empty string.
+ cattr_accessor :table_name_prefix, :instance_writer => false
+ @@table_name_prefix = ""
+
+ # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
+ # "people_basecamp"). By default, the suffix is the empty string.
+ cattr_accessor :table_name_suffix, :instance_writer => false
+ @@table_name_suffix = ""
+
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
+ # If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
+ # See table_name for the full rules on table/class naming. This is true, by default.
+ cattr_accessor :pluralize_table_names, :instance_writer => false
+ @@pluralize_table_names = true
+
+ # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
+ # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
+ # may complicate matters if you use software like syslog. This is true, by default.
+ cattr_accessor :colorize_logging, :instance_writer => false
+ @@colorize_logging = true
+
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
+ # This is set to :local by default.
+ cattr_accessor :default_timezone, :instance_writer => false
+ @@default_timezone = :local
+
+ # Determines whether to use a connection for each thread, or a single shared connection for all threads.
+ # Defaults to false. If you're writing a threaded application, set to true
+ # and periodically call verify_active_connections! to clear out connections
+ # assigned to stale threads.
+ cattr_accessor :allow_concurrency, :instance_writer => false
+ @@allow_concurrency = false
+
+ # Specifies the format to use when dumping the database schema with Rails'
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ cattr_accessor :schema_format , :instance_writer => false
+ @@schema_format = :ruby
+
+ # Determine whether to store the full constant name including namespace when using STI
+ superclass_delegating_accessor :store_full_sti_class
+ self.store_full_sti_class = false
+
+ class << self # Class methods
+ # Find operates with four different retrieval approaches:
+ #
+ # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
+ # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
+ # * Find first - This will return the first record matched by the options used. These options can either be specific
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
+ # Model.find(:first, *args) or its shortcut Model.first(*args).
+ # * Find last - This will return the last record matched by the options used. These options can either be specific
+ # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
+ # Model.find(:last, *args) or its shortcut Model.last(*args).
+ # * Find all - This will return all the records matched by the options used.
+ # If no records are found, an empty array is returned. Use
+ # Model.find(:all, *args) or its shortcut Model.all(*args).
+ #
+ # All approaches accept an options hash as their last parameter.
+ #
+ # ==== Attributes
+ #
+ # * :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * :order - An SQL fragment like "created_at DESC, name".
+ # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # * :limit - An integer determining the limit on the number of rows that should be returned.
+ # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
+ # * :joins - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
+ # or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s).
+ # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # Pass :readonly => false to override.
+ # * :include - Names associations that should be loaded alongside. The symbols named refer
+ # to already defined associations. See eager loading under Associations.
+ # * :select - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
+ # include the joined columns.
+ # * :from - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
+ # of a database view).
+ # * :readonly - Mark the returned records read-only so they cannot be saved or updated.
+ # * :lock - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
+ # :lock => true gives connection's default exclusive lock, usually "FOR UPDATE".
+ #
+ # ==== Examples
+ #
+ # # find by id
+ # Person.find(1) # returns the object for ID = 1
+ # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
+ # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
+ # Person.find([1]) # returns an array for the object with ID = 1
+ # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
+ #
+ # Note that returned records may not be in the same order as the ids you
+ # provide since database rows are unordered. Give an explicit :order
+ # to ensure the results are sorted.
+ #
+ # ==== Examples
+ #
+ # # find first
+ # Person.find(:first) # returns the first object fetched by SELECT * FROM people
+ # Person.find(:first, :conditions => [ "user_name = ?", user_name])
+ # Person.find(:first, :order => "created_on DESC", :offset => 5)
+ #
+ # # find last
+ # Person.find(:last) # returns the last object fetched by SELECT * FROM people
+ # Person.find(:last, :conditions => [ "user_name = ?", user_name])
+ # Person.find(:last, :order => "created_on DESC", :offset => 5)
+ #
+ # # find all
+ # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
+ # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
+ # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
+ # Person.find(:all, :offset => 10, :limit => 10)
+ # Person.find(:all, :include => [ :account, :friends ])
+ # Person.find(:all, :group => "category")
+ #
+ # Example for find with a lock: Imagine two concurrent transactions:
+ # each will read person.visits == 2, add 1 to it, and save, resulting
+ # in two saves of person.visits = 3. By locking the row, the second
+ # transaction has to wait until the first is finished; we get the
+ # expected person.visits == 4.
+ #
+ # Person.transaction do
+ # person = Person.find(1, :lock => true)
+ # person.visits += 1
+ # person.save!
+ # end
+ def find(*args)
+ options = args.extract_options!
+ validate_find_options(options)
+ set_readonly_option!(options)
+
+ case args.first
+ when :first then find_initial(options)
+ when :last then find_last(options)
+ when :all then find_every(options)
+ else find_from_ids(args, options)
+ end
+ end
+
+ # A convenience wrapper for find(:first, *args). You can pass in all the
+ # same arguments to this method as you can to find(:first).
+ def first(*args)
+ find(:first, *args)
+ end
+
+ # A convenience wrapper for find(:last, *args). You can pass in all the
+ # same arguments to this method as you can to find(:last).
+ def last(*args)
+ find(:last, *args)
+ end
+
+ # This is an alias for find(:all). You can pass in all the same arguments to this method as you can
+ # to find(:all)
+ def all(*args)
+ find(:all, *args)
+ end
+
+ # Executes a custom SQL query against your database and returns all the results. The results will
+ # be returned as an array with columns requested encapsulated as attributes of the model you call
+ # this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
+ # object with the attributes you specified in the SQL query.
+ #
+ # If you call a complicated SQL query which spans multiple tables the columns specified by the
+ # SELECT will be attributes of the model, whether or not they are columns of the corresponding
+ # table.
+ #
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
+ # MySQL specific terms will lock you to using that particular database engine or require you to
+ # change your call if you switch engines
+ #
+ # ==== Examples
+ # # A simple SQL query spanning multiple tables
+ # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
+ # > [#"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
+ #
+ # # You can use the same string replacement techniques as you can with ActiveRecord#find
+ # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
+ # > [#"The Cheap Man Buys Twice"}>, ...]
+ def find_by_sql(sql)
+ connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
+ end
+
+ # Checks whether a record exists in the database that matches conditions given. These conditions
+ # can either be a single integer representing a primary key id to be found, or a condition to be
+ # matched like using ActiveRecord#find.
+ #
+ # The +id_or_conditions+ parameter can be an Integer or a String if you want to search the primary key
+ # column of the table for a matching id, or if you're looking to match against a condition you can use
+ # an Array or a Hash.
+ #
+ # Possible gotcha: You can't pass in a condition as a string e.g. "name = 'Jamie'", this would be
+ # sanitized and then queried against the primary key column as "id = 'name = \'Jamie"
+ #
+ # ==== Examples
+ # Person.exists?(5)
+ # Person.exists?('5')
+ # Person.exists?(:name => "David")
+ # Person.exists?(['name LIKE ?', "%#{query}%"])
+ def exists?(id_or_conditions)
+ connection.select_all(
+ construct_finder_sql(
+ :select => "#{quoted_table_name}.#{primary_key}",
+ :conditions => expand_id_conditions(id_or_conditions),
+ :limit => 1
+ ),
+ "#{name} Exists"
+ ).size > 0
+ end
+
+ # Creates an object (or multiple objects) and saves it to the database, if validations pass.
+ # The resulting object is returned whether the object was saved successfully to the database or not.
+ #
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
+ # attributes on the objects that are to be created.
+ #
+ # ==== Examples
+ # # Create a single new object
+ # User.create(:first_name => 'Jamie')
+ #
+ # # Create an Array of new objects
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
+ #
+ # # Create a single object and pass it into a block to set other attributes.
+ # User.create(:first_name => 'Jamie') do |u|
+ # u.is_admin = false
+ # end
+ #
+ # # Creating an Array of new objects using a block, where the block is executed for each object:
+ # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
+ # u.is_admin = false
+ # end
+ def create(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, &block) }
+ else
+ object = new(attributes)
+ yield(object) if block_given?
+ object.save
+ object
+ end
+ end
+
+ # Updates an object (or multiple objects) and saves it to the database, if validations pass.
+ # The resulting object is returned whether the object was saved successfully to the database or not.
+ #
+ # ==== Attributes
+ #
+ # * +id+ - This should be the id or an array of ids to be updated.
+ # * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
+ #
+ # ==== Examples
+ #
+ # # Updating one record:
+ # Person.update(15, { :user_name => 'Samuel', :group => 'expert' })
+ #
+ # # Updating multiple records:
+ # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
+ # Person.update(people.keys, people.values)
+ def update(id, attributes)
+ if id.is_a?(Array)
+ idx = -1
+ id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
+ else
+ object = find(id)
+ object.update_attributes(attributes)
+ object
+ end
+ end
+
+ # Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command
+ # is executed on the database which means that no callbacks are fired off running this. This is an efficient method
+ # of deleting records that don't need cleaning up after or other actions to be taken.
+ #
+ # Objects are _not_ instantiated with this method.
+ #
+ # ==== Attributes
+ #
+ # * +id+ - Can be either an Integer or an Array of Integers.
+ #
+ # ==== Examples
+ #
+ # # Delete a single object
+ # Todo.delete(1)
+ #
+ # # Delete multiple objects
+ # todos = [1,2,3]
+ # Todo.delete(todos)
+ def delete(id)
+ delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
+ end
+
+ # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
+ # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
+ #
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
+ # from the attributes, and then calls destroy on it.
+ #
+ # ==== Attributes
+ #
+ # * +id+ - Can be either an Integer or an Array of Integers.
+ #
+ # ==== Examples
+ #
+ # # Destroy a single object
+ # Todo.destroy(1)
+ #
+ # # Destroy multiple objects
+ # todos = [1,2,3]
+ # Todo.destroy(todos)
+ def destroy(id)
+ if id.is_a?(Array)
+ id.map { |one_id| destroy(one_id) }
+ else
+ find(id).destroy
+ end
+ end
+
+ # Updates all records with details given if they match a set of conditions supplied, limits and order can
+ # also be supplied.
+ #
+ # ==== Attributes
+ #
+ # * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
+ # * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
+ # See conditions in the intro for more info.
+ # * +options+ - Additional options are :limit and/or :order, see the examples for usage.
+ #
+ # ==== Examples
+ #
+ # # Update all billing objects with the 3 different attributes given
+ # Billing.update_all( "category = 'authorized', approved = 1, author = 'David'" )
+ #
+ # # Update records that match our conditions
+ # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'" )
+ #
+ # # Update records that match our conditions but limit it to 5 ordered by date
+ # Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
+ # :order => 'created_at', :limit => 5 )
+ def update_all(updates, conditions = nil, options = {})
+ sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
+ scope = scope(:find)
+ add_conditions!(sql, conditions, scope)
+ add_order!(sql, options[:order], nil)
+ add_limit!(sql, options, nil)
+ connection.update(sql, "#{name} Update")
+ end
+
+ # Destroys the records matching +conditions+ by instantiating each record and calling the destroy method.
+ # This means at least 2*N database queries to destroy N records, so avoid destroy_all if you are deleting
+ # many records. If you want to simply delete records without worrying about dependent associations or
+ # callbacks, use the much faster +delete_all+ method instead.
+ #
+ # ==== Attributes
+ #
+ # * +conditions+ - Conditions are specified the same way as with +find+ method.
+ #
+ # ==== Example
+ #
+ # Person.destroy_all "last_login < '2004-04-04'"
+ #
+ # This loads and destroys each person one by one, including its dependent associations and before_ and
+ # after_destroy callbacks.
+ def destroy_all(conditions = nil)
+ find(:all, :conditions => conditions).each { |object| object.destroy }
+ end
+
+ # Deletes the records matching +conditions+ without instantiating the records first, and hence not
+ # calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
+ # than destroy_all.
+ #
+ # ==== Attributes
+ #
+ # * +conditions+ - Conditions are specified the same way as with +find+ method.
+ #
+ # ==== Example
+ #
+ # Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
+ #
+ # This deletes the affected posts all at once with a single DELETE query. If you need to destroy dependent
+ # associations or call your before_ or after_destroy callbacks, use the +destroy_all+ method instead.
+ def delete_all(conditions = nil)
+ sql = "DELETE FROM #{quoted_table_name} "
+ add_conditions!(sql, conditions, scope(:find))
+ connection.delete(sql, "#{name} Delete all")
+ end
+
+ # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
+ # The use of this method should be restricted to complicated SQL queries that can't be executed
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ #
+ # ==== Attributes
+ #
+ # * +sql+ - An SQL statement which should return a count query from the database, see the example below.
+ #
+ # ==== Examples
+ #
+ # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
+ def count_by_sql(sql)
+ sql = sanitize_conditions(sql)
+ connection.select_value(sql, "#{name} Count").to_i
+ end
+
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Attributes
+ #
+ # * +id+ - The id of the object you wish to update a counter on.
+ # * +counters+ - An Array of Hashes containing the names of the fields
+ # to update as keys and the amount to update the field by as values.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = comment_count - 1,
+ # # action_count = action_count + 1
+ # # WHERE id = 5
+ def update_counters(id, counters)
+ updates = counters.inject([]) { |list, (counter_name, increment)|
+ sign = increment < 0 ? "-" : "+"
+ list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
+ }.join(", ")
+ update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Attributes
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
+
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Attributes
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
+
+
+ # Attributes named in this macro are protected from mass-assignment,
+ # such as new(attributes),
+ # update_attributes(attributes), or
+ # attributes=(attributes).
+ #
+ # Mass-assignment to these attributes will simply be ignored, to assign
+ # to them you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms.
+ #
+ # class Customer < ActiveRecord::Base
+ # attr_protected :credit_rating
+ # end
+ #
+ # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
+ # customer.credit_rating # => nil
+ # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
+ # customer.credit_rating # => nil
+ #
+ # customer.credit_rating = "Average"
+ # customer.credit_rating # => "Average"
+ #
+ # To start from an all-closed default and enable attributes as needed,
+ # have a look at +attr_accessible+.
+ def attr_protected(*attributes)
+ write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
+ end
+
+ # Returns an array of all the attributes that have been protected from mass-assignment.
+ def protected_attributes # :nodoc:
+ read_inheritable_attribute("attr_protected")
+ end
+
+ # Specifies a white list of model attributes that can be set via
+ # mass-assignment, such as new(attributes),
+ # update_attributes(attributes), or
+ # attributes=(attributes)
+ #
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
+ # will only set attributes in this list, to assign to the rest of
+ # attributes you can use direct writer methods. This is meant to protect
+ # sensitive attributes from being overwritten by malicious users
+ # tampering with URLs or forms. If you'd rather start from an all-open
+ # default and restrict attributes as needed, have a look at
+ # +attr_protected+.
+ #
+ # class Customer < ActiveRecord::Base
+ # attr_accessible :name, :nickname
+ # end
+ #
+ # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
+ # customer.credit_rating # => nil
+ # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
+ # customer.credit_rating # => nil
+ #
+ # customer.credit_rating = "Average"
+ # customer.credit_rating # => "Average"
+ def attr_accessible(*attributes)
+ write_inheritable_attribute("attr_accessible", Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
+ end
+
+ # Returns an array of all the attributes that have been made accessible to mass-assignment.
+ def accessible_attributes # :nodoc:
+ read_inheritable_attribute("attr_accessible")
+ end
+
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
+ def attr_readonly(*attributes)
+ write_inheritable_attribute("attr_readonly", Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
+ end
+
+ # Returns an array of all the attributes that have been specified as readonly.
+ def readonly_attributes
+ read_inheritable_attribute("attr_readonly")
+ end
+
+ # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
+ # then specify the name of that attribute using this method and it will be handled automatically.
+ # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
+ # class on retrieval or SerializationTypeMismatch will be raised.
+ #
+ # ==== Attributes
+ #
+ # * +attr_name+ - The field name that should be serialized.
+ # * +class_name+ - Optional, class name that the object type should be equal to.
+ #
+ # ==== Example
+ # # Serialize a preferences attribute
+ # class User
+ # serialize :preferences
+ # end
+ def serialize(attr_name, class_name = Object)
+ serialized_attributes[attr_name.to_s] = class_name
+ end
+
+ # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
+ def serialized_attributes
+ read_inheritable_attribute("attr_serialized") or write_inheritable_attribute("attr_serialized", {})
+ end
+
+
+ # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
+ # directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
+ # to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
+ # in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
+ #
+ # Nested classes are given table names prefixed by the singular form of
+ # the parent's table name. Enclosing modules are not considered.
+ #
+ # ==== Examples
+ #
+ # class Invoice < ActiveRecord::Base; end;
+ # file class table_name
+ # invoice.rb Invoice invoices
+ #
+ # class Invoice < ActiveRecord::Base; class Lineitem < ActiveRecord::Base; end; end;
+ # file class table_name
+ # invoice.rb Invoice::Lineitem invoice_lineitems
+ #
+ # module Invoice; class Lineitem < ActiveRecord::Base; end; end;
+ # file class table_name
+ # invoice/lineitem.rb Invoice::Lineitem lineitems
+ #
+ # Additionally, the class-level +table_name_prefix+ is prepended and the
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
+ # the table name guess for an Invoice class becomes "myapp_invoices".
+ # Invoice::Lineitem becomes "myapp_invoice_lineitems".
+ #
+ # You can also overwrite this class method to allow for unguessable
+ # links, such as a Mouse class with a link to a "mice" table. Example:
+ #
+ # class Mouse < ActiveRecord::Base
+ # set_table_name "mice"
+ # end
+ def table_name
+ reset_table_name
+ end
+
+ def reset_table_name #:nodoc:
+ base = base_class
+
+ name =
+ # STI subclasses always use their superclass' table.
+ unless self == base
+ base.table_name
+ else
+ # Nested classes are prefixed with singular parent table name.
+ if parent < ActiveRecord::Base && !parent.abstract_class?
+ contained = parent.table_name
+ contained = contained.singularize if parent.pluralize_table_names
+ contained << '_'
+ end
+ name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
+ end
+
+ set_table_name(name)
+ name
+ end
+
+ # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
+ # primary_key_prefix_type setting, though.
+ def primary_key
+ reset_primary_key
+ end
+
+ def reset_primary_key #:nodoc:
+ key = get_primary_key(base_class.name)
+ set_primary_key(key)
+ key
+ end
+
+ def get_primary_key(base_name) #:nodoc:
+ key = 'id'
+ case primary_key_prefix_type
+ when :table_name
+ key = base_name.to_s.foreign_key(false)
+ when :table_name_with_underscore
+ key = base_name.to_s.foreign_key
+ end
+ key
+ end
+
+ # Defines the column name for use with single table inheritance
+ # -- can be set in subclasses like so: self.inheritance_column = "type_id"
+ def inheritance_column
+ @inheritance_column ||= "type".freeze
+ end
+
+ # Lazy-set the sequence name to the connection's default. This method
+ # is only ever called once since set_sequence_name overrides it.
+ def sequence_name #:nodoc:
+ reset_sequence_name
+ end
+
+ def reset_sequence_name #:nodoc:
+ default = connection.default_sequence_name(table_name, primary_key)
+ set_sequence_name(default)
+ default
+ end
+
+ # Sets the table name to use to the given value, or (if the value
+ # is nil or false) to the value returned by the given block.
+ #
+ # class Project < ActiveRecord::Base
+ # set_table_name "project"
+ # end
+ def set_table_name(value = nil, &block)
+ define_attr_method :table_name, value, &block
+ end
+ alias :table_name= :set_table_name
+
+ # Sets the name of the primary key column to use to the given value,
+ # or (if the value is nil or false) to the value returned by the given
+ # block.
+ #
+ # class Project < ActiveRecord::Base
+ # set_primary_key "sysid"
+ # end
+ def set_primary_key(value = nil, &block)
+ define_attr_method :primary_key, value, &block
+ end
+ alias :primary_key= :set_primary_key
+
+ # Sets the name of the inheritance column to use to the given value,
+ # or (if the value # is nil or false) to the value returned by the
+ # given block.
+ #
+ # class Project < ActiveRecord::Base
+ # set_inheritance_column do
+ # original_inheritance_column + "_id"
+ # end
+ # end
+ def set_inheritance_column(value = nil, &block)
+ define_attr_method :inheritance_column, value, &block
+ end
+ alias :inheritance_column= :set_inheritance_column
+
+ # Sets the name of the sequence to use when generating ids to the given
+ # value, or (if the value is nil or false) to the value returned by the
+ # given block. This is required for Oracle and is useful for any
+ # database which relies on sequences for primary key generation.
+ #
+ # If a sequence name is not explicitly set when using Oracle or Firebird,
+ # it will default to the commonly used pattern of: #{table_name}_seq
+ #
+ # If a sequence name is not explicitly set when using PostgreSQL, it
+ # will discover the sequence corresponding to your primary key for you.
+ #
+ # class Project < ActiveRecord::Base
+ # set_sequence_name "projectseq" # default would have been "project_seq"
+ # end
+ def set_sequence_name(value = nil, &block)
+ define_attr_method :sequence_name, value, &block
+ end
+ alias :sequence_name= :set_sequence_name
+
+ # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
+ def class_name(table_name = table_name) # :nodoc:
+ # remove any prefix and/or suffix from the table name
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].camelize
+ class_name = class_name.singularize if pluralize_table_names
+ class_name
+ end
+
+ # Indicates whether the table associated with this class exists
+ def table_exists?
+ connection.table_exists?(table_name)
+ end
+
+ # Returns an array of column objects for the table associated with this class.
+ def columns
+ unless defined?(@columns) && @columns
+ @columns = connection.columns(table_name, "#{name} Columns")
+ @columns.each { |column| column.primary = column.name == primary_key }
+ end
+ @columns
+ end
+
+ # Returns a hash of column objects for the table associated with this class.
+ def columns_hash
+ @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
+ end
+
+ # Returns an array of column names as strings.
+ def column_names
+ @column_names ||= columns.map { |column| column.name }
+ end
+
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
+ # and columns used for single table inheritance have been removed.
+ def content_columns
+ @content_columns ||= columns.reject { |c| c.primary || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
+ end
+
+ # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
+ # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
+ # is available.
+ def column_methods_hash #:nodoc:
+ @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
+ attr_name = attr.to_s
+ methods[attr.to_sym] = attr_name
+ methods["#{attr}=".to_sym] = attr_name
+ methods["#{attr}?".to_sym] = attr_name
+ methods["#{attr}_before_type_cast".to_sym] = attr_name
+ methods
+ end
+ end
+
+ # Resets all the cached information about columns, which will cause them to be reloaded on the next request.
+ def reset_column_information
+ generated_methods.each { |name| undef_method(name) }
+ @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil
+ end
+
+ def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
+ subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
+ end
+
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
+ # Person.human_attribute_name("first_name") # => "First name"
+ # Deprecated in favor of just calling "first_name".humanize
+ def human_attribute_name(attribute_key_name) #:nodoc:
+ attribute_key_name.humanize
+ end
+
+ # True if this isn't a concrete subclass needing a STI type condition.
+ def descends_from_active_record?
+ if superclass.abstract_class?
+ superclass.descends_from_active_record?
+ else
+ superclass == Base || !columns_hash.include?(inheritance_column)
+ end
+ end
+
+ def finder_needs_type_condition? #:nodoc:
+ # This is like this because benchmarking justifies the strange :false stuff
+ :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
+ end
+
+ # Returns a string like 'Post id:integer, title:string, body:text'
+ def inspect
+ if self == Base
+ super
+ elsif abstract_class?
+ "#{super}(abstract)"
+ elsif table_exists?
+ attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
+ "#{super}(#{attr_list})"
+ else
+ "#{super}(Table doesn't exist)"
+ end
+ end
+
+
+ def quote_value(value, column = nil) #:nodoc:
+ connection.quote(value,column)
+ end
+
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to connection.quote.
+ def sanitize(object) #:nodoc:
+ connection.quote(object)
+ end
+
+ # Log and benchmark multiple statements in a single block. Example:
+ #
+ # Project.benchmark("Creating project") do
+ # project = Project.create("name" => "stuff")
+ # project.create_manager("name" => "David")
+ # project.milestones << Milestone.find(:all)
+ # end
+ #
+ # The benchmark is only recorded if the current level of the logger is less than or equal to the log_level,
+ # which makes it easy to include benchmarking statements in production software that will remain inexpensive because
+ # the benchmark will only be conducted if the log level is low enough.
+ #
+ # The logging of the multiple statements is turned off unless use_silence is set to false.
+ def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
+ if logger && logger.level <= log_level
+ result = nil
+ seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
+ logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
+ result
+ else
+ yield
+ end
+ end
+
+ # Silences the logger for the duration of the block.
+ def silence
+ old_logger_level, logger.level = logger.level, Logger::ERROR if logger
+ yield
+ ensure
+ logger.level = old_logger_level if logger
+ end
+
+ # Overwrite the default class equality method to provide support for association proxies.
+ def ===(object)
+ object.is_a?(self)
+ end
+
+ # Returns the base AR subclass that this class descends from. If A
+ # extends AR::Base, A.base_class will return A. If B descends from A
+ # through some arbitrarily deep hierarchy, B.base_class will return A.
+ def base_class
+ class_of_active_record_descendant(self)
+ end
+
+ # Set this to true if this is an abstract class (see abstract_class?).
+ attr_accessor :abstract_class
+
+ # Returns whether this class is a base AR class. If A is a base class and
+ # B descends from A, then B.base_class will return B.
+ def abstract_class?
+ defined?(@abstract_class) && @abstract_class == true
+ end
+
+ def respond_to?(method_id, include_private = false)
+ if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
+ return true if all_attributes_exists?(extract_attribute_names_from_match(match))
+ end
+ super
+ end
+
+ def sti_name
+ store_full_sti_class ? name : name.demodulize
+ end
+
+ private
+ def find_initial(options)
+ options.update(:limit => 1)
+ find_every(options).first
+ end
+
+ def find_last(options)
+ order = options[:order]
+
+ if order
+ order = reverse_sql_order(order)
+ elsif !scoped?(:find, :order)
+ order = "#{table_name}.#{primary_key} DESC"
+ end
+
+ if scoped?(:find, :order)
+ scoped_order = reverse_sql_order(scope(:find, :order))
+ scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
+ end
+
+ find_initial(options.merge({ :order => order }))
+ end
+
+ def reverse_sql_order(order_query)
+ reversed_query = order_query.split(/,/).each { |s|
+ if s.match(/\s(asc|ASC)$/)
+ s.gsub!(/\s(asc|ASC)$/, ' DESC')
+ elsif s.match(/\s(desc|DESC)$/)
+ s.gsub!(/\s(desc|DESC)$/, ' ASC')
+ elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
+ s.concat(' DESC')
+ end
+ }.join(',')
+ end
+
+ def find_every(options)
+ include_associations = merge_includes(scope(:find, :include), options[:include])
+
+ if include_associations.any? && references_eager_loaded_tables?(options)
+ records = find_with_associations(options)
+ else
+ records = find_by_sql(construct_finder_sql(options))
+ if include_associations.any?
+ preload_associations(records, include_associations)
+ end
+ end
+
+ records.each { |record| record.readonly! } if options[:readonly]
+
+ records
+ end
+
+ def find_from_ids(ids, options)
+ expects_array = ids.first.kind_of?(Array)
+ return ids.first if expects_array && ids.first.empty?
+
+ ids = ids.flatten.compact.uniq
+
+ case ids.size
+ when 0
+ raise RecordNotFound, "Couldn't find #{name} without an ID"
+ when 1
+ result = find_one(ids.first, options)
+ expects_array ? [ result ] : result
+ else
+ find_some(ids, options)
+ end
+ end
+
+ def find_one(id, options)
+ conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
+ options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} = #{quote_value(id,columns_hash[primary_key])}#{conditions}"
+
+ # Use find_every(options).first since the primary key condition
+ # already ensures we have a single record. Using find_initial adds
+ # a superfluous :limit => 1.
+ if result = find_every(options).first
+ result
+ else
+ raise RecordNotFound, "Couldn't find #{name} with ID=#{id}#{conditions}"
+ end
+ end
+
+ def find_some(ids, options)
+ conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
+ ids_list = ids.map { |id| quote_value(id,columns_hash[primary_key]) }.join(',')
+ options.update :conditions => "#{quoted_table_name}.#{connection.quote_column_name(primary_key)} IN (#{ids_list})#{conditions}"
+
+ result = find_every(options)
+
+ # Determine expected size from limit and offset, not just ids.size.
+ expected_size =
+ if options[:limit] && ids.size > options[:limit]
+ options[:limit]
+ else
+ ids.size
+ end
+
+ # 11 ids with limit 3, offset 9 should give 2 results.
+ if options[:offset] && (ids.size - options[:offset] < expected_size)
+ expected_size = ids.size - options[:offset]
+ end
+
+ if result.size == expected_size
+ result
+ else
+ raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions} (found #{result.size} results, but was looking for #{expected_size})"
+ end
+ 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)
+ object =
+ if subclass_name = record[inheritance_column]
+ # No type given.
+ if subclass_name.empty?
+ allocate
+
+ else
+ # Ignore type if no column is present since it was probably
+ # pulled in from a sloppy join.
+ unless columns_hash.include?(inheritance_column)
+ allocate
+
+ else
+ begin
+ compute_type(subclass_name).allocate
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
+ end
+ end
+ end
+ else
+ allocate
+ end
+
+ object.instance_variable_set("@attributes", record)
+ object.instance_variable_set("@attributes_cache", Hash.new)
+
+ if object.respond_to_without_attributes?(:after_find)
+ object.send(:callback, :after_find)
+ end
+
+ if object.respond_to_without_attributes?(:after_initialize)
+ object.send(:callback, :after_initialize)
+ end
+
+ object
+ end
+
+ # Nest the type name in the same module as this class.
+ # Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
+ def type_name_with_module(type_name)
+ if store_full_sti_class
+ type_name
+ else
+ (/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
+ end
+ end
+
+ def construct_finder_sql(options)
+ scope = scope(:find)
+ sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
+ sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
+
+ add_joins!(sql, options, scope)
+ add_conditions!(sql, options[:conditions], scope)
+
+ add_group!(sql, options[:group], scope)
+ add_order!(sql, options[:order], scope)
+ add_limit!(sql, options, scope)
+ add_lock!(sql, options, scope)
+
+ sql
+ end
+
+ # Merges includes so that the result is a valid +include+
+ def merge_includes(first, second)
+ (safe_to_array(first) + safe_to_array(second)).uniq
+ end
+
+ # Merges conditions so that the result is a valid +condition+
+ def merge_conditions(*conditions)
+ segments = []
+
+ conditions.each do |condition|
+ unless condition.blank?
+ sql = sanitize_sql(condition)
+ segments << sql unless sql.blank?
+ end
+ end
+
+ "(#{segments.join(') AND (')})" unless segments.empty?
+ end
+
+ # Object#to_a is deprecated, though it does have the desired behavior
+ def safe_to_array(o)
+ case o
+ when NilClass
+ []
+ when Array
+ o
+ else
+ [o]
+ end
+ end
+
+ def add_order!(sql, order, scope = :auto)
+ scope = scope(:find) if :auto == scope
+ scoped_order = scope[:order] if scope
+ if order
+ sql << " ORDER BY #{order}"
+ sql << ", #{scoped_order}" if scoped_order
+ else
+ sql << " ORDER BY #{scoped_order}" if scoped_order
+ end
+ end
+
+ def add_group!(sql, group, scope = :auto)
+ if group
+ sql << " GROUP BY #{group}"
+ else
+ scope = scope(:find) if :auto == scope
+ if scope && (scoped_group = scope[:group])
+ sql << " GROUP BY #{scoped_group}"
+ end
+ end
+ end
+
+ # The optional scope argument is for the current :find scope.
+ def add_limit!(sql, options, scope = :auto)
+ scope = scope(:find) if :auto == scope
+
+ if scope
+ options[:limit] ||= scope[:limit]
+ options[:offset] ||= scope[:offset]
+ end
+
+ connection.add_limit_offset!(sql, options)
+ end
+
+ # The optional scope argument is for the current :find scope.
+ # The :lock option has precedence over a scoped :lock.
+ def add_lock!(sql, options, scope = :auto)
+ scope = scope(:find) if :auto == scope
+ options = options.reverse_merge(:lock => scope[:lock]) if scope
+ connection.add_lock!(sql, options)
+ end
+
+ # The optional scope argument is for the current :find scope.
+ def add_joins!(sql, options, scope = :auto)
+ scope = scope(:find) if :auto == scope
+ [(scope && scope[:joins]), options[:joins]].each do |join|
+ case join
+ when Symbol, Hash, Array
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
+ sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
+ else
+ sql << " #{join} "
+ end
+ end
+ end
+
+ # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
+ # The optional scope argument is for the current :find scope.
+ def add_conditions!(sql, conditions, scope = :auto)
+ scope = scope(:find) if :auto == scope
+ conditions = [conditions]
+ conditions << scope[:conditions] if scope
+ conditions << type_condition if finder_needs_type_condition?
+ merged_conditions = merge_conditions(*conditions)
+ sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
+ end
+
+ def type_condition
+ quoted_inheritance_column = connection.quote_column_name(inheritance_column)
+ type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
+ condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
+ end
+
+ " (#{type_condition}) "
+ end
+
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
+ def undecorated_table_name(class_name = base_class.name)
+ table_name = class_name.to_s.demodulize.underscore
+ table_name = table_name.pluralize if pluralize_table_names
+ table_name
+ end
+
+ # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
+ # find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
+ # respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]).
+ #
+ # It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
+ # is actually find_all_by_amount(amount, options).
+ #
+ # This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
+ # or find_or_create_by_user_and_password(user, password).
+ #
+ # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
+ # attempts to use it do not run through method_missing.
+ def method_missing(method_id, *arguments)
+ if match = matches_dynamic_finder?(method_id)
+ finder = determine_finder(match)
+
+ attribute_names = extract_attribute_names_from_match(match)
+ super unless all_attributes_exists?(attribute_names)
+
+ self.class_eval %{
+ def self.#{method_id}(*args)
+ options = args.extract_options!
+ attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ finder_options = { :conditions => attributes }
+ validate_find_options(options)
+ set_readonly_option!(options)
+
+ if options[:conditions]
+ with_scope(:find => finder_options) do
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options) }
+ end
+ else
+ ActiveSupport::Deprecation.silence { send(:#{finder}, options.merge(finder_options)) }
+ end
+ end
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
+ instantiator = determine_instantiator(match)
+ attribute_names = extract_attribute_names_from_match(match)
+ super unless all_attributes_exists?(attribute_names)
+
+ self.class_eval %{
+ def self.#{method_id}(*args)
+ guard_protected_attributes = false
+
+ if args[0].is_a?(Hash)
+ guard_protected_attributes = true
+ attributes = args[0].with_indifferent_access
+ find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
+ else
+ find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
+ end
+
+ options = { :conditions => find_attributes }
+ set_readonly_option!(options)
+
+ record = find_initial(options)
+
+ if record.nil?
+ record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
+ #{'yield(record) if block_given?'}
+ #{'record.save' if instantiator == :create}
+ record
+ else
+ record
+ end
+ end
+ }, __FILE__, __LINE__
+ send(method_id, *arguments)
+ else
+ super
+ end
+ end
+
+ def matches_dynamic_finder?(method_id)
+ /^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
+ end
+
+ def matches_dynamic_finder_with_initialize_or_create?(method_id)
+ /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
+ end
+
+ def determine_finder(match)
+ match.captures.first == 'all_by' ? :find_every : :find_initial
+ end
+
+ def determine_instantiator(match)
+ match.captures.first == 'initialize' ? :new : :create
+ end
+
+ def extract_attribute_names_from_match(match)
+ match.captures.last.split('_and_')
+ end
+
+ def construct_attributes_from_arguments(attribute_names, arguments)
+ attributes = {}
+ attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
+ attributes
+ end
+
+ # Similar in purpose to +expand_hash_conditions_for_aggregates+.
+ def expand_attribute_names_for_aggregates(attribute_names)
+ expanded_attribute_names = []
+ attribute_names.each do |attribute_name|
+ unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
+ aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
+ expanded_attribute_names << field_attr
+ end
+ else
+ expanded_attribute_names << attribute_name
+ end
+ end
+ expanded_attribute_names
+ end
+
+ def all_attributes_exists?(attribute_names)
+ attribute_names = expand_attribute_names_for_aggregates(attribute_names)
+ attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
+ end
+
+ def attribute_condition(argument)
+ case argument
+ when nil then "IS ?"
+ when Array, ActiveRecord::Associations::AssociationCollection then "IN (?)"
+ when Range then "BETWEEN ? AND ?"
+ else "= ?"
+ end
+ end
+
+ # Interpret Array and Hash as conditions and anything else as an id.
+ def expand_id_conditions(id_or_conditions)
+ case id_or_conditions
+ when Array, Hash then id_or_conditions
+ else sanitize_sql(primary_key => id_or_conditions)
+ end
+ end
+
+
+ # Defines an "attribute" method (like +inheritance_column+ or
+ # +table_name+). A new (class) method will be created with the
+ # given name. If a value is specified, the new method will
+ # return that value (as a string). Otherwise, the given block
+ # will be used to compute the value of the method.
+ #
+ # The original method will be aliased, with the new name being
+ # prefixed with "original_". This allows the new method to
+ # access the original value.
+ #
+ # Example:
+ #
+ # class A < ActiveRecord::Base
+ # define_attr_method :primary_key, "sysid"
+ # define_attr_method( :inheritance_column ) do
+ # original_inheritance_column + "_id"
+ # end
+ # end
+ def define_attr_method(name, value=nil, &block)
+ sing = class << self; self; end
+ sing.send :alias_method, "original_#{name}", name
+ if block_given?
+ sing.send :define_method, name, &block
+ else
+ # use eval instead of a block to work around a memory leak in dev
+ # mode in fcgi
+ sing.class_eval "def #{name}; #{value.to_s.inspect}; end"
+ end
+ end
+
+ protected
+ # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
+ # method_name may be :find or :create. :find parameters may include the :conditions, :joins,
+ # :include, :offset, :limit, and :readonly options. :create parameters are an attributes hash.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.create_with_scope
+ # with_scope(:find => { :conditions => "blog_id = 1" }, :create => { :blog_id => 1 }) do
+ # find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
+ # a = create(1)
+ # a.blog_id # => 1
+ # end
+ # end
+ # end
+ #
+ # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
+ # :conditions and :include options in :find, which are merged.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.find_with_scope
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
+ # with_scope(:find => { :limit => 10 })
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
+ # end
+ # with_scope(:find => { :conditions => "author_id = 3" })
+ # find(:all) # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
+ # end
+ # end
+ # end
+ # end
+ #
+ # You can ignore any previous scopings by using the with_exclusive_scope method.
+ #
+ # class Article < ActiveRecord::Base
+ # def self.find_with_exclusive_scope
+ # with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }) do
+ # with_exclusive_scope(:find => { :limit => 10 })
+ # find(:all) # => SELECT * from articles LIMIT 10
+ # end
+ # end
+ # end
+ # end
+ def with_scope(method_scoping = {}, action = :merge, &block)
+ method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
+
+ # Dup first and second level of hash (method and params).
+ method_scoping = method_scoping.inject({}) do |hash, (method, params)|
+ hash[method] = (params == true) ? params : params.dup
+ hash
+ end
+
+ method_scoping.assert_valid_keys([ :find, :create ])
+
+ if f = method_scoping[:find]
+ f.assert_valid_keys(VALID_FIND_OPTIONS)
+ set_readonly_option! f
+ end
+
+ # Merge scopings
+ if action == :merge && current_scoped_methods
+ method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
+ case hash[method]
+ when Hash
+ if method == :find
+ (hash[method].keys + params.keys).uniq.each do |key|
+ merge = hash[method][key] && params[key] # merge if both scopes have the same key
+ if key == :conditions && merge
+ hash[method][key] = merge_conditions(params[key], hash[method][key])
+ elsif key == :include && merge
+ hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
+ else
+ hash[method][key] = hash[method][key] || params[key]
+ end
+ end
+ else
+ hash[method] = params.merge(hash[method])
+ end
+ else
+ hash[method] = params
+ end
+ hash
+ end
+ end
+
+ self.scoped_methods << method_scoping
+
+ begin
+ yield
+ ensure
+ self.scoped_methods.pop
+ end
+ end
+
+ # Works like with_scope, but discards any nested properties.
+ def with_exclusive_scope(method_scoping = {}, &block)
+ with_scope(method_scoping, :overwrite, &block)
+ end
+
+ def subclasses #:nodoc:
+ @@subclasses[self] ||= []
+ @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
+ end
+
+ # Test whether the given method and optional key are scoped.
+ def scoped?(method, key = nil) #:nodoc:
+ if current_scoped_methods && (scope = current_scoped_methods[method])
+ !key || scope.has_key?(key)
+ end
+ end
+
+ # Retrieve the scope for the given method and optional key.
+ def scope(method, key = nil) #:nodoc:
+ if current_scoped_methods && (scope = current_scoped_methods[method])
+ key ? scope[key] : scope
+ end
+ end
+
+ def thread_safe_scoped_methods #:nodoc:
+ scoped_methods = (Thread.current[:scoped_methods] ||= {})
+ scoped_methods[self] ||= []
+ end
+
+ def single_threaded_scoped_methods #:nodoc:
+ @scoped_methods ||= []
+ end
+
+ # pick up the correct scoped_methods version from @@allow_concurrency
+ if @@allow_concurrency
+ alias_method :scoped_methods, :thread_safe_scoped_methods
+ else
+ alias_method :scoped_methods, :single_threaded_scoped_methods
+ end
+
+ def current_scoped_methods #:nodoc:
+ scoped_methods.last
+ end
+
+ # Returns the class type of the record using the current module as a prefix. So descendents of
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
+ def compute_type(type_name)
+ modularized_name = type_name_with_module(type_name)
+ begin
+ class_eval(modularized_name, __FILE__, __LINE__)
+ rescue NameError
+ class_eval(type_name, __FILE__, __LINE__)
+ end
+ end
+
+ # Returns the class descending directly from Active Record in the inheritance hierarchy.
+ def class_of_active_record_descendant(klass)
+ if klass.superclass == Base || klass.superclass.abstract_class?
+ klass
+ elsif klass.superclass.nil?
+ raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
+ else
+ class_of_active_record_descendant(klass.superclass)
+ end
+ end
+
+ # Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
+ def class_name_of_active_record_descendant(klass) #:nodoc:
+ klass.base_class.name
+ end
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a WHERE clause.
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
+ def sanitize_sql_for_conditions(condition)
+ return nil if condition.blank?
+
+ case condition
+ when Array; sanitize_sql_array(condition)
+ when Hash; sanitize_sql_hash_for_conditions(condition)
+ else condition
+ end
+ end
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
+
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
+ # them into a valid SQL fragment for a SET clause.
+ # { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
+ def sanitize_sql_for_assignment(assignments)
+ case assignments
+ when Array; sanitize_sql_array(assignments)
+ when Hash; sanitize_sql_hash_for_assignment(assignments)
+ else assignments
+ end
+ end
+
+ def aggregate_mapping(reflection)
+ mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
+ mapping.first.is_a?(Array) ? mapping : [mapping]
+ end
+
+ # Accepts a hash of SQL conditions and replaces those attributes
+ # that correspond to a +composed_of+ relationship with their expanded
+ # aggregate attribute values.
+ # Given:
+ # class Person < ActiveRecord::Base
+ # composed_of :address, :class_name => "Address",
+ # :mapping => [%w(address_street street), %w(address_city city)]
+ # end
+ # Then:
+ # { :address => Address.new("813 abc st.", "chicago") }
+ # # => { :address_street => "813 abc st.", :address_city => "chicago" }
+ def expand_hash_conditions_for_aggregates(attrs)
+ expanded_attrs = {}
+ attrs.each do |attr, value|
+ unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
+ mapping = aggregate_mapping(aggregation)
+ mapping.each do |field_attr, aggregate_attr|
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
+ expanded_attrs[field_attr] = value
+ else
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
+ end
+ end
+ else
+ expanded_attrs[attr] = value
+ end
+ end
+ expanded_attrs
+ end
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
+ # { :name => "foo'bar", :group_id => 4 }
+ # # => "name='foo''bar' and group_id= 4"
+ # { :status => nil, :group_id => [1,2,3] }
+ # # => "status IS NULL and group_id IN (1,2,3)"
+ # { :age => 13..18 }
+ # # => "age BETWEEN 13 AND 18"
+ # { 'other_records.id' => 7 }
+ # # => "`other_records`.`id` = 7"
+ # And for value objects on a composed_of relationship:
+ # { :address => Address.new("123 abc st.", "chicago") }
+ # # => "address_street='123 abc st.' and address_city='chicago'"
+ def sanitize_sql_hash_for_conditions(attrs)
+ attrs = expand_hash_conditions_for_aggregates(attrs)
+
+ conditions = attrs.map do |attr, value|
+ attr = attr.to_s
+
+ # Extract table name from qualified attribute names.
+ if attr.include?('.')
+ table_name, attr = attr.split('.', 2)
+ table_name = connection.quote_table_name(table_name)
+ else
+ table_name = quoted_table_name
+ end
+
+ "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition(value)}"
+ end.join(' AND ')
+
+ replace_bind_variables(conditions, expand_range_bind_variables(attrs.values))
+ end
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
+
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
+ # { :status => nil, :group_id => 1 }
+ # # => "status = NULL , group_id = 1"
+ def sanitize_sql_hash_for_assignment(attrs)
+ attrs.map do |attr, value|
+ "#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
+ end.join(', ')
+ end
+
+ # Accepts an array of conditions. The array has each value
+ # sanitized and interpolated into the SQL statement.
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
+ def sanitize_sql_array(ary)
+ statement, *values = ary
+ if values.first.is_a?(Hash) and statement =~ /:\w+/
+ replace_named_bind_variables(statement, values.first)
+ elsif statement.include?('?')
+ replace_bind_variables(statement, values)
+ else
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
+ end
+ end
+
+ alias_method :sanitize_conditions, :sanitize_sql
+
+ def replace_bind_variables(statement, values) #:nodoc:
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
+ bound = values.dup
+ statement.gsub('?') { quote_bound_value(bound.shift) }
+ end
+
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
+ statement.gsub(/:([a-zA-Z]\w*)/) do
+ match = $1.to_sym
+ if bind_vars.include?(match)
+ quote_bound_value(bind_vars[match])
+ else
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
+ end
+ end
+ end
+
+ def expand_range_bind_variables(bind_vars) #:nodoc:
+ bind_vars.sum do |var|
+ if var.is_a?(Range)
+ [var.first, var.last]
+ else
+ [var]
+ end
+ end
+ end
+
+ def quote_bound_value(value) #:nodoc:
+ if value.respond_to?(:map) && !value.is_a?(String)
+ if value.respond_to?(:empty?) && value.empty?
+ connection.quote(nil)
+ else
+ value.map { |v| connection.quote(v) }.join(',')
+ end
+ else
+ connection.quote(value)
+ end
+ end
+
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
+ unless expected == provided
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
+ end
+ end
+
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
+ :order, :select, :readonly, :group, :from, :lock ]
+
+ def validate_find_options(options) #:nodoc:
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
+ end
+
+ def set_readonly_option!(options) #:nodoc:
+ # Inherit :readonly from finder scope if set. Otherwise,
+ # if :joins is not blank then :readonly defaults to true.
+ unless options.has_key?(:readonly)
+ if scoped_readonly = scope(:find, :readonly)
+ options[:readonly] = scoped_readonly
+ elsif !options[:joins].blank? && !options[:select]
+ options[:readonly] = true
+ end
+ end
+ end
+
+ def encode_quoted_value(value) #:nodoc:
+ quoted_value = connection.quote(value)
+ quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'") # (for ruby mode) "
+ quoted_value
+ end
+ end
+
+ public
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
+ # hence you can't have attributes that aren't part of the table columns.
+ def initialize(attributes = nil)
+ @attributes = attributes_from_column_definition
+ @attributes_cache = {}
+ @new_record = true
+ ensure_proper_type
+ self.attributes = attributes unless attributes.nil?
+ self.class.send(:scope, :create).each { |att,value| self.send("#{att}=", value) } if self.class.send(:scoped?, :create)
+ result = yield self if block_given?
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
+ result
+ end
+
+ # A model instance's primary key is always available as model.id
+ # whether you name it the default 'id' or set it to something else.
+ def id
+ attr_name = self.class.primary_key
+ column = column_for_attribute(attr_name)
+
+ self.class.send(:define_read_method, :id, attr_name, column)
+ # now that the method exists, call it
+ self.send attr_name.to_sym
+
+ end
+
+ # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
+ def to_param
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
+ (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
+ end
+
+ # Returns a cache key that can be used to identify this record.
+ #
+ # ==== Examples
+ #
+ # Product.new.cache_key # => "products/new"
+ # Product.find(5).cache_key # => "products/5" (updated_at not available)
+ # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
+ def cache_key
+ case
+ when new_record?
+ "#{self.class.name.tableize}/new"
+ when self[:updated_at]
+ "#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
+ else
+ "#{self.class.name.tableize}/#{id}"
+ end
+ end
+
+ def id_before_type_cast #:nodoc:
+ read_attribute_before_type_cast(self.class.primary_key)
+ end
+
+ def quoted_id #:nodoc:
+ quote_value(id, column_for_attribute(self.class.primary_key))
+ end
+
+ # Sets the primary ID.
+ def id=(value)
+ write_attribute(self.class.primary_key, value)
+ end
+
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
+ def new_record?
+ defined?(@new_record) && @new_record
+ end
+
+ # * No record exists: Creates a new record with values matching those of the object attributes.
+ # * A record does exist: Updates the record with values matching those of the object attributes.
+ #
+ # Note: If your model specifies any validations then the method declaration dynamically
+ # changes to:
+ # save(perform_validation=true)
+ # Calling save(false) saves the model without running validations.
+ # See ActiveRecord::Validations for more information.
+ def save
+ create_or_update
+ end
+
+ # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
+ # RecordNotSaved exception
+ def save!
+ create_or_update || raise(RecordNotSaved)
+ end
+
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
+ # be made (since they can't be persisted).
+ def destroy
+ unless new_record?
+ connection.delete <<-end_sql, "#{self.class.name} Destroy"
+ DELETE FROM #{self.class.quoted_table_name}
+ WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}
+ end_sql
+ end
+
+ freeze
+ end
+
+ # Returns a clone of the record that hasn't been assigned an id yet and
+ # is treated as a new record. Note that this is a "shallow" clone:
+ # it copies the object's attributes only, not its associations.
+ # The extent of a "deep" clone is application-specific and is therefore
+ # left to the application to implement according to its need.
+ def clone
+ attrs = clone_attributes(:read_attribute_before_type_cast)
+ attrs.delete(self.class.primary_key)
+ record = self.class.new
+ record.send :instance_variable_set, '@attributes', attrs
+ record
+ end
+
+ # Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
+ # single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
+ # identification in Action Pack to allow, say, Client < Company to do something like render :partial => @client.becomes(Company)
+ # to render that instance using the companies/company partial instead of clients/client.
+ #
+ # Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
+ # instance will affect the other.
+ def becomes(klass)
+ returning klass.new do |became|
+ became.instance_variable_set("@attributes", @attributes)
+ became.instance_variable_set("@attributes_cache", @attributes_cache)
+ became.instance_variable_set("@new_record", new_record?)
+ end
+ end
+
+ # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
+ # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
+ # aren't subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
+ def update_attribute(name, value)
+ send(name.to_s + '=', value)
+ save
+ end
+
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
+ # fail and false will be returned.
+ def update_attributes(attributes)
+ self.attributes = attributes
+ save
+ end
+
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
+ def update_attributes!(attributes)
+ self.attributes = attributes
+ save!
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
+ # The increment is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def increment(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] += by
+ self
+ end
+
+ # Wrapper around +increment+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def increment!(attribute, by = 1)
+ increment(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
+ # The decrement is performed directly on the underlying attribute, no setter is invoked.
+ # Only makes sense for number-based attributes. Returns +self+.
+ def decrement(attribute, by = 1)
+ self[attribute] ||= 0
+ self[attribute] -= by
+ self
+ end
+
+ # Wrapper around +decrement+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def decrement!(attribute, by = 1)
+ decrement(attribute, by).update_attribute(attribute, self[attribute])
+ end
+
+ # Assigns to +attribute+ the boolean opposite of attribute?. So
+ # if the predicate returns +true+ the attribute will become +false+. This
+ # method toggles directly the underlying value without calling any setter.
+ # Returns +self+.
+ def toggle(attribute)
+ self[attribute] = !send("#{attribute}?")
+ self
+ end
+
+ # Wrapper around +toggle+ that saves the record. This method differs from
+ # its non-bang version in that it passes through the attribute setter.
+ # Saving is not subjected to validation checks. Returns +true+ if the
+ # record could be saved.
+ def toggle!(attribute)
+ toggle(attribute).update_attribute(attribute, self[attribute])
+ end
+
+ # Reloads the attributes of this object from the database.
+ # The optional options argument is passed to find when reloading so you
+ # may do e.g. record.reload(:lock => true) to reload the same record with
+ # an exclusive row lock.
+ def reload(options = nil)
+ clear_aggregation_cache
+ clear_association_cache
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
+ @attributes_cache = {}
+ self
+ end
+
+ # Returns the value of the attribute identified by attr_name after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by attr_name with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+
+ # Allows you to set all the attributes at once by passing in a hash with keys
+ # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
+ # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
+ # specify which attributes *can* be accessed with the +attr_accessible+ macro. Then all the
+ # attributes not included in that won't be allowed to be mass-assigned.
+ def attributes=(new_attributes, guard_protected_attributes = true)
+ return if new_attributes.nil?
+ attributes = new_attributes.dup
+ attributes.stringify_keys!
+
+ multi_parameter_attributes = []
+ attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
+
+ attributes.each do |k, v|
+ k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
+ end
+
+ assign_multiparameter_attributes(multi_parameter_attributes)
+ end
+
+
+ # Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
+ def attributes
+ self.attribute_names.inject({}) do |attrs, name|
+ attrs[name] = read_attribute(name)
+ attrs
+ end
+ end
+
+ # Returns a hash of attributes before typecasting and deserialization.
+ def attributes_before_type_cast
+ self.attribute_names.inject({}) do |attrs, name|
+ attrs[name] = read_attribute_before_type_cast(name)
+ attrs
+ end
+ end
+
+ # Format attributes nicely for inspect.
+ def attribute_for_inspect(attr_name)
+ value = read_attribute(attr_name)
+
+ if value.is_a?(String) && value.length > 50
+ "#{value[0..50]}...".inspect
+ elsif value.is_a?(Date) || value.is_a?(Time)
+ %("#{value.to_s(:db)}")
+ else
+ value.inspect
+ end
+ end
+
+ # 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)
+ value = read_attribute(attribute)
+ !value.blank?
+ end
+
+ # Returns true if the given attribute is in the attributes hash
+ def has_attribute?(attr_name)
+ @attributes.has_key?(attr_name.to_s)
+ end
+
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
+ def attribute_names
+ @attributes.keys.sort
+ end
+
+ # Returns the column object for the named attribute.
+ def column_for_attribute(name)
+ self.class.columns_hash[name.to_s]
+ end
+
+ # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
+ def ==(comparison_object)
+ comparison_object.equal?(self) ||
+ (comparison_object.instance_of?(self.class) &&
+ comparison_object.id == id &&
+ !comparison_object.new_record?)
+ end
+
+ # Delegates to ==
+ def eql?(comparison_object)
+ self == (comparison_object)
+ end
+
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
+ def hash
+ id.hash
+ end
+
+ # Freeze the attributes hash such that associations are still accessible, even on destroyed records.
+ def freeze
+ @attributes.freeze; self
+ end
+
+ # Returns +true+ if the attributes hash has been frozen.
+ def frozen?
+ @attributes.frozen?
+ end
+
+ # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
+ # attributes will be marked as read only since they cannot be saved.
+ def readonly?
+ defined?(@readonly) && @readonly == true
+ end
+
+ # Marks this record as read only.
+ def readonly!
+ @readonly = true
+ end
+
+ # Returns the contents of the record as a nicely formatted string.
+ def inspect
+ attributes_as_nice_string = self.class.column_names.collect { |name|
+ if has_attribute?(name) || new_record?
+ "#{name}: #{attribute_for_inspect(name)}"
+ end
+ }.compact.join(", ")
+ "#<#{self.class} #{attributes_as_nice_string}>"
+ end
+
+ private
+ def create_or_update
+ raise ReadOnlyRecord if readonly?
+ result = new_record? ? create : update
+ result != false
+ end
+
+ # Updates the associated record with values matching those of the instance attributes.
+ # Returns the number of affected rows.
+ def update(attribute_names = @attributes.keys)
+ quoted_attributes = attributes_with_quotes(false, false, attribute_names)
+ return 0 if quoted_attributes.empty?
+ connection.update(
+ "UPDATE #{self.class.quoted_table_name} " +
+ "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
+ "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
+ "#{self.class.name} Update"
+ )
+ end
+
+ # Creates a record with values matching those of the instance attributes
+ # and returns its id.
+ def create
+ if self.id.nil? && connection.prefetch_primary_key?(self.class.table_name)
+ self.id = connection.next_sequence_value(self.class.sequence_name)
+ end
+
+ quoted_attributes = attributes_with_quotes
+
+ statement = if quoted_attributes.empty?
+ connection.empty_insert_statement(self.class.table_name)
+ else
+ "INSERT INTO #{self.class.quoted_table_name} " +
+ "(#{quoted_column_names.join(', ')}) " +
+ "VALUES(#{quoted_attributes.values.join(', ')})"
+ end
+
+ self.id = connection.insert(statement, "#{self.class.name} Create",
+ self.class.primary_key, self.id, self.class.sequence_name)
+
+ @new_record = false
+ id
+ end
+
+ # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
+ # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
+ # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
+ # Message class in that example.
+ def ensure_proper_type
+ unless self.class.descends_from_active_record?
+ write_attribute(self.class.inheritance_column, self.class.sti_name)
+ end
+ end
+
+ def convert_number_column_value(value)
+ case value
+ when FalseClass; 0
+ when TrueClass; 1
+ when ''; nil
+ else value
+ end
+ end
+
+ def remove_attributes_protected_from_mass_assignment(attributes)
+ safe_attributes =
+ if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
+ elsif self.class.protected_attributes.nil?
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
+ elsif self.class.accessible_attributes.nil?
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
+ else
+ raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
+ end
+
+ removed_attributes = attributes.keys - safe_attributes.keys
+
+ if removed_attributes.any?
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}"
+ end
+
+ safe_attributes
+ end
+
+ # Removes attributes which have been marked as readonly.
+ def remove_readonly_attributes(attributes)
+ unless self.class.readonly_attributes.nil?
+ attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
+ else
+ attributes
+ end
+ end
+
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
+ def attributes_protected_by_default
+ default = [ self.class.primary_key, self.class.inheritance_column ]
+ default << 'id' unless self.class.primary_key.eql? 'id'
+ default
+ end
+
+ # Returns a copy of the attributes hash where all the values have been safely quoted for use in
+ # an SQL statement.
+ def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
+ quoted = {}
+ connection = self.class.connection
+ attribute_names.each do |name|
+ if column = column_for_attribute(name)
+ quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
+ end
+ end
+ include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
+ end
+
+ # Quote strings appropriately for SQL statements.
+ def quote_value(value, column = nil)
+ self.class.connection.quote(value, column)
+ end
+
+ # Interpolate custom SQL string in instance context.
+ # Optional record argument is meant for custom insert_sql.
+ def interpolate_sql(sql, record = nil)
+ instance_eval("%@#{sql.gsub('@', '\@')}@")
+ end
+
+ # Initializes the attributes array with keys matching the columns from the linked table and
+ # the values matching the corresponding default value of that column, so
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
+ # that instances loaded from the database would.
+ def attributes_from_column_definition
+ self.class.columns.inject({}) do |attributes, column|
+ attributes[column.name] = column.default unless column.name == self.class.primary_key
+ attributes
+ end
+ end
+
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
+ # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
+ def assign_multiparameter_attributes(pairs)
+ execute_callstack_for_multiparameter_attributes(
+ extract_callstack_for_multiparameter_attributes(pairs)
+ )
+ end
+
+ def instantiate_time_object(name, values)
+ if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
+ Time.zone.local(*values)
+ else
+ Time.time_with_datetime_fallback(@@default_timezone, *values)
+ end
+ end
+
+ def execute_callstack_for_multiparameter_attributes(callstack)
+ errors = []
+ callstack.each do |name, values|
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values.empty?
+ send(name + "=", nil)
+ else
+ begin
+ value = if Time == klass
+ instantiate_time_object(name, values)
+ elsif Date == klass
+ begin
+ Date.new(*values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ else
+ klass.new(*values)
+ end
+
+ send(name + "=", value)
+ rescue => ex
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ end
+ end
+ end
+ unless errors.empty?
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
+ end
+ end
+
+ def extract_callstack_for_multiparameter_attributes(pairs)
+ attributes = { }
+
+ for pair in pairs
+ multiparameter_name, value = pair
+ attribute_name = multiparameter_name.split("(").first
+ attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+
+ unless value.empty?
+ attributes[attribute_name] <<
+ [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
+ end
+ end
+
+ attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ end
+
+ def type_cast_attribute_value(multiparameter_name, value)
+ multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
+ end
+
+ def find_parameter_position(multiparameter_name)
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ end
+
+ # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
+ def comma_pair_list(hash)
+ hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
+ end
+
+ def quoted_column_names(attributes = attributes_with_quotes)
+ connection = self.class.connection
+ attributes.keys.collect do |column_name|
+ connection.quote_column_name(column_name)
+ end
+ end
+
+ def self.quoted_table_name
+ self.connection.quote_table_name(self.table_name)
+ end
+
+ def quote_columns(quoter, hash)
+ hash.inject({}) do |quoted, (name, value)|
+ quoted[quoter.quote_column_name(name)] = value
+ quoted
+ end
+ end
+
+ def quoted_comma_pair_list(quoter, hash)
+ comma_pair_list(quote_columns(quoter, hash))
+ end
+
+ def object_from_yaml(string)
+ return string unless string.is_a?(String)
+ YAML::load(string) rescue string
+ end
+
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
+ self.attribute_names.inject(attributes) do |attrs, name|
+ attrs[name] = clone_attribute_value(reader_method, name)
+ attrs
+ end
+ end
+
+ def clone_attribute_value(reader_method, attribute_name)
+ value = send(reader_method, attribute_name)
+ value.duplicable? ? value.clone : value
+ rescue TypeError, NoMethodError
+ value
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/calculations.rb b/vendor/rails/activerecord/lib/active_record/calculations.rb
new file mode 100644
index 0000000..10e8330
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/calculations.rb
@@ -0,0 +1,275 @@
+module ActiveRecord
+ module Calculations #:nodoc:
+ CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include]
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Count operates using three different approaches.
+ #
+ # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
+ # * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present
+ # * Count using options will find the row count matched by the options used.
+ #
+ # The third approach, count using options, accepts an option hash as the only parameter. The options are:
+ #
+ # * :conditions: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * :joins: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
+ # or named associations in the same form used for the :include option, which will perform an INNER JOIN on the associated table(s).
+ # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # Pass :readonly => false to override.
+ # * :include: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
+ # to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
+ # See eager loading under Associations.
+ # * :order: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
+ # * :group: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # * :select: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
+ # include the joined columns.
+ # * :distinct: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
+ #
+ # Examples for counting all:
+ # Person.count # returns the total count of all people
+ #
+ # Examples for counting by column:
+ # Person.count(:age) # returns the total count of all people whose age is present in database
+ #
+ # Examples for count with options:
+ # Person.count(:conditions => "age > 26")
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
+ # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
+ # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
+ #
+ # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead.
+ def count(*args)
+ calculate(:count, *construct_count_options_from_args(*args))
+ end
+
+ # Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options.
+ #
+ # Person.average('age')
+ def average(column_name, options = {})
+ calculate(:avg, column_name, options)
+ end
+
+ # Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ #
+ # Person.minimum('age')
+ def minimum(column_name, options = {})
+ calculate(:min, column_name, options)
+ end
+
+ # Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ #
+ # Person.maximum('age')
+ def maximum(column_name, options = {})
+ calculate(:max, column_name, options)
+ end
+
+ # Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
+ #
+ # Person.sum('age')
+ def sum(column_name, options = {})
+ calculate(:sum, column_name, options) || 0
+ end
+
+ # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
+ # Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query.
+ #
+ # There are two basic forms of output:
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
+ # * Grouped values: This returns an ordered hash of the values and groups them by the :group option. It takes either a column name, or the name
+ # of a belongs_to association.
+ #
+ # values = Person.maximum(:age, :group => 'last_name')
+ # puts values["Drake"]
+ # => 43
+ #
+ # drake = Family.find_by_last_name('Drake')
+ # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
+ # puts values[drake]
+ # => 43
+ #
+ # values.each do |family, max_age|
+ # ...
+ # end
+ #
+ # Options:
+ # * :conditions - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
+ # * :include: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
+ # * :joins - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
+ # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
+ # * :order - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
+ # * :group - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
+ # * :select - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
+ # include the joined columns.
+ # * :distinct - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
+ #
+ # Examples:
+ # Person.calculate(:count, :all) # The same as Person.count
+ # Person.average(:age) # SELECT AVG(age) FROM people...
+ # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
+ # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
+ # Person.sum("2 * age")
+ def calculate(operation, column_name, options = {})
+ validate_calculation_options(operation, options)
+ column_name = options[:select] if options[:select]
+ column_name = '*' if column_name == :all
+ column = column_for column_name
+ catch :invalid_query do
+ if options[:group]
+ return execute_grouped_calculation(operation, column_name, column, options)
+ else
+ return execute_simple_calculation(operation, column_name, column, options)
+ end
+ end
+ 0
+ end
+
+ protected
+ def construct_count_options_from_args(*args)
+ options = {}
+ column_name = :all
+
+ # We need to handle
+ # count()
+ # count(:column_name=:all)
+ # count(options={})
+ # count(column_name=:all, options={})
+ case args.size
+ when 1
+ args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
+ when 2
+ column_name, options = args
+ else
+ raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
+ end if args.size > 0
+
+ [column_name, options]
+ end
+
+ def construct_calculation_sql(operation, column_name, options) #:nodoc:
+ operation = operation.to_s.downcase
+ options = options.symbolize_keys
+
+ scope = scope(:find)
+ merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
+ aggregate_alias = column_alias_for(operation, column_name)
+ column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s)
+
+ if operation == 'count'
+ if merged_includes.any?
+ options[:distinct] = true
+ column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
+ end
+
+ if options[:distinct]
+ use_workaround = !connection.supports_count_distinct?
+ end
+ end
+
+ if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i
+ distinct = 'DISTINCT '
+ end
+ sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}"
+
+ # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
+ sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
+
+ sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
+ sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
+ sql << " FROM #{connection.quote_table_name(table_name)} "
+ if merged_includes.any?
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
+ end
+ add_joins!(sql, options, scope)
+ add_conditions!(sql, options[:conditions], scope)
+ add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
+
+ if options[:group]
+ group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
+ sql << " GROUP BY #{options[group_key]} "
+ end
+
+ if options[:group] && options[:having]
+ # FrontBase requires identifiers in the HAVING clause and chokes on function calls
+ if connection.adapter_name == 'FrontBase'
+ options[:having].downcase!
+ options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
+ end
+
+ sql << " HAVING #{options[:having]} "
+ end
+
+ sql << " ORDER BY #{options[:order]} " if options[:order]
+ add_limit!(sql, options, scope)
+ sql << ')' if use_workaround
+ sql
+ end
+
+ def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
+ value = connection.select_value(construct_calculation_sql(operation, column_name, options))
+ type_cast_calculated_value(value, column, operation)
+ end
+
+ def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
+ group_attr = options[:group].to_s
+ association = reflect_on_association(group_attr.to_sym)
+ associated = association && association.macro == :belongs_to # only count belongs_to associations
+ group_field = associated ? association.primary_key_name : group_attr
+ group_alias = column_alias_for(group_field)
+ group_column = column_for group_field
+ sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
+ calculated_data = connection.select_all(sql)
+ aggregate_alias = column_alias_for(operation, column_name)
+
+ if association
+ key_ids = calculated_data.collect { |row| row[group_alias] }
+ key_records = association.klass.base_class.find(key_ids)
+ key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
+ end
+
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
+ key = type_cast_calculated_value(row[group_alias], group_column)
+ key = key_records[key] if associated
+ value = row[aggregate_alias]
+ all[key] = type_cast_calculated_value(value, column, operation)
+ all
+ end
+ end
+
+ private
+ def validate_calculation_options(operation, options = {})
+ options.assert_valid_keys(CALCULATIONS_OPTIONS)
+ end
+
+ # Converts the given keys to the value that the database adapter returns as
+ # a usable column name:
+ #
+ # column_alias_for("users.id") # => "users_id"
+ # column_alias_for("sum(id)") # => "sum_id"
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
+ # column_alias_for("count(*)") # => "count_all"
+ # column_alias_for("count", "id") # => "count_id"
+ def column_alias_for(*keys)
+ connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
+ end
+
+ def column_for(field)
+ field_name = field.to_s.split('.').last
+ columns.detect { |c| c.name.to_s == field_name }
+ end
+
+ def type_cast_calculated_value(value, column, operation = nil)
+ operation = operation.to_s.downcase
+ case operation
+ when 'count' then value.to_i
+ when 'avg' then value && value.to_f
+ else column ? column.type_cast(value) : value
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/callbacks.rb b/vendor/rails/activerecord/lib/active_record/callbacks.rb
new file mode 100644
index 0000000..41ec5c5
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/callbacks.rb
@@ -0,0 +1,312 @@
+require 'observer'
+
+module ActiveRecord
+ # Callbacks are hooks into the lifecycle of an Active Record object that allow you to trigger logic
+ # before or after an alteration of the object state. This can be used to make sure that associated and
+ # dependent objects are deleted when destroy is called (by overwriting +before_destroy+) or to massage attributes
+ # before they're validated (by overwriting +before_validation+). As an example of the callbacks initiated, consider
+ # the Base#save call:
+ #
+ # * (-) save
+ # * (-) valid
+ # * (1) before_validation
+ # * (2) before_validation_on_create
+ # * (-) validate
+ # * (-) validate_on_create
+ # * (3) after_validation
+ # * (4) after_validation_on_create
+ # * (5) before_save
+ # * (6) before_create
+ # * (-) create
+ # * (7) after_create
+ # * (8) after_save
+ #
+ # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
+ # Active Record lifecycle.
+ #
+ # Examples:
+ # class CreditCard < ActiveRecord::Base
+ # # Strip everything but digits, so the user can specify "555 234 34" or
+ # # "5552-3434" or both will mean "55523434"
+ # def before_validation_on_create
+ # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
+ # end
+ # end
+ #
+ # class Subscription < ActiveRecord::Base
+ # before_create :record_signup
+ #
+ # private
+ # def record_signup
+ # self.signed_up_on = Date.today
+ # end
+ # end
+ #
+ # class Firm < ActiveRecord::Base
+ # # Destroys the associated clients and people when the firm is destroyed
+ # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
+ # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
+ # end
+ #
+ # == Inheritable callback queues
+ #
+ # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
+ # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
+ # hierarchy. Example:
+ #
+ # class Topic < ActiveRecord::Base
+ # before_destroy :destroy_author
+ # end
+ #
+ # class Reply < Topic
+ # before_destroy :destroy_readers
+ # end
+ #
+ # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run, both +destroy_author+ and
+ # +destroy_readers+ are called. Contrast this to the situation where we've implemented the save behavior through overwriteable
+ # methods:
+ #
+ # class Topic < ActiveRecord::Base
+ # def before_destroy() destroy_author end
+ # end
+ #
+ # class Reply < Topic
+ # def before_destroy() destroy_readers end
+ # end
+ #
+ # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
+ # you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
+ # when you want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
+ #
+ # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
+ # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
+ # be inherited.
+ #
+ # == Types of callbacks
+ #
+ # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
+ # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
+ # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
+ # eval methods are deprecated.
+ #
+ # The method reference callbacks work by specifying a protected or private method available in the object, like this:
+ #
+ # class Topic < ActiveRecord::Base
+ # before_destroy :delete_parents
+ #
+ # private
+ # def delete_parents
+ # self.class.delete_all "parent_id = #{id}"
+ # end
+ # end
+ #
+ # The callback objects have methods named after the callback called with the record as the only parameter, such as:
+ #
+ # class BankAccount < ActiveRecord::Base
+ # before_save EncryptionWrapper.new("credit_card_number")
+ # after_save EncryptionWrapper.new("credit_card_number")
+ # after_initialize EncryptionWrapper.new("credit_card_number")
+ # end
+ #
+ # class EncryptionWrapper
+ # def initialize(attribute)
+ # @attribute = attribute
+ # end
+ #
+ # def before_save(record)
+ # record.credit_card_number = encrypt(record.credit_card_number)
+ # end
+ #
+ # def after_save(record)
+ # record.credit_card_number = decrypt(record.credit_card_number)
+ # end
+ #
+ # alias_method :after_find, :after_save
+ #
+ # private
+ # def encrypt(value)
+ # # Secrecy is committed
+ # end
+ #
+ # def decrypt(value)
+ # # Secrecy is unveiled
+ # end
+ # end
+ #
+ # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
+ # a method by the name of the callback messaged.
+ #
+ # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
+ # which will then be evaluated within the binding of the callback. Example:
+ #
+ # class Topic < ActiveRecord::Base
+ # before_destroy 'self.class.delete_all "parent_id = #{id}"'
+ # end
+ #
+ # Notice that single quotes (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these
+ # inline callbacks can be stacked just like the regular ones:
+ #
+ # class Topic < ActiveRecord::Base
+ # before_destroy 'self.class.delete_all "parent_id = #{id}"',
+ # 'puts "Evaluated after parents are destroyed"'
+ # end
+ #
+ # == The +after_find+ and +after_initialize+ exceptions
+ #
+ # Because +after_find+ and +after_initialize+ are called for each object found and instantiated by a finder, such as Base.find(:all), we've had
+ # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, +after_find+ and
+ # +after_initialize+ will only be run if an explicit implementation is defined (def after_find). In that case, all of the
+ # callback types will be called.
+ #
+ # == before_validation* returning statements
+ #
+ # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be aborted and Base#save will return +false+.
+ # If Base#save! is called it will raise a RecordNotSaved exception.
+ # Nothing will be appended to the errors object.
+ #
+ # == Canceling callbacks
+ #
+ # If a before_* callback returns +false+, all the later callbacks and the associated action are cancelled. If an after_* callback returns
+ # +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
+ # defined as methods on the model, which are called last.
+ module Callbacks
+ CALLBACKS = %w(
+ after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
+ after_validation_on_update before_destroy after_destroy
+ )
+
+ def self.included(base) #:nodoc:
+ base.extend Observable
+
+ [:create_or_update, :valid?, :create, :update, :destroy].each do |method|
+ base.send :alias_method_chain, method, :callbacks
+ end
+
+ base.send :include, ActiveSupport::Callbacks
+ base.define_callbacks *CALLBACKS
+ end
+
+ # Is called when the object was instantiated by one of the finders, like Base.find.
+ #def after_find() end
+
+ # Is called after the object has been instantiated by a call to Base.new.
+ #def after_initialize() end
+
+ # Is called _before_ Base.save (regardless of whether it's a +create+ or +update+ save).
+ def before_save() end
+
+ # Is called _after_ Base.save (regardless of whether it's a +create+ or +update+ save).
+ #
+ # class Contact < ActiveRecord::Base
+ # after_save { logger.info( 'New contact saved!' ) }
+ # end
+ def after_save() end
+ def create_or_update_with_callbacks #:nodoc:
+ return false if callback(:before_save) == false
+ result = create_or_update_without_callbacks
+ callback(:after_save)
+ result
+ end
+ private :create_or_update_with_callbacks
+
+ # Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
+ def before_create() end
+
+ # Is called _after_ Base.save on new objects that haven't been saved yet (no record exists).
+ def after_create() end
+ def create_with_callbacks #:nodoc:
+ return false if callback(:before_create) == false
+ result = create_without_callbacks
+ callback(:after_create)
+ result
+ end
+ private :create_with_callbacks
+
+ # Is called _before_ Base.save on existing objects that have a record.
+ def before_update() end
+
+ # Is called _after_ Base.save on existing objects that have a record.
+ def after_update() end
+
+ def update_with_callbacks(*args) #:nodoc:
+ return false if callback(:before_update) == false
+ result = update_without_callbacks(*args)
+ callback(:after_update)
+ result
+ end
+ private :update_with_callbacks
+
+ # Is called _before_ Validations.validate (which is part of the Base.save call).
+ def before_validation() end
+
+ # Is called _after_ Validations.validate (which is part of the Base.save call).
+ def after_validation() end
+
+ # Is called _before_ Validations.validate (which is part of the Base.save call) on new objects
+ # that haven't been saved yet (no record exists).
+ def before_validation_on_create() end
+
+ # Is called _after_ Validations.validate (which is part of the Base.save call) on new objects
+ # that haven't been saved yet (no record exists).
+ def after_validation_on_create() end
+
+ # Is called _before_ Validations.validate (which is part of the Base.save call) on
+ # existing objects that have a record.
+ def before_validation_on_update() end
+
+ # Is called _after_ Validations.validate (which is part of the Base.save call) on
+ # existing objects that have a record.
+ def after_validation_on_update() end
+
+ def valid_with_callbacks? #:nodoc:
+ return false if callback(:before_validation) == false
+ if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
+ return false if result == false
+
+ result = valid_without_callbacks?
+
+ callback(:after_validation)
+ if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
+
+ return result
+ end
+
+ # Is called _before_ Base.destroy.
+ #
+ # Note: If you need to _destroy_ or _nullify_ associated records first,
+ # use the :dependent option on your associations.
+ def before_destroy() end
+
+ # Is called _after_ Base.destroy (and all the attributes have been frozen).
+ #
+ # class Contact < ActiveRecord::Base
+ # after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
+ # end
+ def after_destroy() end
+ def destroy_with_callbacks #:nodoc:
+ return false if callback(:before_destroy) == false
+ result = destroy_without_callbacks
+ callback(:after_destroy)
+ result
+ end
+
+ private
+ def callback(method)
+ notify(method)
+
+ result = run_callbacks(method) { |result, object| result == false }
+
+ if result != false && respond_to_without_attributes?(method)
+ result = send(method)
+ end
+
+ return result
+ end
+
+ def notify(method) #:nodoc:
+ self.class.changed
+ self.class.notify_observers(method, self)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
new file mode 100644
index 0000000..2a8807f
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -0,0 +1,309 @@
+require 'set'
+
+module ActiveRecord
+ class Base
+ class ConnectionSpecification #:nodoc:
+ attr_reader :config, :adapter_method
+ def initialize (config, adapter_method)
+ @config, @adapter_method = config, adapter_method
+ end
+ end
+
+ # Check for activity after at least +verification_timeout+ seconds.
+ # Defaults to 0 (always check.)
+ cattr_accessor :verification_timeout, :instance_writer => false
+ @@verification_timeout = 0
+
+ # The class -> [adapter_method, config] map
+ @@defined_connections = {}
+
+ # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
+ @@active_connections = {}
+
+ class << self
+ # Retrieve the connection cache.
+ def thread_safe_active_connections #:nodoc:
+ @@active_connections[Thread.current.object_id] ||= {}
+ end
+
+ def single_threaded_active_connections #:nodoc:
+ @@active_connections
+ end
+
+ # pick up the right active_connection method from @@allow_concurrency
+ if @@allow_concurrency
+ alias_method :active_connections, :thread_safe_active_connections
+ else
+ alias_method :active_connections, :single_threaded_active_connections
+ end
+
+ # set concurrency support flag (not thread safe, like most of the methods in this file)
+ def allow_concurrency=(threaded) #:nodoc:
+ logger.debug "allow_concurrency=#{threaded}" if logger
+ return if @@allow_concurrency == threaded
+ clear_all_cached_connections!
+ @@allow_concurrency = threaded
+ method_prefix = threaded ? "thread_safe" : "single_threaded"
+ sing = (class << self; self; end)
+ [:active_connections, :scoped_methods].each do |method|
+ sing.send(:alias_method, method, "#{method_prefix}_#{method}")
+ end
+ log_connections if logger
+ end
+
+ def active_connection_name #:nodoc:
+ @active_connection_name ||=
+ if active_connections[name] || @@defined_connections[name]
+ name
+ elsif self == ActiveRecord::Base
+ nil
+ else
+ superclass.active_connection_name
+ end
+ end
+
+ def clear_active_connection_name #:nodoc:
+ @active_connection_name = nil
+ subclasses.each { |klass| klass.clear_active_connection_name }
+ end
+
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work unrelated
+ # to any of the specific Active Records.
+ def connection
+ if defined?(@active_connection_name) && (conn = active_connections[@active_connection_name])
+ conn
+ else
+ # retrieve_connection sets the cache key.
+ conn = retrieve_connection
+ active_connections[@active_connection_name] = conn
+ end
+ end
+
+ # Clears the cache which maps classes to connections.
+ def clear_active_connections!
+ clear_cache!(@@active_connections) do |name, conn|
+ conn.disconnect!
+ end
+ end
+
+ # Clears the cache which maps classes
+ def clear_reloadable_connections!
+ if @@allow_concurrency
+ # With concurrent connections @@active_connections is
+ # a hash keyed by thread id.
+ @@active_connections.each do |thread_id, conns|
+ conns.each do |name, conn|
+ if conn.requires_reloading?
+ conn.disconnect!
+ @@active_connections[thread_id].delete(name)
+ end
+ end
+ end
+ else
+ @@active_connections.each do |name, conn|
+ if conn.requires_reloading?
+ conn.disconnect!
+ @@active_connections.delete(name)
+ end
+ end
+ end
+ end
+
+ # Verify active connections.
+ def verify_active_connections! #:nodoc:
+ if @@allow_concurrency
+ remove_stale_cached_threads!(@@active_connections) do |name, conn|
+ conn.disconnect!
+ end
+ end
+
+ active_connections.each_value do |connection|
+ connection.verify!(@@verification_timeout)
+ end
+ end
+
+ private
+ def clear_cache!(cache, thread_id = nil, &block)
+ if cache
+ if @@allow_concurrency
+ thread_id ||= Thread.current.object_id
+ thread_cache, cache = cache, cache[thread_id]
+ return unless cache
+ end
+
+ cache.each(&block) if block_given?
+ cache.clear
+ end
+ ensure
+ if thread_cache && @@allow_concurrency
+ thread_cache.delete(thread_id)
+ end
+ end
+
+ # Remove stale threads from the cache.
+ def remove_stale_cached_threads!(cache, &block)
+ stale = Set.new(cache.keys)
+
+ Thread.list.each do |thread|
+ stale.delete(thread.object_id) if thread.alive?
+ end
+
+ stale.each do |thread_id|
+ clear_cache!(cache, thread_id, &block)
+ end
+ end
+
+ def clear_all_cached_connections!
+ if @@allow_concurrency
+ @@active_connections.each_value do |connection_hash_for_thread|
+ connection_hash_for_thread.each_value {|conn| conn.disconnect! }
+ connection_hash_for_thread.clear
+ end
+ else
+ @@active_connections.each_value {|conn| conn.disconnect! }
+ end
+ @@active_connections.clear
+ end
+ end
+
+ # Returns the connection currently associated with the class. This can
+ # also be used to "borrow" the connection to do database work that isn't
+ # easily done without going straight to SQL.
+ def connection
+ self.class.connection
+ end
+
+ # Establishes the connection to the database. Accepts a hash as input where
+ # the :adapter key must be specified with the name of a database adapter (in lower-case)
+ # example for regular databases (MySQL, Postgresql, etc):
+ #
+ # ActiveRecord::Base.establish_connection(
+ # :adapter => "mysql",
+ # :host => "localhost",
+ # :username => "myuser",
+ # :password => "mypass",
+ # :database => "somedatabase"
+ # )
+ #
+ # Example for SQLite database:
+ #
+ # ActiveRecord::Base.establish_connection(
+ # :adapter => "sqlite",
+ # :database => "path/to/dbfile"
+ # )
+ #
+ # Also accepts keys as strings (for parsing from YAML for example):
+ #
+ # ActiveRecord::Base.establish_connection(
+ # "adapter" => "sqlite",
+ # "database" => "path/to/dbfile"
+ # )
+ #
+ # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
+ # may be returned on an error.
+ def self.establish_connection(spec = nil)
+ case spec
+ when nil
+ raise AdapterNotSpecified unless defined? RAILS_ENV
+ establish_connection(RAILS_ENV)
+ when ConnectionSpecification
+ clear_active_connection_name
+ @active_connection_name = name
+ @@defined_connections[name] = spec
+ when Symbol, String
+ if configuration = configurations[spec.to_s]
+ establish_connection(configuration)
+ else
+ raise AdapterNotSpecified, "#{spec} database is not configured"
+ end
+ else
+ spec = spec.symbolize_keys
+ unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end
+
+ begin
+ require 'rubygems'
+ gem "activerecord-#{spec[:adapter]}-adapter"
+ require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
+ rescue LoadError
+ begin
+ require "active_record/connection_adapters/#{spec[:adapter]}_adapter"
+ rescue LoadError
+ raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{$!})"
+ end
+ end
+
+ adapter_method = "#{spec[:adapter]}_connection"
+ if !respond_to?(adapter_method)
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
+ end
+
+ remove_connection
+ establish_connection(ConnectionSpecification.new(spec, adapter_method))
+ end
+ end
+
+ # Locate the connection of the nearest super class. This can be an
+ # active or defined connection: if it is the latter, it will be
+ # opened and set as the active connection for the class it was defined
+ # for (not necessarily the current class).
+ def self.retrieve_connection #:nodoc:
+ # Name is nil if establish_connection hasn't been called for
+ # some class along the inheritance chain up to AR::Base yet.
+ if name = active_connection_name
+ if conn = active_connections[name]
+ # Verify the connection.
+ conn.verify!(@@verification_timeout)
+ elsif spec = @@defined_connections[name]
+ # Activate this connection specification.
+ klass = name.constantize
+ klass.connection = spec
+ conn = active_connections[name]
+ end
+ end
+
+ conn or raise ConnectionNotEstablished
+ end
+
+ # Returns true if a connection that's accessible to this class has already been opened.
+ def self.connected?
+ active_connections[active_connection_name] ? true : false
+ end
+
+ # Remove the connection for this class. This will close the active
+ # connection and the defined connection (if they exist). The result
+ # can be used as an argument for establish_connection, for easily
+ # re-establishing the connection.
+ def self.remove_connection(klass=self)
+ spec = @@defined_connections[klass.name]
+ konn = active_connections[klass.name]
+ @@defined_connections.delete_if { |key, value| value == spec }
+ active_connections.delete_if { |key, value| value == konn }
+ konn.disconnect! if konn
+ spec.config if spec
+ end
+
+ # Set the connection for the class.
+ def self.connection=(spec) #:nodoc:
+ if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
+ active_connections[name] = spec
+ elsif spec.kind_of?(ConnectionSpecification)
+ config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
+ self.connection = self.send(spec.adapter_method, config)
+ elsif spec.nil?
+ raise ConnectionNotEstablished
+ else
+ establish_connection spec
+ end
+ end
+
+ # connection state logging
+ def self.log_connections #:nodoc:
+ if logger
+ logger.info "Defined connections: #{@@defined_connections.inspect}"
+ logger.info "Active connections: #{active_connections.inspect}"
+ logger.info "Active connection name: #{@active_connection_name}"
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
new file mode 100644
index 0000000..5358491
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -0,0 +1,176 @@
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ module DatabaseStatements
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select_all(sql, name = nil)
+ select(sql, name)
+ end
+
+ # Returns a record hash with the column names as keys and column values
+ # as values.
+ def select_one(sql, name = nil)
+ result = select_all(sql, name)
+ result.first if result
+ end
+
+ # Returns a single value from a record
+ def select_value(sql, name = nil)
+ if result = select_one(sql, name)
+ result.values.first
+ end
+ end
+
+ # Returns an array of the values of the first column in a select:
+ # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
+ def select_values(sql, name = nil)
+ result = select_rows(sql, name)
+ result.map { |v| v[0] }
+ end
+
+ # Returns an array of arrays containing the field values.
+ # Order is the same as that returned by +columns+.
+ def select_rows(sql, name = nil)
+ raise NotImplementedError, "select_rows is an abstract method"
+ end
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ raise NotImplementedError, "execute is an abstract method"
+ end
+
+ # Returns the last auto-generated ID from the affected table.
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ insert_sql(sql, name, pk, id_value, sequence_name)
+ end
+
+ # Executes the update statement and returns the number of rows affected.
+ def update(sql, name = nil)
+ update_sql(sql, name)
+ end
+
+ # Executes the delete statement and returns the number of rows affected.
+ def delete(sql, name = nil)
+ delete_sql(sql, name)
+ end
+
+ # Wrap a block in a transaction. Returns result of block.
+ def transaction(start_db_transaction = true)
+ transaction_open = false
+ begin
+ if block_given?
+ if start_db_transaction
+ begin_db_transaction
+ transaction_open = true
+ end
+ yield
+ end
+ rescue Exception => database_transaction_rollback
+ if transaction_open
+ transaction_open = false
+ rollback_db_transaction
+ end
+ raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
+ end
+ ensure
+ if transaction_open
+ begin
+ commit_db_transaction
+ rescue Exception => database_transaction_rollback
+ rollback_db_transaction
+ raise
+ end
+ end
+ end
+
+ # Begins the transaction (and turns off auto-committing).
+ def begin_db_transaction() end
+
+ # Commits the transaction (and turns on auto-committing).
+ def commit_db_transaction() end
+
+ # Rolls back the transaction (and turns on auto-committing). Must be
+ # done if the transaction block raises an exception or returns false.
+ def rollback_db_transaction() end
+
+ # Alias for add_limit_offset!.
+ def add_limit!(sql, options)
+ add_limit_offset!(sql, options) if options
+ end
+
+ # Appends +LIMIT+ and +OFFSET+ options to an SQL statement.
+ # This method *modifies* the +sql+ parameter.
+ # ===== Examples
+ # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50})
+ # generates
+ # SELECT * FROM suppliers LIMIT 10 OFFSET 50
+ def add_limit_offset!(sql, options)
+ if limit = options[:limit]
+ sql << " LIMIT #{sanitize_limit(limit)}"
+ if offset = options[:offset]
+ sql << " OFFSET #{offset.to_i}"
+ end
+ end
+ sql
+ end
+
+ def sanitize_limit(limit)
+ limit.to_s[/,/] ? limit.split(',').map{ |i| i.to_i }.join(',') : limit.to_i
+ end
+
+ # Appends a locking clause to an SQL statement.
+ # This method *modifies* the +sql+ parameter.
+ # # SELECT * FROM suppliers FOR UPDATE
+ # add_lock! 'SELECT * FROM suppliers', :lock => true
+ # add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
+ def add_lock!(sql, options)
+ case lock = options[:lock]
+ when true; sql << ' FOR UPDATE'
+ when String; sql << " #{lock}"
+ end
+ end
+
+ def default_sequence_name(table, column)
+ nil
+ end
+
+ # Set the sequence to the max value of the table's column.
+ def reset_sequence!(table, column, sequence = nil)
+ # Do nothing by default. Implement for PostgreSQL, Oracle, ...
+ end
+
+ # Inserts the given fixture into the table. Overridden in adapters that require
+ # something beyond a simple insert (eg. Oracle).
+ def insert_fixture(fixture, table_name)
+ execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
+ end
+
+ def empty_insert_statement(table_name)
+ "INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)"
+ end
+
+ protected
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil)
+ raise NotImplementedError, "select is an abstract method"
+ end
+
+ # Returns the last auto-generated ID from the affected table.
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ execute(sql, name)
+ id_value
+ end
+
+ # Executes the update statement and returns the number of rows affected.
+ def update_sql(sql, name = nil)
+ execute(sql, name)
+ end
+
+ # Executes the delete statement and returns the number of rows affected.
+ def delete_sql(sql, name = nil)
+ update_sql(sql, name)
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
new file mode 100644
index 0000000..2afd606
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -0,0 +1,93 @@
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ module QueryCache
+ class << self
+ def included(base)
+ base.class_eval do
+ attr_accessor :query_cache_enabled
+ alias_method_chain :columns, :query_cache
+ alias_method_chain :select_all, :query_cache
+ end
+
+ dirties_query_cache base, :insert, :update, :delete
+ end
+
+ def dirties_query_cache(base, *method_names)
+ method_names.each do |method_name|
+ base.class_eval <<-end_code, __FILE__, __LINE__
+ def #{method_name}_with_query_dirty(*args)
+ clear_query_cache if @query_cache_enabled
+ #{method_name}_without_query_dirty(*args)
+ end
+
+ alias_method_chain :#{method_name}, :query_dirty
+ end_code
+ end
+ end
+ end
+
+ # Enable the query cache within the block.
+ def cache
+ old, @query_cache_enabled = @query_cache_enabled, true
+ @query_cache ||= {}
+ yield
+ ensure
+ clear_query_cache
+ @query_cache_enabled = old
+ end
+
+ # Disable the query cache within the block.
+ def uncached
+ old, @query_cache_enabled = @query_cache_enabled, false
+ yield
+ ensure
+ @query_cache_enabled = old
+ end
+
+ # Clears the query cache.
+ #
+ # One reason you may wish to call this method explicitly is between queries
+ # that ask the database to randomize results. Otherwise the cache would see
+ # the same SQL query and repeatedly return the same result each time, silently
+ # undermining the randomness you were expecting.
+ def clear_query_cache
+ @query_cache.clear if @query_cache
+ end
+
+ def select_all_with_query_cache(*args)
+ if @query_cache_enabled
+ cache_sql(args.first) { select_all_without_query_cache(*args) }
+ else
+ select_all_without_query_cache(*args)
+ end
+ end
+
+ def columns_with_query_cache(*args)
+ if @query_cache_enabled
+ @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args)
+ else
+ columns_without_query_cache(*args)
+ end
+ end
+
+ private
+ def cache_sql(sql)
+ result =
+ if @query_cache.has_key?(sql)
+ log_info(sql, "CACHE", 0.0)
+ @query_cache[sql]
+ else
+ @query_cache[sql] = yield
+ end
+
+ if Array === result
+ result.collect { |row| row.dup }
+ else
+ result.duplicable? ? result.dup : result
+ end
+ rescue TypeError
+ result
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
new file mode 100644
index 0000000..3a7bf35
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -0,0 +1,69 @@
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ module Quoting
+ # Quotes the column value to help prevent
+ # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
+ def quote(value, column = nil)
+ # records are quoted as their primary key
+ return value.quoted_id if value.respond_to?(:quoted_id)
+
+ case value
+ when String, ActiveSupport::Multibyte::Chars
+ value = value.to_s
+ if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ "#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
+ elsif column && [:integer, :float].include?(column.type)
+ value = column.type == :integer ? value.to_i : value.to_f
+ value.to_s
+ else
+ "#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode)
+ end
+ when NilClass then "NULL"
+ when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
+ when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
+ when Float, Fixnum, Bignum then value.to_s
+ # BigDecimals need to be output in a non-normalized form and quoted.
+ when BigDecimal then value.to_s('F')
+ else
+ if value.acts_like?(:date) || value.acts_like?(:time)
+ "'#{quoted_date(value)}'"
+ else
+ "#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'"
+ end
+ end
+ end
+
+ # Quotes a string, escaping any ' (single quote) and \ (backslash)
+ # characters.
+ def quote_string(s)
+ s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
+ end
+
+ # Quotes the column name. Defaults to no quoting.
+ def quote_column_name(column_name)
+ column_name
+ end
+
+ # Quotes the table name. Defaults to column name quoting.
+ def quote_table_name(table_name)
+ quote_column_name(table_name)
+ end
+
+ def quoted_true
+ "'t'"
+ end
+
+ def quoted_false
+ "'f'"
+ end
+
+ def quoted_date(value)
+ value.to_s(:db)
+ end
+
+ def quoted_string_prefix
+ ''
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
new file mode 100644
index 0000000..f968b9b
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -0,0 +1,661 @@
+require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+module ActiveRecord
+ module ConnectionAdapters #:nodoc:
+ # An abstract definition of a column in a table.
+ class Column
+ module Format
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
+ ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
+ end
+
+ attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale
+ attr_accessor :primary
+
+ # Instantiates a new column in the table.
+ #
+ # +name+ is the column's name, such as supplier_id in supplier_id int(11).
+ # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'.
+ # +sql_type+ is only used to extract the column's length, if necessary. For example +60+ in company_name varchar(60).
+ # +null+ determines if this column allows +NULL+ values.
+ def initialize(name, default, sql_type = nil, null = true)
+ @name, @sql_type, @null = name, sql_type, null
+ @limit, @precision, @scale = extract_limit(sql_type), extract_precision(sql_type), extract_scale(sql_type)
+ @type = simplified_type(sql_type)
+ @default = extract_default(default)
+
+ @primary = nil
+ end
+
+ def text?
+ [:string, :text].include? type
+ end
+
+ def number?
+ [:float, :integer, :decimal].include? type
+ end
+
+ # Returns the Ruby class that corresponds to the abstract data type.
+ def klass
+ case type
+ when :integer then Fixnum
+ when :float then Float
+ when :decimal then BigDecimal
+ when :datetime then Time
+ when :date then Date
+ when :timestamp then Time
+ when :time then Time
+ when :text, :string then String
+ when :binary then String
+ when :boolean then Object
+ end
+ end
+
+ # Casts value (which is a String) to an appropriate instance.
+ def type_cast(value)
+ return nil if value.nil?
+ case type
+ when :string then value
+ when :text then value
+ when :integer then value.to_i rescue value ? 1 : 0
+ when :float then value.to_f
+ when :decimal then self.class.value_to_decimal(value)
+ when :datetime then self.class.string_to_time(value)
+ when :timestamp then self.class.string_to_time(value)
+ when :time then self.class.string_to_dummy_time(value)
+ when :date then self.class.string_to_date(value)
+ when :binary then self.class.binary_to_string(value)
+ when :boolean then self.class.value_to_boolean(value)
+ else value
+ end
+ end
+
+ def type_cast_code(var_name)
+ case type
+ when :string then nil
+ when :text then nil
+ when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
+ when :float then "#{var_name}.to_f"
+ when :decimal then "#{self.class.name}.value_to_decimal(#{var_name})"
+ when :datetime then "#{self.class.name}.string_to_time(#{var_name})"
+ when :timestamp then "#{self.class.name}.string_to_time(#{var_name})"
+ when :time then "#{self.class.name}.string_to_dummy_time(#{var_name})"
+ when :date then "#{self.class.name}.string_to_date(#{var_name})"
+ when :binary then "#{self.class.name}.binary_to_string(#{var_name})"
+ when :boolean then "#{self.class.name}.value_to_boolean(#{var_name})"
+ else nil
+ end
+ end
+
+ # Returns the human name of the column name.
+ #
+ # ===== Examples
+ # Column.new('sales_stage', ...).human_name # => 'Sales stage'
+ def human_name
+ Base.human_attribute_name(@name)
+ end
+
+ def extract_default(default)
+ type_cast(default)
+ end
+
+ class << self
+ # Used to convert from Strings to BLOBs
+ def string_to_binary(value)
+ value
+ end
+
+ # Used to convert from BLOBs to Strings
+ def binary_to_string(value)
+ value
+ end
+
+ def string_to_date(string)
+ return string unless string.is_a?(String)
+ return nil if string.empty?
+
+ fast_string_to_date(string) || fallback_string_to_date(string)
+ end
+
+ def string_to_time(string)
+ return string unless string.is_a?(String)
+ return nil if string.empty?
+
+ fast_string_to_time(string) || fallback_string_to_time(string)
+ end
+
+ def string_to_dummy_time(string)
+ return string unless string.is_a?(String)
+ return nil if string.empty?
+
+ string_to_time "2000-01-01 #{string}"
+ end
+
+ # convert something to a boolean
+ def value_to_boolean(value)
+ if value == true || value == false
+ value
+ else
+ %w(true t 1).include?(value.to_s.downcase)
+ end
+ end
+
+ # convert something to a BigDecimal
+ def value_to_decimal(value)
+ # Using .class is faster than .is_a? and
+ # subclasses of BigDecimal will be handled
+ # in the else clause
+ if value.class == BigDecimal
+ value
+ elsif value.respond_to?(:to_d)
+ value.to_d
+ else
+ value.to_s.to_d
+ end
+ end
+
+ protected
+ # '0.123456' -> 123456
+ # '1.123456' -> 123456
+ def microseconds(time)
+ ((time[:sec_fraction].to_f % 1) * 1_000_000).to_i
+ end
+
+ def new_date(year, mon, mday)
+ if year && year != 0
+ Date.new(year, mon, mday) rescue nil
+ end
+ end
+
+ def new_time(year, mon, mday, hour, min, sec, microsec)
+ # Treat 0000-00-00 00:00:00 as nil.
+ return nil if year.nil? || year == 0
+
+ Time.time_with_datetime_fallback(Base.default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
+ end
+
+ def fast_string_to_date(string)
+ if string =~ Format::ISO_DATE
+ new_date $1.to_i, $2.to_i, $3.to_i
+ end
+ end
+
+ # Doesn't handle time zones.
+ def fast_string_to_time(string)
+ if string =~ Format::ISO_DATETIME
+ microsec = ($7.to_f * 1_000_000).to_i
+ new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
+ end
+ end
+
+ def fallback_string_to_date(string)
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
+ end
+
+ def fallback_string_to_time(string)
+ time_hash = Date._parse(string)
+ time_hash[:sec_fraction] = microseconds(time_hash)
+
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
+ end
+ end
+
+ private
+ def extract_limit(sql_type)
+ $1.to_i if sql_type =~ /\((.*)\)/
+ end
+
+ def extract_precision(sql_type)
+ $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i
+ end
+
+ def extract_scale(sql_type)
+ case sql_type
+ when /^(numeric|decimal|number)\((\d+)\)/i then 0
+ when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i
+ end
+ end
+
+ def simplified_type(field_type)
+ case field_type
+ when /int/i
+ :integer
+ when /float|double/i
+ :float
+ when /decimal|numeric|number/i
+ extract_scale(field_type) == 0 ? :integer : :decimal
+ when /datetime/i
+ :datetime
+ when /timestamp/i
+ :timestamp
+ when /time/i
+ :time
+ when /date/i
+ :date
+ when /clob/i, /text/i
+ :text
+ when /blob/i, /binary/i
+ :binary
+ when /char/i, /string/i
+ :string
+ when /boolean/i
+ :boolean
+ end
+ end
+ end
+
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc:
+ end
+
+ class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc:
+
+ def sql_type
+ base.type_to_sql(type.to_sym, limit, precision, scale) rescue type
+ end
+
+ def to_sql
+ column_sql = "#{base.quote_column_name(name)} #{sql_type}"
+ add_column_options!(column_sql, :null => null, :default => default) unless type.to_sym == :primary_key
+ column_sql
+ end
+ alias to_s :to_sql
+
+ private
+
+ def add_column_options!(sql, options)
+ base.add_column_options!(sql, options.merge(:column => self))
+ end
+ end
+
+ # Represents a SQL table in an abstract way.
+ # Columns are stored as a ColumnDefinition in the +columns+ attribute.
+ class TableDefinition
+ attr_accessor :columns
+
+ def initialize(base)
+ @columns = []
+ @base = base
+ end
+
+ # Appends a primary key definition to the table definition.
+ # Can be called multiple times, but this is probably not a good idea.
+ def primary_key(name)
+ column(name, :primary_key)
+ end
+
+ # Returns a ColumnDefinition for the column with name +name+.
+ def [](name)
+ @columns.find {|column| column.name.to_s == name.to_s}
+ end
+
+ # Instantiates a new column for the table.
+ # The +type+ parameter is normally one of the migrations native types,
+ # which is one of the following:
+ # :primary_key, :string, :text,
+ # :integer, :float, :decimal,
+ # :datetime, :timestamp, :time,
+ # :date, :binary, :boolean.
+ #
+ # You may use a type not in this list as long as it is supported by your
+ # database (for example, "polygon" in MySQL), but this will not be database
+ # agnostic and should usually be avoided.
+ #
+ # Available options are (none of these exists by default):
+ # * :limit -
+ # Requests a maximum column length (:string, :text,
+ # :binary or :integer columns only)
+ # * :default -
+ # The column's default value. Use nil for NULL.
+ # * :null -
+ # Allows or disallows +NULL+ values in the column. This option could
+ # have been named :null_allowed.
+ # * :precision -
+ # Specifies the precision for a :decimal column.
+ # * :scale -
+ # Specifies the scale for a :decimal column.
+ #
+ # Please be aware of different RDBMS implementations behavior with
+ # :decimal columns:
+ # * The SQL standard says the default scale should be 0, :scale <=
+ # :precision, and makes no comments about the requirements of
+ # :precision.
+ # * MySQL: :precision [1..63], :scale [0..30].
+ # Default is (10,0).
+ # * PostgreSQL: :precision [1..infinity],
+ # :scale [0..infinity]. No default.
+ # * SQLite2: Any :precision and :scale may be used.
+ # Internal storage as strings. No default.
+ # * SQLite3: No restrictions on :precision and :scale,
+ # but the maximum supported :precision is 16. No default.
+ # * Oracle: :precision [1..38], :scale [-84..127].
+ # Default is (38,0).
+ # * DB2: :precision [1..63], :scale [0..62].
+ # Default unknown.
+ # * Firebird: :precision [1..18], :scale [0..18].
+ # Default (9,0). Internal types NUMERIC and DECIMAL have different
+ # storage rules, decimal being better.
+ # * FrontBase?: :precision [1..38], :scale [0..38].
+ # Default (38,0). WARNING Max :precision/:scale for
+ # NUMERIC is 19, and DECIMAL is 38.
+ # * SqlServer?: :precision [1..38], :scale [0..38].
+ # Default (38,0).
+ # * Sybase: :precision [1..38], :scale [0..38].
+ # Default (38,0).
+ # * OpenBase?: Documentation unclear. Claims storage in double.
+ #
+ # This method returns self.
+ #
+ # == Examples
+ # # Assuming td is an instance of TableDefinition
+ # td.column(:granted, :boolean)
+ # # granted BOOLEAN
+ #
+ # td.column(:picture, :binary, :limit => 2.megabytes)
+ # # => picture BLOB(2097152)
+ #
+ # td.column(:sales_stage, :string, :limit => 20, :default => 'new', :null => false)
+ # # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
+ #
+ # td.column(:bill_gates_money, :decimal, :precision => 15, :scale => 2)
+ # # => bill_gates_money DECIMAL(15,2)
+ #
+ # td.column(:sensor_reading, :decimal, :precision => 30, :scale => 20)
+ # # => sensor_reading DECIMAL(30,20)
+ #
+ # # While :scale defaults to zero on most databases, it
+ # # probably wouldn't hurt to include it.
+ # td.column(:huge_integer, :decimal, :precision => 30)
+ # # => huge_integer DECIMAL(30)
+ #
+ # == Short-hand examples
+ #
+ # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types.
+ # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined
+ # in a single statement.
+ #
+ # What can be written like this with the regular calls to column:
+ #
+ # create_table "products", :force => true do |t|
+ # t.column "shop_id", :integer
+ # t.column "creator_id", :integer
+ # t.column "name", :string, :default => "Untitled"
+ # t.column "value", :string, :default => "Untitled"
+ # t.column "created_at", :datetime
+ # t.column "updated_at", :datetime
+ # end
+ #
+ # Can also be written as follows using the short-hand:
+ #
+ # create_table :products do |t|
+ # t.integer :shop_id, :creator_id
+ # t.string :name, :value, :default => "Untitled"
+ # t.timestamps
+ # end
+ #
+ # There's a short-hand method for each of the type values declared at the top. And then there's
+ # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
+ #
+ # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
+ # column if the :polymorphic option is supplied. If :polymorphic is a hash of options, these will be
+ # used when creating the _type column. So what can be written like this:
+ #
+ # create_table :taggings do |t|
+ # t.integer :tag_id, :tagger_id, :taggable_id
+ # t.string :tagger_type
+ # t.string :taggable_type, :default => 'Photo'
+ # end
+ #
+ # Can also be written as follows using references:
+ #
+ # create_table :taggings do |t|
+ # t.references :tag
+ # t.references :tagger, :polymorphic => true
+ # t.references :taggable, :polymorphic => { :default => 'Photo' }
+ # end
+ def column(name, type, options = {})
+ column = self[name] || ColumnDefinition.new(@base, name, type)
+ if options[:limit]
+ column.limit = options[:limit]
+ elsif native[type.to_sym].is_a?(Hash)
+ column.limit = native[type.to_sym][:limit]
+ end
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ @columns << column unless @columns.include? column
+ self
+ end
+
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
+ class_eval <<-EOV
+ def #{column_type}(*args)
+ options = args.extract_options!
+ column_names = args
+
+ column_names.each { |name| column(name, '#{column_type}', options) }
+ end
+ EOV
+ end
+
+ # Appends :datetime columns :created_at and
+ # :updated_at to the table.
+ def timestamps
+ column(:created_at, :datetime)
+ column(:updated_at, :datetime)
+ end
+
+ def references(*args)
+ options = args.extract_options!
+ polymorphic = options.delete(:polymorphic)
+ args.each do |col|
+ column("#{col}_id", :integer, options)
+ column("#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ end
+ end
+ alias :belongs_to :references
+
+ # Returns a String whose contents are the column definitions
+ # concatenated together. This string can then be prepended and appended to
+ # to generate the final SQL to create the table.
+ def to_sql
+ @columns * ', '
+ end
+
+ private
+ def native
+ @base.native_database_types
+ end
+ end
+
+ # Represents a SQL table in an abstract way for updating a table.
+ # Also see TableDefinition and SchemaStatements#create_table
+ #
+ # Available transformations are:
+ #
+ # change_table :table do |t|
+ # t.column
+ # t.index
+ # t.timestamps
+ # t.change
+ # t.change_default
+ # t.rename
+ # t.references
+ # t.belongs_to
+ # t.string
+ # t.text
+ # t.integer
+ # t.float
+ # t.decimal
+ # t.datetime
+ # t.timestamp
+ # t.time
+ # t.date
+ # t.binary
+ # t.boolean
+ # t.remove
+ # t.remove_references
+ # t.remove_belongs_to
+ # t.remove_index
+ # t.remove_timestamps
+ # end
+ #
+ class Table
+ def initialize(table_name, base)
+ @table_name = table_name
+ @base = base
+ end
+
+ # Adds a new column to the named table.
+ # See TableDefinition#column for details of the options you can use.
+ # ===== Example
+ # ====== Creating a simple column
+ # t.column(:name, :string)
+ def column(column_name, type, options = {})
+ @base.add_column(@table_name, column_name, type, options)
+ end
+
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
+ # an Array of Symbols. See SchemaStatements#add_index
+ #
+ # ===== Examples
+ # ====== Creating a simple index
+ # t.index(:name)
+ # ====== Creating a unique index
+ # t.index([:branch_id, :party_id], :unique => true)
+ # ====== Creating a named index
+ # t.index([:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
+ def index(column_name, options = {})
+ @base.add_index(@table_name, column_name, options)
+ end
+
+ # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
+ # ===== Example
+ # t.timestamps
+ def timestamps
+ @base.add_timestamps(@table_name)
+ end
+
+ # Changes the column's definition according to the new options.
+ # See TableDefinition#column for details of the options you can use.
+ # ===== Examples
+ # t.change(:name, :string, :limit => 80)
+ # t.change(:description, :text)
+ def change(column_name, type, options = {})
+ @base.change_column(@table_name, column_name, type, options)
+ end
+
+ # Sets a new default value for a column. See SchemaStatements#change_column_default
+ # ===== Examples
+ # t.change_default(:qualification, 'new')
+ # t.change_default(:authorized, 1)
+ def change_default(column_name, default)
+ @base.change_column_default(@table_name, column_name, default)
+ end
+
+ # Removes the column(s) from the table definition.
+ # ===== Examples
+ # t.remove(:qualification)
+ # t.remove(:qualification, :experience)
+ def remove(*column_names)
+ @base.remove_column(@table_name, column_names)
+ end
+
+ # Removes the given index from the table.
+ #
+ # ===== Examples
+ # ====== Remove the suppliers_name_index in the suppliers table
+ # t.remove_index :name
+ # ====== Remove the index named accounts_branch_id_index in the accounts table
+ # t.remove_index :column => :branch_id
+ # ====== Remove the index named accounts_branch_id_party_id_index in the accounts table
+ # t.remove_index :column => [:branch_id, :party_id]
+ # ====== Remove the index named by_branch_party in the accounts table
+ # t.remove_index :name => :by_branch_party
+ def remove_index(options = {})
+ @base.remove_index(@table_name, options)
+ end
+
+ # Removes the timestamp columns (created_at and updated_at) from the table.
+ # ===== Example
+ # t.remove_timestamps
+ def remove_timestamps
+ @base.remove_timestamps(@table_name)
+ end
+
+ # Renames a column.
+ # ===== Example
+ # t.rename(:description, :name)
+ def rename(column_name, new_column_name)
+ @base.rename_column(@table_name, column_name, new_column_name)
+ end
+
+ # Adds a reference. Optionally adds a +type+ column.
+ # references and belongs_to are acceptable.
+ # ===== Examples
+ # t.references(:goat)
+ # t.references(:goat, :polymorphic => true)
+ # t.belongs_to(:goat)
+ def references(*args)
+ options = args.extract_options!
+ polymorphic = options.delete(:polymorphic)
+ args.each do |col|
+ @base.add_column(@table_name, "#{col}_id", :integer, options)
+ @base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+ end
+ end
+ alias :belongs_to :references
+
+ # Removes a reference. Optionally removes a +type+ column.
+ # remove_references and remove_belongs_to are acceptable.
+ # ===== Examples
+ # t.remove_references(:goat)
+ # t.remove_references(:goat, :polymorphic => true)
+ # t.remove_belongs_to(:goat)
+ def remove_references(*args)
+ options = args.extract_options!
+ polymorphic = options.delete(:polymorphic)
+ args.each do |col|
+ @base.remove_column(@table_name, "#{col}_id")
+ @base.remove_column(@table_name, "#{col}_type") unless polymorphic.nil?
+ end
+ end
+ alias :remove_belongs_to :remove_references
+
+ # Adds a column or columns of a specified type
+ # ===== Examples
+ # t.string(:goat)
+ # t.string(:goat, :sheep)
+ %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
+ class_eval <<-EOV
+ def #{column_type}(*args)
+ options = args.extract_options!
+ column_names = args
+
+ column_names.each do |name|
+ column = ColumnDefinition.new(@base, name, '#{column_type}')
+ if options[:limit]
+ column.limit = options[:limit]
+ elsif native['#{column_type}'.to_sym].is_a?(Hash)
+ column.limit = native['#{column_type}'.to_sym][:limit]
+ end
+ column.precision = options[:precision]
+ column.scale = options[:scale]
+ column.default = options[:default]
+ column.null = options[:null]
+ @base.add_column(@table_name, name, column.sql_type, options)
+ end
+ end
+ EOV
+ end
+
+ private
+ def native
+ @base.native_database_types
+ end
+ end
+
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
new file mode 100644
index 0000000..67d70b3
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -0,0 +1,421 @@
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ module SchemaStatements
+ # Returns a Hash of mappings from the abstract data types to the native
+ # database types. See TableDefinition#column for details on the recognized
+ # abstract data types.
+ def native_database_types
+ {}
+ end
+
+ # This is the maximum length a table alias can be
+ def table_alias_length
+ 255
+ end
+
+ # Truncates a table alias according to the limits of the current adapter.
+ def table_alias_for(table_name)
+ table_name[0..table_alias_length-1].gsub(/\./, '_')
+ end
+
+ # def tables(name = nil) end
+
+ def table_exists?(table_name)
+ tables.include?(table_name.to_s)
+ end
+
+ # Returns an array of indexes for the given table.
+ # def indexes(table_name, name = nil) end
+
+ # Returns an array of Column objects for the table specified by +table_name+.
+ # See the concrete implementation for details on the expected parameter values.
+ def columns(table_name, name = nil) end
+
+ # Creates a new table
+ # There are two ways to work with +create_table+. You can use the block
+ # form or the regular form, like this:
+ #
+ # === Block form
+ # # create_table() yields a TableDefinition instance
+ # create_table(:suppliers) do |t|
+ # t.column :name, :string, :limit => 60
+ # # Other fields here
+ # end
+ #
+ # === Regular form
+ # create_table(:suppliers)
+ # add_column(:suppliers, :name, :string, {:limit => 60})
+ #
+ # The +options+ hash can include the following keys:
+ # [:id]
+ # Whether to automatically add a primary key column. Defaults to true.
+ # Join tables for +has_and_belongs_to_many+ should set :id => false.
+ # [:primary_key]
+ # The name of the primary key, if one is to be added automatically.
+ # Defaults to +id+.
+ # [:options]
+ # Any extra options you want appended to the table definition.
+ # [:temporary]
+ # Make a temporary table.
+ # [:force]
+ # Set to true to drop the table before creating it.
+ # Defaults to false.
+ #
+ # ===== Examples
+ # ====== Add a backend specific option to the generated SQL (MySQL)
+ # create_table(:suppliers, :options => 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
+ # generates:
+ # CREATE TABLE suppliers (
+ # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
+ #
+ # ====== Rename the primary key column
+ # create_table(:objects, :primary_key => 'guid') do |t|
+ # t.column :name, :string, :limit => 80
+ # end
+ # generates:
+ # CREATE TABLE objects (
+ # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
+ # name varchar(80)
+ # )
+ #
+ # ====== Do not add a primary key column
+ # create_table(:categories_suppliers, :id => false) do |t|
+ # t.column :category_id, :integer
+ # t.column :supplier_id, :integer
+ # end
+ # generates:
+ # CREATE TABLE categories_suppliers (
+ # category_id int,
+ # supplier_id int
+ # )
+ #
+ # See also TableDefinition#column for details on how to create columns.
+ def create_table(table_name, options = {})
+ table_definition = TableDefinition.new(self)
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(table_name)) unless options[:id] == false
+
+ yield table_definition
+
+ if options[:force] && table_exists?(table_name)
+ drop_table(table_name, options)
+ end
+
+ create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE "
+ create_sql << "#{quote_table_name(table_name)} ("
+ create_sql << table_definition.to_sql
+ create_sql << ") #{options[:options]}"
+ execute create_sql
+ end
+
+ # A block for changing columns in +table+.
+ #
+ # === Example
+ # # change_table() yields a Table instance
+ # change_table(:suppliers) do |t|
+ # t.column :name, :string, :limit => 60
+ # # Other column alterations here
+ # end
+ #
+ # ===== Examples
+ # ====== Add a column
+ # change_table(:suppliers) do |t|
+ # t.column :name, :string, :limit => 60
+ # end
+ #
+ # ====== Add 2 integer columns
+ # change_table(:suppliers) do |t|
+ # t.integer :width, :height, :null => false, :default => 0
+ # end
+ #
+ # ====== Add created_at/updated_at columns
+ # change_table(:suppliers) do |t|
+ # t.timestamps
+ # end
+ #
+ # ====== Add a foreign key column
+ # change_table(:suppliers) do |t|
+ # t.references :company
+ # end
+ #
+ # Creates a company_id(integer) column
+ #
+ # ====== Add a polymorphic foreign key column
+ # change_table(:suppliers) do |t|
+ # t.belongs_to :company, :polymorphic => true
+ # end
+ #
+ # Creates company_type(varchar) and company_id(integer) columns
+ #
+ # ====== Remove a column
+ # change_table(:suppliers) do |t|
+ # t.remove :company
+ # end
+ #
+ # ====== Remove several columns
+ # change_table(:suppliers) do |t|
+ # t.remove :company_id
+ # t.remove :width, :height
+ # end
+ #
+ # ====== Remove an index
+ # change_table(:suppliers) do |t|
+ # t.remove_index :company_id
+ # end
+ #
+ # See also Table for details on
+ # all of the various column transformation
+ def change_table(table_name)
+ yield Table.new(table_name, self)
+ end
+
+ # Renames a table.
+ # ===== Example
+ # rename_table('octopuses', 'octopi')
+ def rename_table(table_name, new_name)
+ raise NotImplementedError, "rename_table is not implemented"
+ end
+
+ # Drops a table from the database.
+ def drop_table(table_name, options = {})
+ execute "DROP TABLE #{quote_table_name(table_name)}"
+ end
+
+ # Adds a new column to the named table.
+ # See TableDefinition#column for details of the options you can use.
+ def add_column(table_name, column_name, type, options = {})
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ execute(add_column_sql)
+ end
+
+ # Removes the column(s) from the table definition.
+ # ===== Examples
+ # remove_column(:suppliers, :qualification)
+ # remove_columns(:suppliers, :qualification, :experience)
+ def remove_column(table_name, *column_names)
+ column_names.flatten.each do |column_name|
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
+ end
+ end
+ alias :remove_columns :remove_column
+
+ # Changes the column's definition according to the new options.
+ # See TableDefinition#column for details of the options you can use.
+ # ===== Examples
+ # change_column(:suppliers, :name, :string, :limit => 80)
+ # change_column(:accounts, :description, :text)
+ def change_column(table_name, column_name, type, options = {})
+ raise NotImplementedError, "change_column is not implemented"
+ end
+
+ # Sets a new default value for a column. If you want to set the default
+ # value to +NULL+, you are out of luck. You need to
+ # DatabaseStatements#execute the appropriate SQL statement yourself.
+ # ===== Examples
+ # change_column_default(:suppliers, :qualification, 'new')
+ # change_column_default(:accounts, :authorized, 1)
+ def change_column_default(table_name, column_name, default)
+ raise NotImplementedError, "change_column_default is not implemented"
+ end
+
+ # Renames a column.
+ # ===== Example
+ # rename_column(:suppliers, :description, :name)
+ def rename_column(table_name, column_name, new_column_name)
+ raise NotImplementedError, "rename_column is not implemented"
+ end
+
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
+ # an Array of Symbols.
+ #
+ # The index will be named after the table and the first column name,
+ # unless you pass :name as an option.
+ #
+ # When creating an index on multiple columns, the first column is used as a name
+ # for the index. For example, when you specify an index on two columns
+ # [:first, :last], the DBMS creates an index for both columns as well as an
+ # index for the first column :first. Using just the first name for this index
+ # makes sense, because you will never have to create a singular index with this
+ # name.
+ #
+ # ===== Examples
+ # ====== Creating a simple index
+ # add_index(:suppliers, :name)
+ # generates
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
+ # ====== Creating a unique index
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true)
+ # generates
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
+ # ====== Creating a named index
+ # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party')
+ # generates
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
+ def add_index(table_name, column_name, options = {})
+ column_names = Array(column_name)
+ index_name = index_name(table_name, :column => column_names)
+
+ if Hash === options # legacy support, since this param was a string
+ index_type = options[:unique] ? "UNIQUE" : ""
+ index_name = options[:name] || index_name
+ else
+ index_type = options
+ end
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
+ end
+
+ # Remove the given index from the table.
+ #
+ # Remove the suppliers_name_index in the suppliers table.
+ # remove_index :suppliers, :name
+ # Remove the index named accounts_branch_id_index in the accounts table.
+ # remove_index :accounts, :column => :branch_id
+ # Remove the index named accounts_branch_id_party_id_index in the accounts table.
+ # remove_index :accounts, :column => [:branch_id, :party_id]
+ # Remove the index named by_branch_party in the accounts table.
+ # remove_index :accounts, :name => :by_branch_party
+ def remove_index(table_name, options = {})
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))} ON #{table_name}"
+ end
+
+ def index_name(table_name, options) #:nodoc:
+ if Hash === options # legacy support
+ if options[:column]
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
+ elsif options[:name]
+ options[:name]
+ else
+ raise ArgumentError, "You must specify the index name"
+ end
+ else
+ index_name(table_name, :column => options)
+ end
+ end
+
+ # Returns a string of CREATE TABLE SQL statement(s) for recreating the
+ # entire structure of the database.
+ def structure_dump
+ end
+
+ def dump_schema_information #:nodoc:
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
+ migrated = select_values("SELECT version FROM #{sm_table}")
+ migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n")
+ end
+
+ # Should not be called normally, but this operation is non-destructive.
+ # The migrations module handles this automatically.
+ def initialize_schema_migrations_table
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
+
+ unless tables.detect { |t| t == sm_table }
+ create_table(sm_table, :id => false) do |schema_migrations_table|
+ schema_migrations_table.column :version, :string, :null => false
+ end
+ add_index sm_table, :version, :unique => true,
+ :name => 'unique_schema_migrations'
+
+ # Backwards-compatibility: if we find schema_info, assume we've
+ # migrated up to that point:
+ si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
+
+ if tables.detect { |t| t == si_table }
+
+ old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
+ assume_migrated_upto_version(old_version)
+ drop_table(si_table)
+ end
+ end
+ end
+
+ def assume_migrated_upto_version(version)
+ sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
+ migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
+ versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename|
+ filename.split('/').last.split('_').first.to_i
+ end
+
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" unless migrated.include?(version.to_i)
+ (versions - migrated).select { |v| v < version.to_i }.each do |v|
+ execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
+ end
+ end
+
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
+ if native = native_database_types[type]
+ column_type_sql = native.is_a?(Hash) ? native[:name] : native
+
+ if type == :decimal # ignore limit, use precision and scale
+ scale ||= native[:scale]
+
+ if precision ||= native[:precision]
+ if scale
+ column_type_sql << "(#{precision},#{scale})"
+ else
+ column_type_sql << "(#{precision})"
+ end
+ elsif scale
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale if specified"
+ end
+
+ elsif limit ||= native.is_a?(Hash) && native[:limit]
+ column_type_sql << "(#{limit})"
+ end
+
+ column_type_sql
+ else
+ type
+ end
+ end
+
+ def add_column_options!(sql, options) #:nodoc:
+ sql << " DEFAULT #{quote(options[:default], options[:column])}" if options_include_default?(options)
+ # must explcitly check for :null to allow change_column to work on migrations
+ if options.has_key? :null
+ if options[:null] == false
+ sql << " NOT NULL"
+ else
+ sql << " NULL"
+ end
+ end
+ end
+
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
+ # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax.
+ #
+ # distinct("posts.id", "posts.created_at desc")
+ def distinct(columns, order_by)
+ "DISTINCT #{columns}"
+ end
+
+ # ORDER BY clause for the passed order option.
+ # PostgreSQL overrides this due to its stricter standards compliance.
+ def add_order_by_for_association_limiting!(sql, options)
+ sql << " ORDER BY #{options[:order]}"
+ end
+
+ # Adds timestamps (created_at and updated_at) columns to the named table.
+ # ===== Examples
+ # add_timestamps(:suppliers)
+ def add_timestamps(table_name)
+ add_column table_name, :created_at, :datetime
+ add_column table_name, :updated_at, :datetime
+ end
+
+ # Removes the timestamp columns (created_at and updated_at) from the table definition.
+ # ===== Examples
+ # remove_timestamps(:suppliers)
+ def remove_timestamps(table_name)
+ remove_column table_name, :updated_at
+ remove_column table_name, :created_at
+ end
+
+ protected
+ def options_include_default?(options)
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
new file mode 100644
index 0000000..f48b107
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -0,0 +1,169 @@
+require 'benchmark'
+require 'date'
+require 'bigdecimal'
+require 'bigdecimal/util'
+
+require 'active_record/connection_adapters/abstract/schema_definitions'
+require 'active_record/connection_adapters/abstract/schema_statements'
+require 'active_record/connection_adapters/abstract/database_statements'
+require 'active_record/connection_adapters/abstract/quoting'
+require 'active_record/connection_adapters/abstract/connection_specification'
+require 'active_record/connection_adapters/abstract/query_cache'
+
+module ActiveRecord
+ module ConnectionAdapters # :nodoc:
+ # All the concrete database adapters follow the interface laid down in this class.
+ # You can use this interface directly by borrowing the database connection from the Base with
+ # Base.connection.
+ #
+ # Most of the methods in the adapter are useful during migrations. Most
+ # notably, SchemaStatements#create_table, SchemaStatements#drop_table,
+ # SchemaStatements#add_index, SchemaStatements#remove_index,
+ # SchemaStatements#add_column, SchemaStatements#change_column and
+ # SchemaStatements#remove_column are very useful.
+ class AbstractAdapter
+ include Quoting, DatabaseStatements, SchemaStatements
+ include QueryCache
+ @@row_even = true
+
+ def initialize(connection, logger = nil) #:nodoc:
+ @connection, @logger = connection, logger
+ @runtime = 0
+ @last_verification = 0
+ @query_cache_enabled = false
+ end
+
+ # Returns the human-readable name of the adapter. Use mixed case - one
+ # can always use downcase if needed.
+ def adapter_name
+ 'Abstract'
+ end
+
+ # Does this adapter support migrations? Backend specific, as the
+ # abstract adapter always returns +false+.
+ def supports_migrations?
+ false
+ end
+
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
+ # for all adapters except sqlite.
+ def supports_count_distinct?
+ true
+ end
+
+ # Should primary key values be selected from their corresponding
+ # sequence before the insert statement? If true, next_sequence_value
+ # is called before each insert to set the record's primary key.
+ # This is false for all adapters but Firebird.
+ def prefetch_primary_key?(table_name = nil)
+ false
+ end
+
+ def reset_runtime #:nodoc:
+ rt, @runtime = @runtime, 0
+ rt
+ end
+
+ # QUOTING ==================================================
+
+ # Override to return the quoted table name. Defaults to column quoting.
+ def quote_table_name(name)
+ quote_column_name(name)
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ # Override to turn off referential integrity while executing &block.
+ def disable_referential_integrity(&block)
+ yield
+ end
+
+ # CONNECTION MANAGEMENT ====================================
+
+ # Is this connection active and ready to perform queries?
+ def active?
+ @active != false
+ end
+
+ # Close this connection and open a new one in its place.
+ def reconnect!
+ @active = true
+ end
+
+ # Close this connection
+ def disconnect!
+ @active = false
+ end
+
+ # Returns true if its safe to reload the connection between requests for development mode.
+ # This is not the case for Ruby/MySQL and it's not necessary for any adapters except SQLite.
+ def requires_reloading?
+ false
+ end
+
+ # Lazily verify this connection, calling active? only if it hasn't
+ # been called for +timeout+ seconds.
+ def verify!(timeout)
+ now = Time.now.to_i
+ if (now - @last_verification) > timeout
+ reconnect! unless active?
+ @last_verification = now
+ end
+ end
+
+ # Provides access to the underlying database connection. Useful for
+ # when you need to call a proprietary method such as postgresql's lo_*
+ # methods
+ def raw_connection
+ @connection
+ end
+
+ def log_info(sql, name, runtime)
+ if @logger && @logger.debug?
+ name = "#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})"
+ @logger.debug format_log_entry(name, sql.squeeze(' '))
+ end
+ end
+
+ protected
+ def log(sql, name)
+ if block_given?
+ result = nil
+ seconds = Benchmark.realtime { result = yield }
+ @runtime += seconds
+ log_info(sql, name, seconds)
+ result
+ else
+ log_info(sql, name, 0)
+ nil
+ end
+ rescue Exception => e
+ # Log message and raise exception.
+ # Set last_verification to 0, so that connection gets verified
+ # upon reentering the request loop
+ @last_verification = 0
+ message = "#{e.class.name}: #{e.message}: #{sql}"
+ log_info(message, name, 0)
+ raise ActiveRecord::StatementInvalid, message
+ end
+
+ def format_log_entry(message, dump = nil)
+ if ActiveRecord::Base.colorize_logging
+ if @@row_even
+ @@row_even = false
+ message_color, dump_color = "4;36;1", "0;1"
+ else
+ @@row_even = true
+ message_color, dump_color = "4;35;1", "0"
+ end
+
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
+ log_entry
+ else
+ "%s %s" % [message, dump]
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
new file mode 100644
index 0000000..f00a2c8
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -0,0 +1,530 @@
+require 'active_record/connection_adapters/abstract_adapter'
+require 'set'
+
+module MysqlCompat #:nodoc:
+ # add all_hashes method to standard mysql-c bindings or pure ruby version
+ def self.define_all_hashes_method!
+ raise 'Mysql not loaded' unless defined?(::Mysql)
+
+ target = defined?(Mysql::Result) ? Mysql::Result : MysqlRes
+ return if target.instance_methods.include?('all_hashes')
+
+ # Ruby driver has a version string and returns null values in each_hash
+ # C driver >= 2.7 returns null values in each_hash
+ if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
+ target.class_eval <<-'end_eval'
+ def all_hashes
+ rows = []
+ each_hash { |row| rows << row }
+ rows
+ end
+ end_eval
+
+ # adapters before 2.7 don't have a version constant
+ # and don't return null values in each_hash
+ else
+ target.class_eval <<-'end_eval'
+ def all_hashes
+ rows = []
+ all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
+ each_hash { |row| rows << all_fields.dup.update(row) }
+ rows
+ end
+ end_eval
+ end
+
+ unless target.instance_methods.include?('all_hashes') ||
+ target.instance_methods.include?(:all_hashes)
+ raise "Failed to defined #{target.name}#all_hashes method. Mysql::VERSION = #{Mysql::VERSION.inspect}"
+ end
+ end
+end
+
+module ActiveRecord
+ class Base
+ def self.require_mysql
+ # Include the MySQL driver if one hasn't already been loaded
+ unless defined? Mysql
+ begin
+ require_library_or_gem 'mysql'
+ rescue LoadError => cannot_require_mysql
+ # Use the bundled Ruby/MySQL driver if no driver is already in place
+ begin
+ ActiveRecord::Base.logger.info(
+ "WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. " +
+ "Please install the C-based MySQL library instead (gem install mysql)."
+ ) if ActiveRecord::Base.logger
+
+ require 'active_record/vendor/mysql'
+ rescue LoadError
+ raise cannot_require_mysql
+ end
+ end
+ end
+
+ # Define Mysql::Result.all_hashes
+ MysqlCompat.define_all_hashes_method!
+ end
+
+ # Establishes a connection to the database that's used by all Active Record objects.
+ def self.mysql_connection(config) # :nodoc:
+ config = config.symbolize_keys
+ host = config[:host]
+ port = config[:port]
+ socket = config[:socket]
+ username = config[:username] ? config[:username].to_s : 'root'
+ password = config[:password].to_s
+
+ if config.has_key?(:database)
+ database = config[:database]
+ else
+ raise ArgumentError, "No database specified. Missing argument: database."
+ end
+
+ require_mysql
+ mysql = Mysql.init
+ mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey]
+
+ ConnectionAdapters::MysqlAdapter.new(mysql, logger, [host, username, password, database, port, socket], config)
+ end
+ end
+
+ module ConnectionAdapters
+ class MysqlColumn < Column #:nodoc:
+ def extract_default(default)
+ if type == :binary || type == :text
+ if default.blank?
+ nil
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ private
+ def simplified_type(field_type)
+ return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
+ return :string if field_type =~ /enum/i
+ super
+ end
+
+ def extract_limit(sql_type)
+ if sql_type =~ /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ else
+ super
+ end
+ end
+
+ # MySQL misreports NOT NULL column default when none is given.
+ # We can't detect this for columns which may have a legitimate ''
+ # default (string) but we can for others (integer, datetime, boolean,
+ # and the rest).
+ #
+ # Test whether the column has default '', is not null, and is not
+ # a type allowing default ''.
+ def missing_default_forged_as_empty_string?(default)
+ type != :string && !null && default == ''
+ end
+ end
+
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
+ #
+ # Options:
+ #
+ # * :host - Defaults to "localhost".
+ # * :port - Defaults to 3306.
+ # * :socket - Defaults to "/tmp/mysql.sock".
+ # * :username - Defaults to "root"
+ # * :password - Defaults to nothing.
+ # * :database - The name of the database. No default, must be provided.
+ # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection.
+ # * :sslkey - Necessary to use MySQL with an SSL connection.
+ # * :sslcert - Necessary to use MySQL with an SSL connection.
+ # * :sslcapath - Necessary to use MySQL with an SSL connection.
+ # * :sslcipher - Necessary to use MySQL with an SSL connection.
+ #
+ # By default, the MysqlAdapter will consider all columns of type tinyint(1)
+ # as boolean. If you wish to disable this emulation (which was the default
+ # behavior in versions 0.13.1 and earlier) you can add the following line
+ # to your environment.rb file:
+ #
+ # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
+ class MysqlAdapter < AbstractAdapter
+ @@emulate_booleans = true
+ cattr_accessor :emulate_booleans
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ connect
+ end
+
+ def adapter_name #:nodoc:
+ 'MySQL'
+ end
+
+ def supports_migrations? #:nodoc:
+ true
+ end
+
+ def native_database_types #:nodoc:
+ {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int"},
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+ end
+
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ "'#{value.to_s("F")}'"
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quote_string(string) #:nodoc:
+ @connection.quote(string)
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # CONNECTION MANAGEMENT ====================================
+
+ def active?
+ if @connection.respond_to?(:stat)
+ @connection.stat
+ else
+ @connection.query 'select 1'
+ end
+
+ # mysql-ruby doesn't raise an exception when stat fails.
+ if @connection.respond_to?(:errno)
+ @connection.errno.zero?
+ else
+ true
+ end
+ rescue Mysql::Error
+ false
+ end
+
+ def reconnect!
+ disconnect!
+ connect
+ end
+
+ def disconnect!
+ @connection.close rescue nil
+ end
+
+
+ # DATABASE STATEMENTS ======================================
+
+ def select_rows(sql, name = nil)
+ @connection.query_with_result = true
+ result = execute(sql, name)
+ rows = []
+ result.each { |row| rows << row }
+ result.free
+ rows
+ end
+
+ def execute(sql, name = nil) #:nodoc:
+ log(sql, name) { @connection.query(sql) }
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
+ super sql, name
+ id_value || @connection.insert_id
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction #:nodoc:
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction #:nodoc:
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction #:nodoc:
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+
+ def add_limit_offset!(sql, options) #:nodoc:
+ if limit = options[:limit]
+ unless offset = options[:offset]
+ sql << " LIMIT #{limit}"
+ else
+ sql << " LIMIT #{offset}, #{limit}"
+ end
+ end
+ end
+
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump #:nodoc:
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).inject("") do |structure, table|
+ table.delete('Table_type')
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
+ end
+ end
+
+ def recreate_database(name) #:nodoc:
+ drop_database(name)
+ create_database(name)
+ end
+
+ # Create a new MySQL database with optional :charset and :collation.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ def create_database(name, options = {})
+ if options[:collation]
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ else
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ end
+ end
+
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil) #:nodoc:
+ tables = []
+ execute("SHOW TABLES", name).each { |field| tables << field[0] }
+ tables
+ end
+
+ def drop_table(table_name, options = {})
+ super(table_name, options)
+ end
+
+ def indexes(table_name, name = nil)#:nodoc:
+ indexes = []
+ current_index = nil
+ execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
+ if current_index != row[2]
+ next if row[2] == "PRIMARY" # skip the primary key
+ current_index = row[2]
+ indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [])
+ end
+
+ indexes.last.columns << row[4]
+ end
+ indexes
+ end
+
+ def columns(table_name, name = nil)#:nodoc:
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ columns = []
+ execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ columns
+ end
+
+ def create_table(table_name, options = {}) #:nodoc:
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def change_column_default(table_name, column_name, default) #:nodoc:
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+
+ execute("ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{current_type} DEFAULT #{quote(default)}")
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ unless options_include_default?(options)
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ else
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ end
+
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ execute "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ case limit
+ when 0..3
+ "smallint(#{limit})"
+ when 4..8
+ "int(#{limit})"
+ when 9..20
+ "bigint(#{limit})"
+ else
+ 'int(11)'
+ end
+ end
+
+
+ # SHOW VARIABLES LIKE 'name'
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ # Returns a table's primary key and belonging sequence.
+ def pk_and_sequence_for(table) #:nodoc:
+ keys = []
+ execute("describe #{quote_table_name(table)}").each_hash do |h|
+ keys << h["Field"]if h["Key"] == "PRI"
+ end
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+
+ private
+ def connect
+ encoding = @config[:encoding]
+ if encoding
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
+ end
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) if @config[:sslkey]
+ @connection.real_connect(*@connection_options)
+ execute("SET NAMES '#{encoding}'") if encoding
+
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0")
+ end
+
+ def select(sql, name = nil)
+ @connection.query_with_result = true
+ result = execute(sql, name)
+ rows = result.all_hashes
+ result.free
+ rows
+ end
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def version
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
new file mode 100644
index 0000000..7dbfbb4
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -0,0 +1,948 @@
+require 'active_record/connection_adapters/abstract_adapter'
+
+begin
+ require_library_or_gem 'pg'
+rescue LoadError => e
+ begin
+ require_library_or_gem 'postgres'
+ class PGresult
+ alias_method :nfields, :num_fields unless self.method_defined?(:nfields)
+ alias_method :ntuples, :num_tuples unless self.method_defined?(:ntuples)
+ alias_method :ftype, :type unless self.method_defined?(:ftype)
+ alias_method :cmd_tuples, :cmdtuples unless self.method_defined?(:cmd_tuples)
+ end
+ rescue LoadError
+ raise e
+ end
+end
+
+module ActiveRecord
+ class Base
+ # Establishes a connection to the database that's used by all Active Record objects
+ def self.postgresql_connection(config) # :nodoc:
+ config = config.symbolize_keys
+ host = config[:host]
+ port = config[:port] || 5432
+ username = config[:username].to_s
+ password = config[:password].to_s
+
+ if config.has_key?(:database)
+ database = config[:database]
+ else
+ raise ArgumentError, "No database specified. Missing argument: database."
+ end
+
+ # The postgres drivers don't allow the creation of an unconnected PGconn object,
+ # so just pass a nil connection object for the time being.
+ ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config)
+ end
+ end
+
+ module ConnectionAdapters
+ # PostgreSQL-specific extensions to column definitions in a table.
+ class PostgreSQLColumn < Column #:nodoc:
+ # Instantiates a new PostgreSQL column definition in a table.
+ def initialize(name, default, sql_type = nil, null = true)
+ super(name, self.class.extract_value_from_default(default), sql_type, null)
+ end
+
+ private
+ # Extracts the scale from PostgreSQL-specific data types.
+ def extract_scale(sql_type)
+ # Money type has a fixed scale of 2.
+ sql_type =~ /^money/ ? 2 : super
+ end
+
+ # Extracts the precision from PostgreSQL-specific data types.
+ def extract_precision(sql_type)
+ # Actual code is defined dynamically in PostgreSQLAdapter.connect
+ # depending on the server specifics
+ super
+ end
+
+ # Escapes binary strings for bytea input to the database.
+ def self.string_to_binary(value)
+ if PGconn.respond_to?(:escape_bytea)
+ self.class.module_eval do
+ define_method(:string_to_binary) do |value|
+ PGconn.escape_bytea(value) if value
+ end
+ end
+ else
+ self.class.module_eval do
+ define_method(:string_to_binary) do |value|
+ if value
+ result = ''
+ value.each_byte { |c| result << sprintf('\\\\%03o', c) }
+ result
+ end
+ end
+ end
+ end
+ self.class.string_to_binary(value)
+ end
+
+ # Unescapes bytea output from a database to the binary string it represents.
+ def self.binary_to_string(value)
+ # In each case, check if the value actually is escaped PostgreSQL bytea output
+ # or an unescaped Active Record attribute that was just written.
+ if PGconn.respond_to?(:unescape_bytea)
+ self.class.module_eval do
+ define_method(:binary_to_string) do |value|
+ if value =~ /\\\d{3}/
+ PGconn.unescape_bytea(value)
+ else
+ value
+ end
+ end
+ end
+ else
+ self.class.module_eval do
+ define_method(:binary_to_string) do |value|
+ if value =~ /\\\d{3}/
+ result = ''
+ i, max = 0, value.size
+ while i < max
+ char = value[i]
+ if char == ?\\
+ if value[i+1] == ?\\
+ char = ?\\
+ i += 1
+ else
+ char = value[i+1..i+3].oct
+ i += 3
+ end
+ end
+ result << char
+ i += 1
+ end
+ result
+ else
+ value
+ end
+ end
+ end
+ end
+ self.class.binary_to_string(value)
+ end
+
+ # Maps PostgreSQL-specific data types to logical Rails types.
+ def simplified_type(field_type)
+ case field_type
+ # Numeric and monetary types
+ when /^(?:real|double precision)$/
+ :float
+ # Monetary types
+ when /^money$/
+ :decimal
+ # Character types
+ when /^(?:character varying|bpchar)(?:\(\d+\))?$/
+ :string
+ # Binary data types
+ when /^bytea$/
+ :binary
+ # Date/time types
+ when /^timestamp with(?:out)? time zone$/
+ :datetime
+ when /^interval$/
+ :string
+ # Geometric types
+ when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/
+ :string
+ # Network address types
+ when /^(?:cidr|inet|macaddr)$/
+ :string
+ # Bit strings
+ when /^bit(?: varying)?(?:\(\d+\))?$/
+ :string
+ # XML type
+ when /^xml$/
+ :string
+ # Arrays
+ when /^\D+\[\]$/
+ :string
+ # Object identifier types
+ when /^oid$/
+ :integer
+ # Pass through all types that are not specific to PostgreSQL.
+ else
+ super
+ end
+ end
+
+ # Extracts the value from a PostgreSQL column default definition.
+ def self.extract_value_from_default(default)
+ case default
+ # Numeric types
+ when /\A-?\d+(\.\d*)?\z/
+ default
+ # Character types
+ when /\A'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1
+ # Character types (8.1 formatting)
+ when /\AE'(.*)'::(?:character varying|bpchar|text)\z/m
+ $1.gsub(/\\(\d\d\d)/) { $1.oct.chr }
+ # Binary data types
+ when /\A'(.*)'::bytea\z/m
+ $1
+ # Date/time types
+ when /\A'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)\z/
+ $1
+ when /\A'(.*)'::interval\z/
+ $1
+ # Boolean type
+ when 'true'
+ true
+ when 'false'
+ false
+ # Geometric types
+ when /\A'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)\z/
+ $1
+ # Network address types
+ when /\A'(.*)'::(?:cidr|inet|macaddr)\z/
+ $1
+ # Bit string types
+ when /\AB'(.*)'::"?bit(?: varying)?"?\z/
+ $1
+ # XML type
+ when /\A'(.*)'::xml\z/m
+ $1
+ # Arrays
+ when /\A'(.*)'::"?\D+"?\[\]\z/
+ $1
+ # Object identifier types
+ when /\A-?\d+\z/
+ $1
+ else
+ # Anything else is blank, some user type, or some function
+ # and we can't know the value of that, so return nil.
+ nil
+ end
+ end
+ end
+ end
+
+ module ConnectionAdapters
+ # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure
+ # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers.
+ #
+ # Options:
+ #
+ # * :host - Defaults to "localhost".
+ # * :port - Defaults to 5432.
+ # * :username - Defaults to nothing.
+ # * :password - Defaults to nothing.
+ # * :database - The name of the database. No default, must be provided.
+ # * :schema_search_path - An optional schema search path for the connection given as a string of comma-separated schema names. This is backward-compatible with the :schema_order option.
+ # * :encoding - An optional client encoding that is used in a SET client_encoding TO call on the connection.
+ # * :min_messages - An optional client min messages that is used in a SET client_min_messages TO call on the connection.
+ # * :allow_concurrency - If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods.
+ class PostgreSQLAdapter < AbstractAdapter
+ # Returns 'PostgreSQL' as adapter name for identification purposes.
+ def adapter_name
+ 'PostgreSQL'
+ end
+
+ # Initializes and connects a PostgreSQL adapter.
+ def initialize(connection, logger, connection_parameters, config)
+ super(connection, logger)
+ @connection_parameters, @config = connection_parameters, config
+
+ connect
+ end
+
+ # Is this connection alive and ready for queries?
+ def active?
+ if @connection.respond_to?(:status)
+ @connection.status == PGconn::CONNECTION_OK
+ else
+ # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query
+ @connection.query 'SELECT 1'
+ true
+ end
+ # postgres-pr raises a NoMethodError when querying if no connection is available.
+ rescue PGError, NoMethodError
+ false
+ end
+
+ # Close then reopen the connection.
+ def reconnect!
+ if @connection.respond_to?(:reset)
+ @connection.reset
+ configure_connection
+ else
+ disconnect!
+ connect
+ end
+ end
+
+ # Close the connection.
+ def disconnect!
+ @connection.close rescue nil
+ end
+
+ def native_database_types #:nodoc:
+ {
+ :primary_key => "serial primary key",
+ :string => { :name => "character varying", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "integer" },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "timestamp" },
+ :timestamp => { :name => "timestamp" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "bytea" },
+ :boolean => { :name => "boolean" }
+ }
+ end
+
+ # Does PostgreSQL support migrations?
+ def supports_migrations?
+ true
+ end
+
+ # Does PostgreSQL support standard conforming strings?
+ def supports_standard_conforming_strings?
+ # Temporarily set the client message level above error to prevent unintentional
+ # error messages in the logs when working on a PostgreSQL database server that
+ # does not support standard conforming strings.
+ client_min_messages_old = client_min_messages
+ self.client_min_messages = 'panic'
+
+ # postgres-pr does not raise an exception when client_min_messages is set higher
+ # than error and "SHOW standard_conforming_strings" fails, but returns an empty
+ # PGresult instead.
+ has_support = query('SHOW standard_conforming_strings')[0][0] rescue false
+ self.client_min_messages = client_min_messages_old
+ has_support
+ end
+
+ # Returns the configured supported identifier length supported by PostgreSQL,
+ # or report the default of 63 on PostgreSQL 7.x.
+ def table_alias_length
+ @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63)
+ end
+
+ # QUOTING ==================================================
+
+ # Quotes PostgreSQL-specific data types for SQL input.
+ def quote(value, column = nil) #:nodoc:
+ if value.kind_of?(String) && column && column.type == :binary
+ "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
+ elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
+ "xml '#{quote_string(value)}'"
+ elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
+ # Not truly string input, so doesn't require (or allow) escape string syntax.
+ "'#{value.to_s}'"
+ elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/
+ case value
+ when /^[01]*$/
+ "B'#{value}'" # Bit-string notation
+ when /^[0-9A-F]*$/i
+ "X'#{value}'" # Hexadecimal notation
+ end
+ else
+ super
+ end
+ end
+
+ # Quotes strings for use in SQL input in the postgres driver for better performance.
+ def quote_string(s) #:nodoc:
+ if PGconn.respond_to?(:escape)
+ self.class.instance_eval do
+ define_method(:quote_string) do |s|
+ PGconn.escape(s)
+ end
+ end
+ else
+ # There are some incorrectly compiled postgres drivers out there
+ # that don't define PGconn.escape.
+ self.class.instance_eval do
+ undef_method(:quote_string)
+ end
+ end
+ quote_string(s)
+ end
+
+ # Quotes column names for use in SQL queries.
+ def quote_column_name(name) #:nodoc:
+ %("#{name}")
+ end
+
+ # Quote date/time values for use in SQL input. Includes microseconds
+ # if the value is a Time responding to usec.
+ def quoted_date(value) #:nodoc:
+ if value.acts_like?(:time) && value.respond_to?(:usec)
+ "#{super}.#{sprintf("%06d", value.usec)}"
+ else
+ super
+ end
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def supports_disable_referential_integrity?() #:nodoc:
+ version = query("SHOW server_version")[0][0].split('.')
+ (version[0].to_i >= 8 && version[1].to_i >= 1) ? true : false
+ rescue
+ return false
+ end
+
+ def disable_referential_integrity(&block) #:nodoc:
+ if supports_disable_referential_integrity?() then
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
+ end
+ yield
+ ensure
+ if supports_disable_referential_integrity?() then
+ execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
+ end
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # Executes a SELECT query and returns an array of rows. Each row is an
+ # array of field values.
+ def select_rows(sql, name = nil)
+ select_raw(sql, name).last
+ end
+
+ # Executes an INSERT query and returns the new record's ID
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
+ table = sql.split(" ", 4)[2].gsub('"', '')
+ super || pk && last_insert_id(table, sequence_name || default_sequence_name(table, pk))
+ end
+
+ # create a 2D array representing the result set
+ def result_as_array(res) #:nodoc:
+ ary = []
+ for i in 0...res.ntuples do
+ ary << []
+ for j in 0...res.nfields do
+ ary[i] << res.getvalue(i,j)
+ end
+ end
+ return ary
+ end
+
+
+ # Queries the database and returns the results in an Array-like object
+ def query(sql, name = nil) #:nodoc:
+ log(sql, name) do
+ if @async
+ res = @connection.async_exec(sql)
+ else
+ res = @connection.exec(sql)
+ end
+ return result_as_array(res)
+ end
+ end
+
+ # Executes an SQL statement, returning a PGresult object on success
+ # or raising a PGError exception otherwise.
+ def execute(sql, name = nil)
+ log(sql, name) do
+ if @async
+ @connection.async_exec(sql)
+ else
+ @connection.exec(sql)
+ end
+ end
+ end
+
+ # Executes an UPDATE query and returns the number of affected tuples.
+ def update_sql(sql, name = nil)
+ super.cmd_tuples
+ end
+
+ # Begins a transaction.
+ def begin_db_transaction
+ execute "BEGIN"
+ end
+
+ # Commits a transaction.
+ def commit_db_transaction
+ execute "COMMIT"
+ end
+
+ # Aborts a transaction.
+ def rollback_db_transaction
+ execute "ROLLBACK"
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def recreate_database(name) #:nodoc:
+ drop_database(name)
+ create_database(name)
+ end
+
+ # Create a new PostgreSQL database. Options include :owner, :template,
+ # :encoding, :tablespace, and :connection_limit (note that MySQL uses
+ # :charset while PostgreSQL uses :encoding).
+ #
+ # Example:
+ # create_database config[:database], config
+ # create_database 'foo_development', :encoding => 'unicode'
+ def create_database(name, options = {})
+ options = options.reverse_merge(:encoding => "utf8")
+
+ option_string = options.symbolize_keys.sum do |key, value|
+ case key
+ when :owner
+ " OWNER = '#{value}'"
+ when :template
+ " TEMPLATE = #{value}"
+ when :encoding
+ " ENCODING = '#{value}'"
+ when :tablespace
+ " TABLESPACE = #{value}"
+ when :connection_limit
+ " CONNECTION LIMIT = #{value}"
+ else
+ ""
+ end
+ end
+
+ execute "CREATE DATABASE #{name}#{option_string}"
+ end
+
+ # Drops a PostgreSQL database
+ #
+ # Example:
+ # drop_database 'matt_development'
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS #{name}"
+ end
+
+
+ # Returns the list of all tables in the schema search path or a specified schema.
+ def tables(name = nil)
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
+ query(<<-SQL, name).map { |row| row[0] }
+ SELECT tablename
+ FROM pg_tables
+ WHERE schemaname IN (#{schemas})
+ SQL
+ end
+
+ # Returns the list of all indexes for a table.
+ def indexes(table_name, name = nil)
+ schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
+ result = query(<<-SQL, name)
+ SELECT distinct i.relname, d.indisunique, a.attname
+ FROM pg_class t, pg_class i, pg_index d, pg_attribute a
+ WHERE i.relkind = 'i'
+ AND d.indexrelid = i.oid
+ AND d.indisprimary = 'f'
+ AND t.oid = d.indrelid
+ AND t.relname = '#{table_name}'
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) )
+ AND a.attrelid = t.oid
+ AND ( d.indkey[0]=a.attnum OR d.indkey[1]=a.attnum
+ OR d.indkey[2]=a.attnum OR d.indkey[3]=a.attnum
+ OR d.indkey[4]=a.attnum OR d.indkey[5]=a.attnum
+ OR d.indkey[6]=a.attnum OR d.indkey[7]=a.attnum
+ OR d.indkey[8]=a.attnum OR d.indkey[9]=a.attnum )
+ ORDER BY i.relname
+ SQL
+
+ current_index = nil
+ indexes = []
+
+ result.each do |row|
+ if current_index != row[0]
+ indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])
+ current_index = row[0]
+ end
+
+ indexes.last.columns << row[2]
+ end
+
+ indexes
+ end
+
+ # Returns the list of all column definitions for a table.
+ def columns(table_name, name = nil)
+ # Limit, precision, and scale are all handled by the superclass.
+ column_definitions(table_name).collect do |name, type, default, notnull|
+ PostgreSQLColumn.new(name, default, type, notnull == 'f')
+ end
+ end
+
+ # Sets the schema search path to a string of comma-separated schema names.
+ # Names beginning with $ have to be quoted (e.g. $user => '$user').
+ # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html
+ #
+ # This should be not be called manually but set in database.yml.
+ def schema_search_path=(schema_csv)
+ if schema_csv
+ execute "SET search_path TO #{schema_csv}"
+ @schema_search_path = schema_csv
+ end
+ end
+
+ # Returns the active schema search path.
+ def schema_search_path
+ @schema_search_path ||= query('SHOW search_path')[0][0]
+ end
+
+ # Returns the current client message level.
+ def client_min_messages
+ query('SHOW client_min_messages')[0][0]
+ end
+
+ # Set the client message level.
+ def client_min_messages=(level)
+ execute("SET client_min_messages TO '#{level}'")
+ end
+
+ # Returns the sequence name for a table's primary key or some other specified key.
+ def default_sequence_name(table_name, pk = nil) #:nodoc:
+ default_pk, default_seq = pk_and_sequence_for(table_name)
+ default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq"
+ end
+
+ # Resets the sequence of a table's primary key to the maximum value.
+ def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc:
+ unless pk and sequence
+ default_pk, default_sequence = pk_and_sequence_for(table)
+ pk ||= default_pk
+ sequence ||= default_sequence
+ end
+ if pk
+ if sequence
+ quoted_sequence = quote_column_name(sequence)
+
+ select_value <<-end_sql, 'Reset sequence'
+ SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false)
+ end_sql
+ else
+ @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
+ end
+ end
+ end
+
+ # Returns a table's primary key and belonging sequence.
+ def pk_and_sequence_for(table) #:nodoc:
+ # First try looking for a sequence with a dependency on the
+ # given table's primary key.
+ result = query(<<-end_sql, 'PK and serial sequence')[0]
+ SELECT attr.attname, seq.relname
+ FROM pg_class seq,
+ pg_attribute attr,
+ pg_depend dep,
+ pg_namespace name,
+ pg_constraint cons
+ WHERE seq.oid = dep.objid
+ AND seq.relkind = 'S'
+ AND attr.attrelid = dep.refobjid
+ AND attr.attnum = dep.refobjsubid
+ AND attr.attrelid = cons.conrelid
+ AND attr.attnum = cons.conkey[1]
+ AND cons.contype = 'p'
+ AND dep.refobjid = '#{table}'::regclass
+ end_sql
+
+ if result.nil? or result.empty?
+ # If that fails, try parsing the primary key's default value.
+ # Support the 7.x and 8.0 nextval('foo'::text) as well as
+ # the 8.1+ nextval('foo'::regclass).
+ result = query(<<-end_sql, 'PK and custom sequence')[0]
+ SELECT attr.attname,
+ CASE
+ WHEN split_part(def.adsrc, '''', 2) ~ '.' THEN
+ substr(split_part(def.adsrc, '''', 2),
+ strpos(split_part(def.adsrc, '''', 2), '.')+1)
+ ELSE split_part(def.adsrc, '''', 2)
+ END
+ FROM pg_class t
+ JOIN pg_attribute attr ON (t.oid = attrelid)
+ JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum)
+ JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1])
+ WHERE t.oid = '#{table}'::regclass
+ AND cons.contype = 'p'
+ AND def.adsrc ~* 'nextval'
+ end_sql
+ end
+
+ # [primary_key, sequence]
+ [result.first, result.last]
+ rescue
+ nil
+ end
+
+ # Renames a table.
+ def rename_table(name, new_name)
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
+ end
+
+ # Adds a new column to the named table.
+ # See TableDefinition#column for details of the options you can use.
+ def add_column(table_name, column_name, type, options = {})
+ default = options[:default]
+ notnull = options[:null] == false
+
+ # Add the column.
+ execute("ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}")
+
+ change_column_default(table_name, column_name, default) if options_include_default?(options)
+ change_column_null(table_name, column_name, false, default) if notnull
+ end
+
+ # Changes the column of a table.
+ def change_column(table_name, column_name, type, options = {})
+ quoted_table_name = quote_table_name(table_name)
+
+ begin
+ execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ rescue ActiveRecord::StatementInvalid
+ # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it.
+ begin
+ begin_db_transaction
+ tmp_column_name = "#{column_name}_ar_tmp"
+ add_column(table_name, tmp_column_name, type, options)
+ execute "UPDATE #{quoted_table_name} SET #{quote_column_name(tmp_column_name)} = CAST(#{quote_column_name(column_name)} AS #{type_to_sql(type, options[:limit], options[:precision], options[:scale])})"
+ remove_column(table_name, column_name)
+ rename_column(table_name, tmp_column_name, column_name)
+ commit_db_transaction
+ rescue
+ rollback_db_transaction
+ end
+ end
+
+ change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
+ change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ end
+
+ # Changes the default value of a table column.
+ def change_column_default(table_name, column_name, default)
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+ execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
+ end
+
+ # Renames a column in a table.
+ def rename_column(table_name, column_name, new_column_name)
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
+ end
+
+ # Drops an index from a table.
+ def remove_index(table_name, options = {})
+ execute "DROP INDEX #{index_name(table_name, options)}"
+ end
+
+ # Maps logical Rails types to PostgreSQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ if limit.nil? || limit == 4
+ 'integer'
+ elsif limit < 4
+ 'smallint'
+ else
+ 'bigint'
+ end
+ end
+
+ # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
+ #
+ # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and
+ # requires that the ORDER BY include the distinct column.
+ #
+ # distinct("posts.id", "posts.created_at desc")
+ def distinct(columns, order_by) #:nodoc:
+ return "DISTINCT #{columns}" if order_by.blank?
+
+ # Construct a clean list of column names from the ORDER BY clause, removing
+ # any ASC/DESC modifiers
+ order_columns = order_by.split(',').collect { |s| s.split.first }
+ order_columns.delete_if &:blank?
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
+
+ # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
+ # all the required columns for the ORDER BY to work properly.
+ sql = "DISTINCT ON (#{columns}) #{columns}, "
+ sql << order_columns * ', '
+ end
+
+ # Returns an ORDER BY clause for the passed order option.
+ #
+ # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this
+ # by wrapping the +sql+ string as a sub-select and ordering in that query.
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
+ return sql if options[:order].blank?
+
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
+ order.map! { |s| 'DESC' if s =~ /\bdesc$/i }
+ order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ')
+
+ sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}"
+ end
+
+ protected
+ # Returns the version of the connected PostgreSQL version.
+ def postgresql_version
+ @postgresql_version ||=
+ if @connection.respond_to?(:server_version)
+ @connection.server_version
+ else
+ # Mimic PGconn.server_version behavior
+ begin
+ query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/
+ ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i
+ rescue
+ 0
+ end
+ end
+ end
+
+ private
+ # The internal PostgreSQL identifer of the money data type.
+ MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
+
+ # Connects to a PostgreSQL server and sets up the adapter depending on the
+ # connected server's characteristics.
+ def connect
+ @connection = PGconn.connect(*@connection_parameters)
+ PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
+
+ # Ignore async_exec and async_query when using postgres-pr.
+ @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec)
+
+ # Use escape string syntax if available. We cannot do this lazily when encountering
+ # the first string, because that could then break any transactions in progress.
+ # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html
+ # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't
+ # support escape string syntax. Don't override the inherited quoted_string_prefix.
+ if supports_standard_conforming_strings?
+ self.class.instance_eval do
+ define_method(:quoted_string_prefix) { 'E' }
+ end
+ end
+
+ # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
+ # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
+ # should know about this but can't detect it there, so deal with it here.
+ money_precision = (postgresql_version >= 80300) ? 19 : 10
+ PostgreSQLColumn.module_eval(<<-end_eval)
+ def extract_precision(sql_type)
+ if sql_type =~ /^money$/
+ #{money_precision}
+ else
+ super
+ end
+ end
+ end_eval
+
+ configure_connection
+ end
+
+ # Configures the encoding, verbosity, and schema search path of the connection.
+ # This is called by #connect and should not be called manually.
+ def configure_connection
+ if @config[:encoding]
+ if @connection.respond_to?(:set_client_encoding)
+ @connection.set_client_encoding(@config[:encoding])
+ else
+ execute("SET client_encoding TO '#{@config[:encoding]}'")
+ end
+ end
+ self.client_min_messages = @config[:min_messages] if @config[:min_messages]
+ self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
+ end
+
+ # Returns the current ID of a table's sequence.
+ def last_insert_id(table, sequence_name) #:nodoc:
+ Integer(select_value("SELECT currval('#{sequence_name}')"))
+ end
+
+ # Executes a SELECT query and returns the results, performing any data type
+ # conversions that are required to be performed here instead of in PostgreSQLColumn.
+ def select(sql, name = nil)
+ fields, rows = select_raw(sql, name)
+ result = []
+ for row in rows
+ row_hash = {}
+ fields.each_with_index do |f, i|
+ row_hash[f] = row[i]
+ end
+ result << row_hash
+ end
+ result
+ end
+
+ def select_raw(sql, name = nil)
+ res = execute(sql, name)
+ results = result_as_array(res)
+ fields = []
+ rows = []
+ if res.ntuples > 0
+ fields = res.fields
+ results.each do |row|
+ hashed_row = {}
+ row.each_index do |cell_index|
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ if res.ftype(cell_index) == MONEY_COLUMN_TYPE_OID
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ case column = row[cell_index]
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ row[cell_index] = column.gsub(/[^-\d\.]/, '')
+ when /^-?\D+[\d\.]+,\d{2}$/ # (2)
+ row[cell_index] = column.gsub(/[^-\d,]/, '').sub(/,/, '.')
+ end
+ end
+
+ hashed_row[fields[cell_index]] = column
+ end
+ rows << row
+ end
+ end
+ res.clear
+ return fields, rows
+ end
+
+ # Returns the list of a table's column names, data types, and default values.
+ #
+ # The underlying query is roughly:
+ # SELECT column.name, column.type, default.value
+ # FROM column LEFT JOIN default
+ # ON column.table_id = default.table_id
+ # AND column.num = default.column_num
+ # WHERE column.table_id = get_table_id('table_name')
+ # AND column.num > 0
+ # AND NOT column.is_dropped
+ # ORDER BY column.num
+ #
+ # If the table name is not prefixed with a schema, the database will
+ # take the first match from the schema search path.
+ #
+ # Query implementation notes:
+ # - format_type includes the column size constraint, e.g. varchar(50)
+ # - ::regclass is a function that gives the id for a table name
+ def column_definitions(table_name) #:nodoc:
+ query <<-end_sql
+ SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull
+ FROM pg_attribute a LEFT JOIN pg_attrdef d
+ ON a.attrelid = d.adrelid AND a.attnum = d.adnum
+ WHERE a.attrelid = '#{table_name}'::regclass
+ AND a.attnum > 0 AND NOT a.attisdropped
+ ORDER BY a.attnum
+ end_sql
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
new file mode 100644
index 0000000..cc9c465
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -0,0 +1,34 @@
+require 'active_record/connection_adapters/sqlite_adapter'
+
+module ActiveRecord
+ class Base
+ # sqlite3 adapter reuses sqlite_connection.
+ def self.sqlite3_connection(config) # :nodoc:
+ parse_sqlite_config!(config)
+
+ unless self.class.const_defined?(:SQLite3)
+ require_library_or_gem(config[:adapter])
+ end
+
+ db = SQLite3::Database.new(
+ config[:database],
+ :results_as_hash => true,
+ :type_translation => false
+ )
+
+ db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
+
+ ConnectionAdapters::SQLite3Adapter.new(db, logger)
+ end
+ end
+
+ module ConnectionAdapters #:nodoc:
+ class SQLite3Adapter < SQLiteAdapter # :nodoc:
+ def table_structure(table_name)
+ returning structure = @connection.table_info(quote_table_name(table_name)) do
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
new file mode 100644
index 0000000..51cfd10
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -0,0 +1,406 @@
+require 'active_record/connection_adapters/abstract_adapter'
+
+module ActiveRecord
+ class Base
+ class << self
+ # Establishes a connection to the database that's used by all Active Record objects
+ def sqlite_connection(config) # :nodoc:
+ parse_sqlite_config!(config)
+
+ unless self.class.const_defined?(:SQLite)
+ require_library_or_gem(config[:adapter])
+
+ db = SQLite::Database.new(config[:database], 0)
+ db.show_datatypes = "ON" if !defined? SQLite::Version
+ db.results_as_hash = true if defined? SQLite::Version
+ db.type_translation = false
+
+ # "Downgrade" deprecated sqlite API
+ if SQLite.const_defined?(:Version)
+ ConnectionAdapters::SQLite2Adapter.new(db, logger)
+ else
+ ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
+ end
+ end
+ end
+
+ private
+ def parse_sqlite_config!(config)
+ config[:database] ||= config[:dbfile]
+ # Require database.
+ unless config[:database]
+ raise ArgumentError, "No database file specified. Missing argument: database"
+ end
+
+ # Allow database path relative to RAILS_ROOT, but only if
+ # the database path is not the special path that tells
+ # Sqlite to build a database only in memory.
+ if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database]
+ config[:database] = File.expand_path(config[:database], RAILS_ROOT)
+ end
+ end
+ end
+ end
+
+ module ConnectionAdapters #:nodoc:
+ class SQLiteColumn < Column #:nodoc:
+ class << self
+ def string_to_binary(value)
+ value.gsub(/\0|\%/n) do |b|
+ case b
+ when "\0" then "%00"
+ when "%" then "%25"
+ end
+ end
+ end
+
+ def binary_to_string(value)
+ value.gsub(/%00|%25/n) do |b|
+ case b
+ when "%00" then "\0"
+ when "%25" then "%"
+ end
+ end
+ end
+ end
+ end
+
+ # The SQLite adapter works with both the 2.x and 3.x series of SQLite with the sqlite-ruby drivers (available both as gems and
+ # from http://rubyforge.org/projects/sqlite-ruby/).
+ #
+ # Options:
+ #
+ # * :database - Path to the database file.
+ class SQLiteAdapter < AbstractAdapter
+ def adapter_name #:nodoc:
+ 'SQLite'
+ end
+
+ def supports_migrations? #:nodoc:
+ true
+ end
+
+ def requires_reloading?
+ true
+ end
+
+ def disconnect!
+ super
+ @connection.close rescue nil
+ end
+
+ def supports_count_distinct? #:nodoc:
+ sqlite_version >= '3.2.6'
+ end
+
+ def supports_autoincrement? #:nodoc:
+ sqlite_version >= '3.1.0'
+ end
+
+ def native_database_types #:nodoc:
+ {
+ :primary_key => default_primary_key_type,
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "integer" },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "boolean" }
+ }
+ end
+
+
+ # QUOTING ==================================================
+
+ def quote_string(s) #:nodoc:
+ @connection.class.quote(s)
+ end
+
+ def quote_column_name(name) #:nodoc:
+ %Q("#{name}")
+ end
+
+
+ # DATABASE STATEMENTS ======================================
+
+ def execute(sql, name = nil) #:nodoc:
+ catch_schema_changes { log(sql, name) { @connection.execute(sql) } }
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.changes
+ end
+
+ def delete_sql(sql, name = nil) #:nodoc:
+ sql += " WHERE 1=1" unless sql =~ /WHERE/i
+ super sql, name
+ end
+
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
+ super || @connection.last_insert_row_id
+ end
+
+ def select_rows(sql, name = nil)
+ execute(sql, name).map do |row|
+ (0...(row.size / 2)).map { |i| row[i] }
+ end
+ end
+
+ def begin_db_transaction #:nodoc:
+ catch_schema_changes { @connection.transaction }
+ end
+
+ def commit_db_transaction #:nodoc:
+ catch_schema_changes { @connection.commit }
+ end
+
+ def rollback_db_transaction #:nodoc:
+ catch_schema_changes { @connection.rollback }
+ end
+
+
+ # SELECT ... FOR UPDATE is redundant since the table is locked.
+ def add_lock!(sql, options) #:nodoc:
+ sql
+ end
+
+
+ # SCHEMA STATEMENTS ========================================
+
+ def tables(name = nil) #:nodoc:
+ sql = <<-SQL
+ SELECT name
+ FROM sqlite_master
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
+ SQL
+
+ execute(sql, name).map do |row|
+ row[0]
+ end
+ end
+
+ def columns(table_name, name = nil) #:nodoc:
+ table_structure(table_name).map do |field|
+ SQLiteColumn.new(field['name'], field['dflt_value'], field['type'], field['notnull'] == "0")
+ end
+ end
+
+ def indexes(table_name, name = nil) #:nodoc:
+ execute("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
+ index = IndexDefinition.new(table_name, row['name'])
+ index.unique = row['unique'] != '0'
+ index.columns = execute("PRAGMA index_info('#{index.name}')").map { |col| col['name'] }
+ index
+ end
+ end
+
+ def primary_key(table_name) #:nodoc:
+ column = table_structure(table_name).find {|field| field['pk'].to_i == 1}
+ column ? column['name'] : nil
+ end
+
+ def remove_index(table_name, options={}) #:nodoc:
+ execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}"
+ end
+
+ def rename_table(name, new_name)
+ execute "ALTER TABLE #{name} RENAME TO #{new_name}"
+ end
+
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
+ raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
+ end
+
+ super(table_name, column_name, type, options)
+ # See last paragraph on http://www.sqlite.org/lang_altertable.html
+ execute "VACUUM"
+ end
+
+ def remove_column(table_name, *column_names) #:nodoc:
+ column_names.flatten.each do |column_name|
+ alter_table(table_name) do |definition|
+ definition.columns.delete(definition[column_name])
+ end
+ end
+ end
+ alias :remove_columns :remove_column
+
+ def change_column_default(table_name, column_name, default) #:nodoc:
+ alter_table(table_name) do |definition|
+ definition[column_name].default = default
+ end
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ alter_table(table_name) do |definition|
+ include_default = options_include_default?(options)
+ definition[column_name].instance_eval do
+ self.type = type
+ self.limit = options[:limit] if options.include?(:limit)
+ self.default = options[:default] if include_default
+ self.null = options[:null] if options.include?(:null)
+ end
+ end
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
+ end
+
+ def empty_insert_statement(table_name)
+ "INSERT INTO #{table_name} VALUES(NULL)"
+ end
+
+ protected
+ def select(sql, name = nil) #:nodoc:
+ execute(sql, name).map do |row|
+ record = {}
+ row.each_key do |key|
+ if key.is_a?(String)
+ record[key.sub(/^"?\w+"?\./, '')] = row[key]
+ end
+ end
+ record
+ end
+ end
+
+ def table_structure(table_name)
+ returning structure = execute("PRAGMA table_info(#{quote_table_name(table_name)})") do
+ raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
+ end
+ end
+
+ def alter_table(table_name, options = {}) #:nodoc:
+ altered_table_name = "altered_#{table_name}"
+ caller = lambda {|definition| yield definition if block_given?}
+
+ transaction do
+ move_table(table_name, altered_table_name,
+ options.merge(:temporary => true))
+ move_table(altered_table_name, table_name, &caller)
+ end
+ end
+
+ def move_table(from, to, options = {}, &block) #:nodoc:
+ copy_table(from, to, options, &block)
+ drop_table(from)
+ end
+
+ def copy_table(from, to, options = {}) #:nodoc:
+ options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
+ create_table(to, options) do |definition|
+ @definition = definition
+ columns(from).each do |column|
+ column_name = options[:rename] ?
+ (options[:rename][column.name] ||
+ options[:rename][column.name.to_sym] ||
+ column.name) : column.name
+
+ @definition.column(column_name, column.type,
+ :limit => column.limit, :default => column.default,
+ :null => column.null)
+ end
+ @definition.primary_key(primary_key(from)) if primary_key(from)
+ yield @definition if block_given?
+ end
+
+ copy_table_indexes(from, to, options[:rename] || {})
+ copy_table_contents(from, to,
+ @definition.columns.map {|column| column.name},
+ options[:rename] || {})
+ end
+
+ def copy_table_indexes(from, to, rename = {}) #:nodoc:
+ indexes(from).each do |index|
+ name = index.name
+ if to == "altered_#{from}"
+ name = "temp_#{name}"
+ elsif from == "altered_#{to}"
+ name = name[5..-1]
+ end
+
+ to_column_names = columns(to).map(&:name)
+ columns = index.columns.map {|c| rename[c] || c }.select do |column|
+ to_column_names.include?(column)
+ end
+
+ unless columns.empty?
+ # index name can't be the same
+ opts = { :name => name.gsub(/_(#{from})_/, "_#{to}_") }
+ opts[:unique] = true if index.unique
+ add_index(to, columns, opts)
+ end
+ end
+ end
+
+ def copy_table_contents(from, to, columns, rename = {}) #:nodoc:
+ column_mappings = Hash[*columns.map {|name| [name, name]}.flatten]
+ rename.inject(column_mappings) {|map, a| map[a.last] = a.first; map}
+ from_columns = columns(from).collect {|col| col.name}
+ columns = columns.find_all{|col| from_columns.include?(column_mappings[col])}
+ quoted_columns = columns.map { |col| quote_column_name(col) } * ','
+
+ quoted_to = quote_table_name(to)
+ @connection.execute "SELECT * FROM #{quote_table_name(from)}" do |row|
+ sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES ("
+ sql << columns.map {|col| quote row[column_mappings[col]]} * ', '
+ sql << ')'
+ @connection.execute sql
+ end
+ end
+
+ def catch_schema_changes
+ return yield
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message =~ /database schema has changed/
+ reconnect!
+ retry
+ else
+ raise
+ end
+ end
+
+ def sqlite_version
+ @sqlite_version ||= select_value('select sqlite_version(*)')
+ end
+
+ def default_primary_key_type
+ if supports_autoincrement?
+ 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL'.freeze
+ else
+ 'INTEGER PRIMARY KEY NOT NULL'.freeze
+ end
+ end
+ end
+
+ class SQLite2Adapter < SQLiteAdapter # :nodoc:
+ def supports_count_distinct? #:nodoc:
+ false
+ end
+
+ def rename_table(name, new_name)
+ move_table(name, new_name)
+ end
+
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
+ alter_table(table_name) do |definition|
+ definition.column(column_name, type, options)
+ end
+ end
+ end
+
+ class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
+ def insert(sql, name = nil, pk = nil, id_value = nil)
+ execute(sql, name = nil)
+ id_value || @connection.last_insert_rowid
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/dirty.rb b/vendor/rails/activerecord/lib/active_record/dirty.rb
new file mode 100644
index 0000000..b32e17e
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/dirty.rb
@@ -0,0 +1,158 @@
+module ActiveRecord
+ # Track unsaved attribute changes.
+ #
+ # A newly instantiated object is unchanged:
+ # person = Person.find_by_name('uncle bob')
+ # person.changed? # => false
+ #
+ # Change the name:
+ # person.name = 'Bob'
+ # person.changed? # => true
+ # person.name_changed? # => true
+ # person.name_was # => 'uncle bob'
+ # person.name_change # => ['uncle bob', 'Bob']
+ # person.name = 'Bill'
+ # person.name_change # => ['uncle bob', 'Bill']
+ #
+ # Save the changes:
+ # person.save
+ # person.changed? # => false
+ # person.name_changed? # => false
+ #
+ # Assigning the same value leaves the attribute unchanged:
+ # person.name = 'Bill'
+ # person.name_changed? # => false
+ # person.name_change # => nil
+ #
+ # Which attributes have changed?
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ # person.changes # => { 'name' => ['Bill', 'bob'] }
+ #
+ # Before modifying an attribute in-place:
+ # person.name_will_change!
+ # person.name << 'by'
+ # person.name_change # => ['uncle bob', 'uncle bobby']
+ module Dirty
+ def self.included(base)
+ base.attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
+ base.alias_method_chain :write_attribute, :dirty
+ base.alias_method_chain :save, :dirty
+ base.alias_method_chain :save!, :dirty
+ base.alias_method_chain :update, :dirty
+ base.alias_method_chain :reload, :dirty
+
+ base.superclass_delegating_accessor :partial_updates
+ base.partial_updates = true
+ end
+
+ # Do any attributes have unsaved changes?
+ # person.changed? # => false
+ # person.name = 'bob'
+ # person.changed? # => true
+ def changed?
+ !changed_attributes.empty?
+ end
+
+ # List of attributes with unsaved changes.
+ # person.changed # => []
+ # person.name = 'bob'
+ # person.changed # => ['name']
+ def changed
+ changed_attributes.keys
+ end
+
+ # Map of changed attrs => [original value, new value]
+ # person.changes # => {}
+ # person.name = 'bob'
+ # person.changes # => { 'name' => ['bill', 'bob'] }
+ def changes
+ changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h }
+ end
+
+ # Attempts to +save+ the record and clears changed attributes if successful.
+ def save_with_dirty(*args) #:nodoc:
+ if status = save_without_dirty(*args)
+ changed_attributes.clear
+ end
+ status
+ end
+
+ # Attempts to save! the record and clears changed attributes if successful.
+ def save_with_dirty!(*args) #:nodoc:
+ status = save_without_dirty!(*args)
+ changed_attributes.clear
+ status
+ end
+
+ # reload the record and clears changed attributes.
+ def reload_with_dirty(*args) #:nodoc:
+ record = reload_without_dirty(*args)
+ changed_attributes.clear
+ record
+ end
+
+ private
+ # Map of change attr => original value.
+ def changed_attributes
+ @changed_attributes ||= {}
+ end
+
+ # Handle *_changed? for method_missing.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
+
+ # Handle *_change for method_missing.
+ def attribute_change(attr)
+ [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
+ end
+
+ # Handle *_was for method_missing.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
+ end
+
+ # Handle *_will_change! for method_missing.
+ def attribute_will_change!(attr)
+ changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
+ end
+
+ # Wrap write_attribute to remember original attribute value.
+ def write_attribute_with_dirty(attr, value)
+ attr = attr.to_s
+
+ # The attribute already has an unsaved change.
+ unless changed_attributes.include?(attr)
+ old = clone_attribute_value(:read_attribute, attr)
+ changed_attributes[attr] = old if field_changed?(attr, old, value)
+ end
+
+ # Carry on.
+ write_attribute_without_dirty(attr, value)
+ end
+
+ def update_with_dirty
+ if partial_updates?
+ update_without_dirty(changed)
+ else
+ update_without_dirty
+ end
+ end
+
+ def field_changed?(attr, old, value)
+ if column = column_for_attribute(attr)
+ if column.type == :integer && column.null && old.nil?
+ # For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
+ # Hence we don't record it as a change if the value changes from nil to ''.
+ value = nil if value.blank?
+ else
+ value = column.type_cast(value)
+ end
+ end
+
+ old != value
+ end
+
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/fixtures.rb b/vendor/rails/activerecord/lib/active_record/fixtures.rb
new file mode 100644
index 0000000..c4cbe5d
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/fixtures.rb
@@ -0,0 +1,997 @@
+require 'erb'
+require 'yaml'
+require 'csv'
+require 'active_support/test_case'
+
+if RUBY_VERSION < '1.9'
+ module YAML #:nodoc:
+ class Omap #:nodoc:
+ def keys; map { |k, v| k } end
+ def values; map { |k, v| v } end
+ end
+ end
+end
+
+if defined? ActiveRecord
+ class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
+ end
+else
+ class FixtureClassNotFound < StandardError #:nodoc:
+ end
+end
+
+# Fixtures are a way of organizing data that you want to test against; in short, sample data. They come in 3 flavors:
+#
+# 1. YAML fixtures
+# 2. CSV fixtures
+# 3. Single-file fixtures
+#
+# = YAML fixtures
+#
+# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures
+# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+.
+#
+# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed in the directory appointed
+# by ActiveSupport::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just
+# put your files in /test/fixtures/). The fixture file ends with the .yml file extension (Rails example:
+# /test/fixtures/web_sites.yml). The format of a YAML fixture file looks like this:
+#
+# rubyonrails:
+# id: 1
+# name: Ruby on Rails
+# url: http://www.rubyonrails.org
+#
+# google:
+# id: 2
+# name: Google
+# url: http://www.google.com
+#
+# This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an
+# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing
+# pleasure.
+#
+# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. See http://yaml.org/type/omap.html
+# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table.
+# This is commonly needed for tree structures. Example:
+#
+# --- !omap
+# - parent:
+# id: 1
+# parent_id: NULL
+# title: Parent
+# - child:
+# id: 2
+# parent_id: 1
+# title: Child
+#
+# = CSV fixtures
+#
+# Fixtures can also be kept in the Comma Separated Value format. Akin to YAML fixtures, CSV fixtures are stored
+# in a single file, but instead end with the .csv file extension
+# (Rails example: /test/fixtures/web_sites.csv).
+#
+# The format of this type of fixture file is much more compact than the others, but also a little harder to read by us
+# humans. The first line of the CSV file is a comma-separated list of field names. The rest of the file is then comprised
+# of the actual data (1 per line). Here's an example:
+#
+# id, name, url
+# 1, Ruby On Rails, http://www.rubyonrails.org
+# 2, Google, http://www.google.com
+#
+# Should you have a piece of data with a comma character in it, you can place double quotes around that value. If you
+# need to use a double quote character, you must escape it with another double quote.
+#
+# Another unique attribute of the CSV fixture is that it has *no* fixture name like the other two formats. Instead, the
+# fixture names are automatically generated by deriving the class name of the fixture file and adding an incrementing
+# number to the end. In our example, the 1st fixture would be called "web_site_1" and the 2nd one would be called
+# "web_site_2".
+#
+# Most databases and spreadsheets support exporting to CSV format, so this is a great format for you to choose if you
+# have existing data somewhere already.
+#
+# = Single-file fixtures
+#
+# This type of fixture was the original format for Active Record that has since been deprecated in favor of the YAML and CSV formats.
+# Fixtures for this format are created by placing text files in a sub-directory (with the name of the model) to the directory
+# appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically configured for Rails, so you can just
+# put your files in /test/fixtures// --
+# like /test/fixtures/web_sites/ for the WebSite model).
+#
+# Each text file placed in this directory represents a "record". Usually these types of fixtures are named without
+# extensions, but if you are on a Windows machine, you might consider adding .txt as the extension. Here's what the
+# above example might look like:
+#
+# web_sites/google
+# web_sites/yahoo.txt
+# web_sites/ruby-on-rails
+#
+# The file format of a standard fixture is simple. Each line is a property (or column in db speak) and has the syntax
+# of "name => value". Here's an example of the ruby-on-rails fixture above:
+#
+# id => 1
+# name => Ruby on Rails
+# url => http://www.rubyonrails.org
+#
+# = Using Fixtures
+#
+# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the
+# fixtures, but first let's take a look at a sample unit test:
+#
+# require 'web_site'
+#
+# class WebSiteTest < ActiveSupport::TestCase
+# def test_web_site_count
+# assert_equal 2, WebSite.count
+# end
+# end
+#
+# As it stands, unless we pre-load the web_site table in our database with two records, this test will fail. Here's the
+# easiest way to add fixtures to the database:
+#
+# ...
+# class WebSiteTest < ActiveSupport::TestCase
+# fixtures :web_sites # add more by separating the symbols with commas
+# ...
+#
+# By adding a "fixtures" method to the test case and passing it a list of symbols (only one is shown here though), we trigger
+# the testing environment to automatically load the appropriate fixtures into the database before each test.
+# To ensure consistent data, the environment deletes the fixtures before running the load.
+#
+# In addition to being available in the database, the fixtures are also loaded into a hash stored in an instance variable
+# of the test case. It is named after the symbol... so, in our example, there would be a hash available called
+# @web_sites. This is where the "fixture name" comes into play.
+#
+# On top of that, each record is automatically "found" (using Model.find(id)) and placed in the instance variable of its name.
+# So for the YAML fixtures, we'd get @rubyonrails and @google, which could be interrogated using regular Active Record semantics:
+#
+# # test if the object created from the fixture data has the same attributes as the data itself
+# def test_find
+# assert_equal @web_sites["rubyonrails"]["name"], @rubyonrails.name
+# end
+#
+# As seen above, the data hash created from the YAML fixtures would have @web_sites["rubyonrails"]["url"] return
+# "http://www.rubyonrails.org" and @web_sites["google"]["name"] would return "Google". The same fixtures, but loaded
+# from a CSV fixture file, would be accessible via @web_sites["web_site_1"]["name"] == "Ruby on Rails" and have the individual
+# fixtures available as instance variables @web_site_1 and @web_site_2.
+#
+# If you do not wish to use instantiated fixtures (usually for performance reasons) there are two options.
+#
+# - to completely disable instantiated fixtures:
+# self.use_instantiated_fixtures = false
+#
+# - to keep the fixture instance (@web_sites) available, but do not automatically 'find' each instance:
+# self.use_instantiated_fixtures = :no_instances
+#
+# Even if auto-instantiated fixtures are disabled, you can still access them
+# by name via special dynamic methods. Each method has the same name as the
+# model, and accepts the name of the fixture to instantiate:
+#
+# fixtures :web_sites
+#
+# def test_find
+# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
+# end
+#
+# = Dynamic fixtures with ERb
+#
+# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can
+# mix ERb in with your YAML or CSV fixtures to create a bunch of fixtures for load testing, like:
+#
+# <% for i in 1..1000 %>
+# fix_<%= i %>:
+# id: <%= i %>
+# name: guy_<%= 1 %>
+# <% end %>
+#
+# This will create 1000 very simple YAML fixtures.
+#
+# Using ERb, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>.
+# This is however a feature to be used with some caution. The point of fixtures are that they're stable units of predictable
+# sample data. If you feel that you need to inject dynamic values, then perhaps you should reexamine whether your application
+# is properly testable. Hence, dynamic values in fixtures are to be considered a code smell.
+#
+# = Transactional fixtures
+#
+# TestCases can use begin+rollback to isolate their changes to the database instead of having to delete+insert for every test case.
+# They can also turn off auto-instantiation of fixture data since the feature is costly and often unused.
+#
+# class FooTest < ActiveSupport::TestCase
+# self.use_transactional_fixtures = true
+# self.use_instantiated_fixtures = false
+#
+# fixtures :foos
+#
+# def test_godzilla
+# assert !Foo.find(:all).empty?
+# Foo.destroy_all
+# assert Foo.find(:all).empty?
+# end
+#
+# def test_godzilla_aftermath
+# assert !Foo.find(:all).empty?
+# end
+# end
+#
+# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures,
+# then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes.
+#
+# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide
+# access to fixture data for every table that has been loaded through fixtures (depending on the value of +use_instantiated_fixtures+)
+#
+# When *not* to use transactional fixtures:
+# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until all parent transactions commit,
+# particularly, the fixtures transaction which is begun in setup and rolled back in teardown. Thus, you won't be able to verify
+# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
+# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
+# Use InnoDB, MaxDB, or NDB instead.
+#
+# = Advanced YAML Fixtures
+#
+# YAML fixtures that don't specify an ID get some extra features:
+#
+# * Stable, autogenerated ID's
+# * Label references for associations (belongs_to, has_one, has_many)
+# * HABTM associations as inline lists
+# * Autofilled timestamp columns
+# * Fixture label interpolation
+# * Support for YAML defaults
+#
+# == Stable, autogenerated ID's
+#
+# Here, have a monkey fixture:
+#
+# george:
+# id: 1
+# name: George the Monkey
+#
+# reginald:
+# id: 2
+# name: Reginald the Pirate
+#
+# Each of these fixtures has two unique identifiers: one for the database
+# and one for the humans. Why don't we generate the primary key instead?
+# Hashing each fixture's label yields a consistent ID:
+#
+# george: # generated id: 503576764
+# name: George the Monkey
+#
+# reginald: # generated id: 324201669
+# name: Reginald the Pirate
+#
+# Active Record looks at the fixture's model class, discovers the correct
+# primary key, and generates it right before inserting the fixture
+# into the database.
+#
+# The generated ID for a given label is constant, so we can discover
+# any fixture's ID without loading anything, as long as we know the label.
+#
+# == Label references for associations (belongs_to, has_one, has_many)
+#
+# Specifying foreign keys in fixtures can be very fragile, not to
+# mention difficult to read. Since Active Record can figure out the ID of
+# any fixture from its label, you can specify FK's by label instead of ID.
+#
+# === belongs_to
+#
+# Let's break out some more monkeys and pirates.
+#
+# ### in pirates.yml
+#
+# reginald:
+# id: 1
+# name: Reginald the Pirate
+# monkey_id: 1
+#
+# ### in monkeys.yml
+#
+# george:
+# id: 1
+# name: George the Monkey
+# pirate_id: 1
+#
+# Add a few more monkeys and pirates and break this into multiple files,
+# and it gets pretty hard to keep track of what's going on. Let's
+# use labels instead of ID's:
+#
+# ### in pirates.yml
+#
+# reginald:
+# name: Reginald the Pirate
+# monkey: george
+#
+# ### in monkeys.yml
+#
+# george:
+# name: George the Monkey
+# pirate: reginald
+#
+# Pow! All is made clear. Active Record reflects on the fixture's model class,
+# finds all the +belongs_to+ associations, and allows you to specify
+# a target *label* for the *association* (monkey: george) rather than
+# a target *id* for the *FK* (monkey_id: 1).
+#
+# ==== Polymorphic belongs_to
+#
+# Supporting polymorphic relationships is a little bit more complicated, since
+# Active Record needs to know what type your association is pointing at. Something
+# like this should look familiar:
+#
+# ### in fruit.rb
+#
+# belongs_to :eater, :polymorphic => true
+#
+# ### in fruits.yml
+#
+# apple:
+# id: 1
+# name: apple
+# eater_id: 1
+# eater_type: Monkey
+#
+# Can we do better? You bet!
+#
+# apple:
+# eater: george (Monkey)
+#
+# Just provide the polymorphic target type and Active Record will take care of the rest.
+#
+# === has_and_belongs_to_many
+#
+# Time to give our monkey some fruit.
+#
+# ### in monkeys.yml
+#
+# george:
+# id: 1
+# name: George the Monkey
+# pirate_id: 1
+#
+# ### in fruits.yml
+#
+# apple:
+# id: 1
+# name: apple
+#
+# orange:
+# id: 2
+# name: orange
+#
+# grape:
+# id: 3
+# name: grape
+#
+# ### in fruits_monkeys.yml
+#
+# apple_george:
+# fruit_id: 1
+# monkey_id: 1
+#
+# orange_george:
+# fruit_id: 2
+# monkey_id: 1
+#
+# grape_george:
+# fruit_id: 3
+# monkey_id: 1
+#
+# Let's make the HABTM fixture go away.
+#
+# ### in monkeys.yml
+#
+# george:
+# name: George the Monkey
+# pirate: reginald
+# fruits: apple, orange, grape
+#
+# ### in fruits.yml
+#
+# apple:
+# name: apple
+#
+# orange:
+# name: orange
+#
+# grape:
+# name: grape
+#
+# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
+# on George's fixture, but we could've just as easily specified a list
+# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
+# the fixture's model class and discovers the +has_and_belongs_to_many+
+# associations.
+#
+# == Autofilled timestamp columns
+#
+# If your table/model specifies any of Active Record's
+# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
+# they will automatically be set to Time.now.
+#
+# If you've set specific values, they'll be left alone.
+#
+# == Fixture label interpolation
+#
+# The label of the current fixture is always available as a column value:
+#
+# geeksomnia:
+# name: Geeksomnia's Account
+# subdomain: $LABEL
+#
+# Also, sometimes (like when porting older join table fixtures) you'll need
+# to be able to get ahold of the identifier for a given label. ERB
+# to the rescue:
+#
+# george_reginald:
+# monkey_id: <%= Fixtures.identify(:reginald) %>
+# pirate_id: <%= Fixtures.identify(:george) %>
+#
+# == Support for YAML defaults
+#
+# You probably already know how to use YAML to set and reuse defaults in
+# your database.yml file. You can use the same technique in your fixtures:
+#
+# DEFAULTS: &DEFAULTS
+# created_on: <%= 3.weeks.ago.to_s(:db) %>
+#
+# first:
+# name: Smurf
+# <<: *DEFAULTS
+#
+# second:
+# name: Fraggle
+# <<: *DEFAULTS
+#
+# Any fixture labeled "DEFAULTS" is safely ignored.
+
+class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
+ DEFAULT_FILTER_RE = /\.ya?ml$/
+
+ @@all_cached_fixtures = {}
+
+ def self.reset_cache(connection = nil)
+ connection ||= ActiveRecord::Base.connection
+ @@all_cached_fixtures[connection.object_id] = {}
+ end
+
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection.object_id] ||= {}
+ @@all_cached_fixtures[connection.object_id]
+ end
+
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
+ end
+
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ fixtures = cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ fixtures = cache_for_connection(connection).values
+ end
+ fixtures.size > 1 ? fixtures : fixtures.first
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
+
+ def self.instantiate_fixtures(object, table_name, fixtures, load_instances = true)
+ object.instance_variable_set "@#{table_name.to_s.gsub('.','_')}", fixtures
+ if load_instances
+ ActiveRecord::Base.silence do
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
+ end
+ end
+ end
+ end
+
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
+ end
+
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
+
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ connection = block_given? ? yield : ActiveRecord::Base.connection
+
+ table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
+
+ unless table_names_to_fetch.empty?
+ ActiveRecord::Base.silence do
+ connection.disable_referential_integrity do
+ fixtures_map = {}
+
+ fixtures = table_names_to_fetch.map do |table_name|
+ fixtures_map[table_name] = Fixtures.new(connection, File.split(table_name.to_s).last, class_names[table_name.to_sym], File.join(fixtures_directory, table_name.to_s))
+ end
+
+ all_loaded_fixtures.update(fixtures_map)
+
+ connection.transaction(Thread.current['open_transactions'].to_i == 0) do
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
+ fixtures.each { |fixture| fixture.insert_fixtures }
+
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name)
+ end
+ end
+ end
+
+ cache_fixtures(connection, fixtures_map)
+ end
+ end
+ end
+ cached_fixtures(connection, table_names)
+ end
+
+ # Returns a consistent identifier for +label+. This will always
+ # be a positive integer, and will always be the same for a given
+ # label, assuming the same OS, platform, and version of Ruby.
+ def self.identify(label)
+ label.to_s.hash.abs
+ end
+
+ attr_reader :table_name
+
+ def initialize(connection, table_name, class_name, fixture_path, file_filter = DEFAULT_FILTER_RE)
+ @connection, @table_name, @fixture_path, @file_filter = connection, table_name, fixture_path, file_filter
+ @class_name = class_name ||
+ (ActiveRecord::Base.pluralize_table_names ? @table_name.singularize.camelize : @table_name.camelize)
+ @table_name = ActiveRecord::Base.table_name_prefix + @table_name + ActiveRecord::Base.table_name_suffix
+ @table_name = class_name.table_name if class_name.respond_to?(:table_name)
+ @connection = class_name.connection if class_name.respond_to?(:connection)
+ read_fixture_files
+ end
+
+ def delete_existing_fixtures
+ @connection.delete "DELETE FROM #{@connection.quote_table_name(table_name)}", 'Fixture Delete'
+ end
+
+ def insert_fixtures
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
+
+ # allow a standard key to be used for doing defaults in YAML
+ if is_a?(Hash)
+ delete('DEFAULTS')
+ else
+ delete(assoc('DEFAULTS'))
+ end
+
+ # track any join tables we need to insert later
+ habtm_fixtures = Hash.new do |h, habtm|
+ h[habtm] = HabtmFixtures.new(@connection, habtm.options[:join_table], nil, nil)
+ end
+
+ each do |label, fixture|
+ row = fixture.to_hash
+
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
+ end
+
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
+
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = Fixtures.identify(label)
+ end
+
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
+ end
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ 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
+ end
+
+ row[fk_name] = Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ join_fixtures = habtm_fixtures[association]
+
+ targets.each do |target|
+ join_fixtures["#{label}_#{target}"] = Fixture.new(
+ { association.primary_key_name => row[primary_key_name],
+ association.association_foreign_key => Fixtures.identify(target) }, nil)
+ end
+ end
+ end
+ end
+ end
+
+ @connection.insert_fixture(fixture, @table_name)
+ end
+
+ # insert any HABTM join tables we discovered
+ habtm_fixtures.values.each do |fixture|
+ fixture.delete_existing_fixtures
+ fixture.insert_fixtures
+ end
+ end
+
+ private
+ class HabtmFixtures < ::Fixtures #:nodoc:
+ def read_fixture_files; end
+ end
+
+ def model_class
+ unless defined?(@model_class)
+ @model_class =
+ if @class_name.nil? || @class_name.is_a?(Class)
+ @class_name
+ else
+ @class_name.constantize rescue nil
+ end
+ end
+
+ @model_class
+ end
+
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
+
+ def has_primary_key_column?
+ @has_primary_key_column ||= model_class && primary_key_name &&
+ model_class.columns.find { |c| c.name == primary_key_name }
+ end
+
+ def timestamp_column_names
+ @timestamp_column_names ||= %w(created_at created_on updated_at updated_on).select do |name|
+ column_names.include?(name)
+ end
+ end
+
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
+
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect(&:name)
+ end
+
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ end
+ end
+
+ def read_yaml_fixture_files
+ yaml_string = ""
+ Dir["#{@fixture_path}/**/*.yml"].select { |f| test(?f, f) }.each do |subfixture_path|
+ yaml_string << IO.read(subfixture_path)
+ end
+ yaml_string << IO.read(yaml_file_path)
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
+ end
+
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ self[name] = Fixture.new(data, model_class)
+ end
+ end
+ end
+ end
+
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ end
+ end
+
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
+
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
+
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
+
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
+ end
+
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+end
+
+class Fixture #:nodoc:
+ include Enumerable
+
+ class FixtureError < StandardError #:nodoc:
+ end
+
+ class FormatError < FixtureError #:nodoc:
+ end
+
+ attr_reader :model_class
+
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class.is_a?(Class) ? model_class : model_class.constantize rescue nil
+ end
+
+ def class_name
+ @model_class.name if @model_class
+ end
+
+ def each
+ @fixture.each { |item| yield item }
+ end
+
+ def [](key)
+ @fixture[key]
+ end
+
+ def to_hash
+ @fixture
+ end
+
+ def key_list
+ columns = @fixture.keys.collect{ |column_name| ActiveRecord::Base.connection.quote_column_name(column_name) }
+ columns.join(", ")
+ end
+
+ def value_list
+ list = @fixture.inject([]) do |fixtures, (key, value)|
+ col = model_class.columns_hash[key] if model_class.respond_to?(:ancestors) && model_class.ancestors.include?(ActiveRecord::Base)
+ fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
+ end
+ list * ', '
+ end
+
+ def find
+ if model_class
+ model_class.find(self[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
+ end
+end
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ class TestCase #:nodoc:
+ setup :setup_fixtures
+ teardown :teardown_fixtures
+
+ superclass_delegating_accessor :fixture_path
+ superclass_delegating_accessor :fixture_table_names
+ superclass_delegating_accessor :fixture_class_names
+ superclass_delegating_accessor :use_transactional_fixtures
+ superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
+ superclass_delegating_accessor :pre_loaded_fixtures
+
+ self.fixture_table_names = []
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = true
+ self.pre_loaded_fixtures = false
+
+ @@already_loaded_fixtures = {}
+ self.fixture_class_names = {}
+
+ class << self
+ def set_fixture_class(class_names = {})
+ self.fixture_class_names = self.fixture_class_names.merge(class_names)
+ end
+
+ def fixtures(*table_names)
+ if table_names.first == :all
+ table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
+ table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
+ else
+ table_names = table_names.flatten.map { |n| n.to_s }
+ end
+
+ self.fixture_table_names |= table_names
+ require_fixture_classes(table_names)
+ setup_fixture_accessors(table_names)
+ end
+
+ def try_to_load_dependency(file_name)
+ require_dependency file_name
+ rescue LoadError => e
+ # Let's hope the developer has included it himself
+
+ # Let's warn in case this is a subdependency, otherwise
+ # subdependency error messages are totally cryptic
+ if ActiveRecord::Base.logger
+ ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
+ end
+ end
+
+ def require_fixture_classes(table_names = nil)
+ (table_names || fixture_table_names).each do |table_name|
+ file_name = table_name.to_s
+ file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
+ try_to_load_dependency(file_name)
+ end
+ end
+
+ def setup_fixture_accessors(table_names = nil)
+ table_names = [table_names] if table_names && !table_names.respond_to?(:each)
+ (table_names || fixture_table_names).each do |table_name|
+ table_name = table_name.to_s.tr('.', '_')
+
+ define_method(table_name) do |*fixtures|
+ force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
+
+ @fixture_cache[table_name] ||= {}
+
+ instances = fixtures.map do |fixture|
+ @fixture_cache[table_name].delete(fixture) if force_reload
+
+ if @loaded_fixtures[table_name][fixture.to_s]
+ @fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
+ else
+ raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
+ end
+ end
+
+ instances.size == 1 ? instances.first : instances
+ end
+ end
+ end
+
+ def uses_transaction(*methods)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.concat methods.map(&:to_s)
+ end
+
+ def uses_transaction?(method)
+ @uses_transaction = [] unless defined?(@uses_transaction)
+ @uses_transaction.include?(method.to_s)
+ end
+ end
+
+ def use_transactional_fixtures?
+ use_transactional_fixtures &&
+ !self.class.uses_transaction?(method_name)
+ end
+
+ def setup_fixtures
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
+
+ if pre_loaded_fixtures && !use_transactional_fixtures
+ raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
+ end
+
+ @fixture_cache = {}
+
+ # Load fixtures once and begin transaction.
+ if use_transactional_fixtures?
+ if @@already_loaded_fixtures[self.class]
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
+ else
+ load_fixtures
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
+ end
+ ActiveRecord::Base.send :increment_open_transactions
+ ActiveRecord::Base.connection.begin_db_transaction
+ # Load fixtures for every test.
+ else
+ Fixtures.reset_cache
+ @@already_loaded_fixtures[self.class] = nil
+ load_fixtures
+ end
+
+ # Instantiate fixtures for every test if requested.
+ instantiate_fixtures if use_instantiated_fixtures
+ end
+
+ def teardown_fixtures
+ return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
+
+ unless use_transactional_fixtures?
+ Fixtures.reset_cache
+ end
+
+ # Rollback changes if a transaction is active.
+ if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
+ ActiveRecord::Base.connection.rollback_db_transaction
+ Thread.current['open_transactions'] = 0
+ end
+ ActiveRecord::Base.verify_active_connections!
+ end
+
+ private
+ def load_fixtures
+ @loaded_fixtures = {}
+ fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ unless fixtures.nil?
+ if fixtures.instance_of?(Fixtures)
+ @loaded_fixtures[fixtures.table_name] = fixtures
+ else
+ fixtures.each { |f| @loaded_fixtures[f.table_name] = f }
+ end
+ end
+ end
+
+ # for pre_loaded_fixtures, only require the classes once. huge speed improvement
+ @@required_fixture_classes = false
+
+ def instantiate_fixtures
+ if pre_loaded_fixtures
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ unless @@required_fixture_classes
+ self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ @@required_fixture_classes = true
+ end
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ else
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
+ @loaded_fixtures.each do |table_name, fixtures|
+ Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
+ end
+ end
+ end
+
+ def load_instances?
+ use_instantiated_fixtures != :no_instances
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb
new file mode 100644
index 0000000..c66034d
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/locking/optimistic.rb
@@ -0,0 +1,147 @@
+module ActiveRecord
+ module Locking
+ # == What is Optimistic Locking
+ #
+ # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
+ # conflicts with the data. It does this by checking whether another process has made changes to a record since
+ # it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
+ #
+ # Check out ActiveRecord::Locking::Pessimistic for an alternative.
+ #
+ # == Usage
+ #
+ # Active Records support optimistic locking if the field lock_version is present. Each update to the
+ # record increments the lock_version column and the locking facilities ensure that records instantiated twice
+ # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
+ #
+ # p1 = Person.find(1)
+ # p2 = Person.find(1)
+ #
+ # p1.first_name = "Michael"
+ # p1.save
+ #
+ # p2.first_name = "should fail"
+ # p2.save # Raises a ActiveRecord::StaleObjectError
+ #
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
+ # or otherwise apply the business logic needed to resolve the conflict.
+ #
+ # You must ensure that your database schema defaults the lock_version column to 0.
+ #
+ # This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false.
+ # To override the name of the lock_version column, invoke the set_locking_column method.
+ # This method uses the same syntax as set_table_name
+ module Optimistic
+ def self.included(base) #:nodoc:
+ base.extend ClassMethods
+
+ base.cattr_accessor :lock_optimistically, :instance_writer => false
+ base.lock_optimistically = true
+
+ base.alias_method_chain :update, :lock
+ base.alias_method_chain :attributes_from_column_definition, :lock
+
+ class << base
+ alias_method :locking_column=, :set_locking_column
+ end
+ end
+
+ def locking_enabled? #:nodoc:
+ self.class.locking_enabled?
+ end
+
+ private
+ def attributes_from_column_definition_with_lock
+ result = attributes_from_column_definition_without_lock
+
+ # If the locking column has no default value set,
+ # start the lock version at zero. Note we can't use
+ # locking_enabled? at this point as @attributes may
+ # not have been initialized yet
+
+ if lock_optimistically && result.include?(self.class.locking_column)
+ result[self.class.locking_column] ||= 0
+ end
+
+ return result
+ end
+
+ def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
+ return update_without_lock(attribute_names) unless locking_enabled?
+
+ lock_col = self.class.locking_column
+ previous_value = send(lock_col).to_i
+ send(lock_col + '=', previous_value + 1)
+
+ attribute_names += [lock_col]
+ attribute_names.uniq!
+
+ begin
+ affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
+ UPDATE #{self.class.quoted_table_name}
+ SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))}
+ WHERE #{self.class.primary_key} = #{quote_value(id)}
+ AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
+ end_sql
+
+ unless affected_rows == 1
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
+ end
+
+ affected_rows
+
+ # If something went wrong, revert the version.
+ rescue Exception
+ send(lock_col + '=', previous_value)
+ raise
+ end
+ end
+
+ module ClassMethods
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
+
+ def self.extended(base)
+ class < 1) if locking_enabled?
+ update_counters_without_lock(id, counters)
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb b/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb
new file mode 100644
index 0000000..3206595
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/locking/pessimistic.rb
@@ -0,0 +1,77 @@
+# Copyright (c) 2006 Shugo Maeda
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject
+# to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+module ActiveRecord
+ module Locking
+ # Locking::Pessimistic provides support for row-level locking using
+ # SELECT ... FOR UPDATE and other lock types.
+ #
+ # Pass :lock => true to ActiveRecord::Base.find to obtain an exclusive
+ # lock on the selected rows:
+ # # select * from accounts where id=1 for update
+ # Account.find(1, :lock => true)
+ #
+ # Pass :lock => 'some locking clause' to give a database-specific locking clause
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
+ #
+ # Example:
+ # Account.transaction do
+ # # select * from accounts where name = 'shugo' limit 1 for update
+ # shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
+ # yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
+ # shugo.balance -= 100
+ # shugo.save!
+ # yuko.balance += 100
+ # yuko.save!
+ # end
+ #
+ # You can also use ActiveRecord::Base#lock! method to lock one record by id.
+ # This may be better if you don't need to lock every row. Example:
+ # Account.transaction do
+ # # select * from accounts where ...
+ # accounts = Account.find(:all, :conditions => ...)
+ # account1 = accounts.detect { |account| ... }
+ # account2 = accounts.detect { |account| ... }
+ # # select * from accounts where id=? for update
+ # account1.lock!
+ # account2.lock!
+ # account1.balance -= 100
+ # account1.save!
+ # account2.balance += 100
+ # account2.save!
+ # end
+ #
+ # Database-specific information on row locking:
+ # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
+ # PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
+ module Pessimistic
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
+ # the locked record.
+ def lock!(lock = true)
+ reload(:lock => lock) unless new_record?
+ self
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/migration.rb b/vendor/rails/activerecord/lib/active_record/migration.rb
new file mode 100644
index 0000000..b47b01e
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/migration.rb
@@ -0,0 +1,496 @@
+module ActiveRecord
+ class IrreversibleMigration < ActiveRecordError#:nodoc:
+ end
+
+ class DuplicateMigrationVersionError < ActiveRecordError#:nodoc:
+ def initialize(version)
+ super("Multiple migrations have the version number #{version}")
+ end
+ end
+
+ class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
+ def initialize(name)
+ super("Multiple migrations have the name #{name}")
+ end
+ end
+
+ class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
+ def initialize(version)
+ super("No migration with version number #{version}")
+ end
+ end
+
+ class IllegalMigrationNameError < ActiveRecordError#:nodoc:
+ def initialize(name)
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
+ end
+ end
+
+ # Migrations can manage the evolution of a schema used by several physical databases. It's a solution
+ # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to
+ # push that change to other developers and to the production server. With migrations, you can describe the transformations
+ # in self-contained classes that can be checked into version control systems and executed against another database that
+ # might be one, two, or five versions behind.
+ #
+ # Example of a simple migration:
+ #
+ # class AddSsl < ActiveRecord::Migration
+ # def self.up
+ # add_column :accounts, :ssl_enabled, :boolean, :default => 1
+ # end
+ #
+ # def self.down
+ # remove_column :accounts, :ssl_enabled
+ # end
+ # end
+ #
+ # This migration will add a boolean flag to the accounts table and remove it if you're backing out of the migration.
+ # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement
+ # or remove the migration. These methods can consist of both the migration specific methods like add_column and remove_column,
+ # but may also contain regular Ruby code for generating data needed for the transformations.
+ #
+ # Example of a more complex migration that also needs to initialize data:
+ #
+ # class AddSystemSettings < ActiveRecord::Migration
+ # def self.up
+ # create_table :system_settings do |t|
+ # t.string :name
+ # t.string :label
+ # t.text :value
+ # t.string :type
+ # t.integer :position
+ # end
+ #
+ # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
+ # end
+ #
+ # def self.down
+ # drop_table :system_settings
+ # end
+ # end
+ #
+ # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model
+ # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema
+ # in one block call.
+ #
+ # == Available transformations
+ #
+ # * create_table(name, options) Creates a table called +name+ and makes the table object available to a block
+ # that can then add columns to it, following the same format as add_column. See example above. The options hash is for
+ # fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
+ # * drop_table(name): Drops the table called +name+.
+ # * rename_table(old_name, new_name): Renames the table called +old_name+ to +new_name+.
+ # * add_column(table_name, column_name, type, options): Adds a new column to the table called +table_name+
+ # named +column_name+ specified to be one of the following types:
+ # :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
+ # :date, :binary, :boolean. A default value can be specified by passing an
+ # +options+ hash like { :default => 11 }. Other options include :limit and :null (e.g. { :limit => 50, :null => false })
+ # -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
+ # * rename_column(table_name, column_name, new_column_name): Renames a column but keeps the type and content.
+ # * change_column(table_name, column_name, type, options): Changes the column to a different type using the same
+ # parameters as add_column.
+ # * remove_column(table_name, column_name): Removes the column named +column_name+ from the table called +table_name+.
+ # * add_index(table_name, column_names, options): Adds a new index with the name of the column. Other options include
+ # :name and :unique (e.g. { :name => "users_name_index", :unique => true }).
+ # * remove_index(table_name, index_name): Removes the index specified by +index_name+.
+ #
+ # == Irreversible transformations
+ #
+ # Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
+ # an ActiveRecord::IrreversibleMigration exception in their +down+ method.
+ #
+ # == Running migrations from within Rails
+ #
+ # The Rails package has several tools to help create and apply migrations.
+ #
+ # To generate a new migration, you can use
+ # script/generate migration MyNewMigration
+ #
+ # where MyNewMigration is the name of your migration. The generator will
+ # create an empty migration file nnn_my_new_migration.rb in the db/migrate/
+ # directory where nnn is the next largest migration number.
+ #
+ # You may then edit the self.up and self.down methods of
+ # MyNewMigration.
+ #
+ # There is a special syntactic shortcut to generate migrations that add fields to a table.
+ # script/generate migration add_fieldname_to_tablename fieldname:string
+ #
+ # This will generate the file nnn_add_fieldname_to_tablename, which will look like this:
+ # class AddFieldnameToTablename < ActiveRecord::Migration
+ # def self.up
+ # add_column :tablenames, :fieldname, :string
+ # end
+ #
+ # def self.down
+ # remove_column :tablenames, :fieldname
+ # end
+ # end
+ #
+ # To run migrations against the currently configured database, use
+ # rake db:migrate. This will update the database by running all of the
+ # pending migrations, creating the schema_migrations table
+ # (see "About the schema_migrations table" section below) if missing.
+ #
+ # To roll the database back to a previous migration version, use
+ # rake db:migrate VERSION=X where X is the version to which
+ # you wish to downgrade. If any of the migrations throw an
+ # ActiveRecord::IrreversibleMigration exception, that step will fail and you'll
+ # have some manual work to do.
+ #
+ # == Database support
+ #
+ # Migrations are currently supported in MySQL, PostgreSQL, SQLite,
+ # SQL Server, Sybase, and Oracle (all supported databases except DB2).
+ #
+ # == More examples
+ #
+ # Not all migrations change the schema. Some just fix the data:
+ #
+ # class RemoveEmptyTags < ActiveRecord::Migration
+ # def self.up
+ # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? }
+ # end
+ #
+ # def self.down
+ # # not much we can do to restore deleted data
+ # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags"
+ # end
+ # end
+ #
+ # Others remove columns when they migrate up instead of down:
+ #
+ # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration
+ # def self.up
+ # remove_column :items, :incomplete_items_count
+ # remove_column :items, :completed_items_count
+ # end
+ #
+ # def self.down
+ # add_column :items, :incomplete_items_count
+ # add_column :items, :completed_items_count
+ # end
+ # end
+ #
+ # And sometimes you need to do something in SQL not abstracted directly by migrations:
+ #
+ # class MakeJoinUnique < ActiveRecord::Migration
+ # def self.up
+ # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)"
+ # end
+ #
+ # def self.down
+ # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`"
+ # end
+ # end
+ #
+ # == Using a model after changing its table
+ #
+ # Sometimes you'll want to add a column in a migration and populate it immediately after. In that case, you'll need
+ # to make a call to Base#reset_column_information in order to ensure that the model has the latest column data from
+ # after the new column was added. Example:
+ #
+ # class AddPeopleSalary < ActiveRecord::Migration
+ # def self.up
+ # add_column :people, :salary, :integer
+ # Person.reset_column_information
+ # Person.find(:all).each do |p|
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # end
+ # end
+ # end
+ #
+ # == Controlling verbosity
+ #
+ # By default, migrations will describe the actions they are taking, writing
+ # them to the console as they happen, along with benchmarks describing how
+ # long each step took.
+ #
+ # You can quiet them down by setting ActiveRecord::Migration.verbose = false.
+ #
+ # You can also insert your own messages and benchmarks by using the +say_with_time+
+ # method:
+ #
+ # def self.up
+ # ...
+ # say_with_time "Updating salaries..." do
+ # Person.find(:all).each do |p|
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # end
+ # end
+ # ...
+ # end
+ #
+ # The phrase "Updating salaries..." would then be printed, along with the
+ # benchmark for the block when the block completes.
+ #
+ # == About the schema_migrations table
+ #
+ # Rails versions 2.0 and prior used to create a table called
+ # schema_info when using migrations. This table contained the
+ # version of the schema as of the last applied migration.
+ #
+ # Starting with Rails 2.1, the schema_info table is
+ # (automatically) replaced by the schema_migrations table, which
+ # contains the version numbers of all the migrations applied.
+ #
+ # As a result, it is now possible to add migration files that are numbered
+ # lower than the current schema version: when migrating up, those
+ # never-applied "interleaved" migrations will be automatically applied, and
+ # when migrating down, never-applied "interleaved" migrations will be skipped.
+ class Migration
+ @@verbose = true
+ cattr_accessor :verbose
+
+ class << self
+ def up_with_benchmarks #:nodoc:
+ migrate(:up)
+ end
+
+ def down_with_benchmarks #:nodoc:
+ migrate(:down)
+ end
+
+ # Execute this migration in the named direction
+ def migrate(direction)
+ return unless respond_to?(direction)
+
+ case direction
+ when :up then announce "migrating"
+ when :down then announce "reverting"
+ end
+
+ result = nil
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
+
+ case direction
+ when :up then announce "migrated (%.4fs)" % time.real; write
+ when :down then announce "reverted (%.4fs)" % time.real; write
+ end
+
+ result
+ end
+
+ # Because the method added may do an alias_method, it can be invoked
+ # recursively. We use @ignore_new_methods as a guard to indicate whether
+ # it is safe for the call to proceed.
+ def singleton_method_added(sym) #:nodoc:
+ return if defined?(@ignore_new_methods) && @ignore_new_methods
+
+ begin
+ @ignore_new_methods = true
+
+ case sym
+ when :up, :down
+ klass = (class << self; self; end)
+ klass.send(:alias_method_chain, sym, "benchmarks")
+ end
+ ensure
+ @ignore_new_methods = false
+ end
+ end
+
+ def write(text="")
+ puts(text) if verbose
+ end
+
+ def announce(message)
+ text = "#{@version} #{name}: #{message}"
+ length = [0, 75 - text.length].max
+ write "== %s %s" % [text, "=" * length]
+ end
+
+ def say(message, subitem=false)
+ write "#{subitem ? " ->" : "--"} #{message}"
+ end
+
+ def say_with_time(message)
+ say(message)
+ result = nil
+ time = Benchmark.measure { result = yield }
+ say "%.4fs" % time.real, :subitem
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
+ result
+ end
+
+ def suppress_messages
+ save, self.verbose = verbose, false
+ yield
+ ensure
+ self.verbose = save
+ end
+
+ def method_missing(method, *arguments, &block)
+ arg_list = arguments.map(&:inspect) * ', '
+
+ say_with_time "#{method}(#{arg_list})" do
+ unless arguments.empty? || method == :execute
+ arguments[0] = Migrator.proper_table_name(arguments.first)
+ end
+ ActiveRecord::Base.connection.send(method, *arguments, &block)
+ end
+ end
+ end
+ end
+
+ class Migrator#:nodoc:
+ class << self
+ def migrate(migrations_path, target_version = nil)
+ case
+ when target_version.nil? then up(migrations_path, target_version)
+ when current_version > target_version then down(migrations_path, target_version)
+ else up(migrations_path, target_version)
+ end
+ end
+
+ def rollback(migrations_path, steps=1)
+ migrator = self.new(:down, migrations_path)
+ start_index = migrator.migrations.index(migrator.current_migration)
+
+ return unless start_index
+
+ finish = migrator.migrations[start_index + steps]
+ down(migrations_path, finish ? finish.version : 0)
+ end
+
+ def up(migrations_path, target_version = nil)
+ self.new(:up, migrations_path, target_version).migrate
+ end
+
+ def down(migrations_path, target_version = nil)
+ self.new(:down, migrations_path, target_version).migrate
+ end
+
+ def run(direction, migrations_path, target_version)
+ self.new(direction, migrations_path, target_version).run
+ end
+
+ def schema_migrations_table_name
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
+ end
+
+ def current_version
+ version = Base.connection.select_values(
+ "SELECT version FROM #{schema_migrations_table_name}"
+ ).map(&:to_i).max rescue nil
+ version || 0
+ end
+
+ def proper_table_name(name)
+ # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
+ name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
+ end
+ end
+
+ def initialize(direction, migrations_path, target_version = nil)
+ raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
+ Base.connection.initialize_schema_migrations_table
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
+ end
+
+ def current_version
+ self.class.current_version
+ end
+
+ def current_migration
+ migrations.detect { |m| m.version == current_version }
+ end
+
+ def run
+ target = migrations.detect { |m| m.version == @target_version }
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
+ target.migrate(@direction)
+ end
+
+ def migrate
+ current = migrations.detect { |m| m.version == current_version }
+ target = migrations.detect { |m| m.version == @target_version }
+
+ if target.nil? && !@target_version.nil? && @target_version > 0
+ raise UnknownMigrationVersionError.new(@target_version)
+ end
+
+ start = up? ? 0 : (migrations.index(current) || 0)
+ finish = migrations.index(target) || migrations.size - 1
+ runnable = migrations[start..finish]
+
+ # skip the last migration if we're headed down, but not ALL the way down
+ runnable.pop if down? && !target.nil?
+
+ runnable.each do |migration|
+ Base.logger.info "Migrating to #{migration} (#{migration.version})"
+
+ # On our way up, we skip migrating the ones we've already migrated
+ # On our way down, we skip reverting the ones we've never migrated
+ next if up? && migrated.include?(migration.version.to_i)
+
+ if down? && !migrated.include?(migration.version.to_i)
+ migration.announce 'never migrated, skipping'; migration.write
+ else
+ migration.migrate(@direction)
+ record_version_state_after_migrating(migration.version)
+ end
+ end
+ end
+
+ def migrations
+ @migrations ||= begin
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
+
+ migrations = files.inject([]) do |klasses, file|
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
+
+ raise IllegalMigrationNameError.new(file) unless version
+ version = version.to_i
+
+ if klasses.detect { |m| m.version == version }
+ raise DuplicateMigrationVersionError.new(version)
+ end
+
+ if klasses.detect { |m| m.name == name.camelize }
+ raise DuplicateMigrationNameError.new(name.camelize)
+ end
+
+ load(file)
+
+ klasses << returning(name.camelize.constantize) do |klass|
+ class << klass; attr_accessor :version end
+ klass.version = version
+ end
+ end
+
+ migrations = migrations.sort_by(&:version)
+ down? ? migrations.reverse : migrations
+ end
+ end
+
+ def pending_migrations
+ already_migrated = migrated
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
+ end
+
+ def migrated
+ sm_table = self.class.schema_migrations_table_name
+ Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
+ end
+
+ private
+ def record_version_state_after_migrating(version)
+ sm_table = self.class.schema_migrations_table_name
+
+ if down?
+ Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
+ else
+ Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
+ end
+ end
+
+ def up?
+ @direction == :up
+ end
+
+ def down?
+ @direction == :down
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/named_scope.rb b/vendor/rails/activerecord/lib/active_record/named_scope.rb
new file mode 100644
index 0000000..b0c8a8b
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/named_scope.rb
@@ -0,0 +1,163 @@
+module ActiveRecord
+ module NamedScope
+ # All subclasses of ActiveRecord::Base have two named_scopes:
+ # * all, which is similar to a find(:all) query, and
+ # * scoped, which allows for the creation of anonymous scopes, on the fly: Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
+ #
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
+ # intermediate values (scopes) around as first-class objects is convenient.
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ named_scope :scoped, lambda { |scope| scope }
+ end
+ end
+
+ module ClassMethods
+ def scopes
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
+ end
+
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
+ # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'}
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
+ # end
+ #
+ # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
+ # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}).
+ #
+ # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object
+ # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count,
+ # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block),
+ # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array.
+ #
+ # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments
+ # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count).
+ #
+ # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
+ # has_many associations. If,
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :shirts
+ # end
+ #
+ # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean
+ # only shirts.
+ #
+ # Named scopes can also be procedural.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # In this example, Shirt.colored('puce') finds all puce shirts.
+ #
+ # Named scopes can also have extensions, just as with has_many declarations:
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'} do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ #
+ #
+ # For testing complex named scopes, you can examine the scoping options using the
+ # proxy_options method on the proxy itself.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # expected_options = { :conditions => { :colored => 'red' } }
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
+ def named_scope(name, options = {}, &block)
+ scopes[name] = lambda do |parent_scope, *args|
+ Scope.new(parent_scope, case options
+ when Hash
+ options
+ when Proc
+ options.call(*args)
+ end, &block)
+ end
+ (class << self; self end).instance_eval do
+ define_method name do |*args|
+ scopes[name].call(self, *args)
+ end
+ end
+ end
+ end
+
+ class Scope
+ attr_reader :proxy_scope, :proxy_options
+
+ [].methods.each do |m|
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
+ delegate m, :to => :proxy_found
+ end
+ end
+
+ delegate :scopes, :with_scope, :to => :proxy_scope
+
+ def initialize(proxy_scope, options, &block)
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
+ extend Module.new(&block) if block_given?
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
+ end
+
+ def reload
+ load_found; self
+ end
+
+ def first(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.first(*args)
+ else
+ find(:first, *args)
+ end
+ end
+
+ def last(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.last(*args)
+ else
+ find(:last, *args)
+ end
+ end
+
+ def empty?
+ @found ? @found.empty? : count.zero?
+ end
+
+ protected
+ def proxy_found
+ @found || load_found
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ if scopes.include?(method)
+ scopes[method].call(self, *args)
+ else
+ with_scope :find => proxy_options do
+ proxy_scope.send(method, *args, &block)
+ end
+ end
+ end
+
+ def load_found
+ @found = find(:all)
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/observer.rb b/vendor/rails/activerecord/lib/active_record/observer.rb
new file mode 100644
index 0000000..6e55e36
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/observer.rb
@@ -0,0 +1,195 @@
+require 'singleton'
+require 'set'
+
+module ActiveRecord
+ module Observing # :nodoc:
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # Activates the observers assigned. Examples:
+ #
+ # # Calls PersonObserver.instance
+ # ActiveRecord::Base.observers = :person_observer
+ #
+ # # Calls Cacher.instance and GarbageCollector.instance
+ # ActiveRecord::Base.observers = :cacher, :garbage_collector
+ #
+ # # Same as above, just using explicit class references
+ # ActiveRecord::Base.observers = Cacher, GarbageCollector
+ #
+ # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
+ # called during startup, and before each development request.
+ def observers=(*observers)
+ @observers = observers.flatten
+ end
+
+ # Gets the current observers.
+ def observers
+ @observers ||= []
+ end
+
+ # Instantiate the global Active Record observers.
+ def instantiate_observers
+ return if @observers.blank?
+ @observers.each do |observer|
+ if observer.respond_to?(:to_sym) # Symbol or String
+ observer.to_s.camelize.constantize.instance
+ elsif observer.respond_to?(:instance)
+ observer.instance
+ else
+ raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
+ end
+ end
+ end
+
+ protected
+ # Notify observers when the observed class is subclassed.
+ def inherited(subclass)
+ super
+ changed
+ notify_observers :observed_class_inherited, subclass
+ end
+ end
+ end
+
+ # Observer classes respond to lifecycle callbacks to implement trigger-like
+ # behavior outside the original class. This is a great way to reduce the
+ # clutter that normally comes when the model class is burdened with
+ # functionality that doesn't pertain to the core responsibility of the
+ # class. Example:
+ #
+ # class CommentObserver < ActiveRecord::Observer
+ # def after_save(comment)
+ # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
+ # end
+ # end
+ #
+ # This Observer sends an email when a Comment#save is finished.
+ #
+ # class ContactObserver < ActiveRecord::Observer
+ # def after_create(contact)
+ # contact.logger.info('New contact added!')
+ # end
+ #
+ # def after_destroy(contact)
+ # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
+ # end
+ # end
+ #
+ # This Observer uses logger to log when specific callbacks are triggered.
+ #
+ # == Observing a class that can't be inferred
+ #
+ # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
+ # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
+ # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
+ # either the concrete class (Product) or a symbol for that class (:product):
+ #
+ # class AuditObserver < ActiveRecord::Observer
+ # observe :account
+ #
+ # def after_update(account)
+ # AuditTrail.new(account, "UPDATED")
+ # end
+ # end
+ #
+ # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
+ #
+ # class AuditObserver < ActiveRecord::Observer
+ # observe :account, :balance
+ #
+ # def after_update(record)
+ # AuditTrail.new(record, "UPDATED")
+ # end
+ # end
+ #
+ # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
+ #
+ # == Available callback methods
+ #
+ # The observer can implement callback methods for each of the methods described in the Callbacks module.
+ #
+ # == Storing Observers in Rails
+ #
+ # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
+ # naming convention of app/models/audit_observer.rb.
+ #
+ # == Configuration
+ #
+ # In order to activate an observer, list it in the config.active_record.observers configuration setting in your
+ # config/environment.rb file.
+ #
+ # config.active_record.observers = :comment_observer, :signup_observer
+ #
+ # Observers will not be invoked unless you define these in your application configuration.
+ #
+ # == Loading
+ #
+ # Observers register themselves in the model class they observe, since it is the class that
+ # notifies them of events when they occur. As a side-effect, when an observer is loaded its
+ # corresponding model class is loaded.
+ #
+ # Up to (and including) Rails 2.0.2 observers were instantiated between plugins and
+ # application initializers. Now observers are loaded after application initializers,
+ # so observed models can make use of extensions.
+ #
+ # If by any chance you are using observed models in the initialization you can still
+ # load their observers by calling ModelObserver.instance before. Observers are
+ # singletons and that call instantiates and registers them.
+ #
+ class Observer
+ include Singleton
+
+ class << self
+ # Attaches the observer to the supplied model classes.
+ def observe(*models)
+ models.flatten!
+ models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
+ define_method(:observed_classes) { Set.new(models) }
+ end
+
+ # The class observed by default is inferred from the observer's class name:
+ # assert_equal Person, PersonObserver.observed_class
+ def observed_class
+ if observed_class_name = name[/(.*)Observer/, 1]
+ observed_class_name.constantize
+ else
+ nil
+ end
+ end
+ end
+
+ # Start observing the declared classes and their subclasses.
+ def initialize
+ Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
+ end
+
+ # Send observed_method(object) if the method exists.
+ def update(observed_method, object) #:nodoc:
+ send(observed_method, object) if respond_to?(observed_method)
+ end
+
+ # Special method sent by the observed class when it is inherited.
+ # Passes the new subclass.
+ def observed_class_inherited(subclass) #:nodoc:
+ self.class.observe(observed_classes + [subclass])
+ add_observer!(subclass)
+ end
+
+ protected
+ def observed_classes
+ Set.new([self.class.observed_class].compact.flatten)
+ end
+
+ def observed_subclasses
+ observed_classes.sum([]) { |klass| klass.send(:subclasses) }
+ end
+
+ def add_observer!(klass)
+ klass.add_observer(self)
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/query_cache.rb b/vendor/rails/activerecord/lib/active_record/query_cache.rb
new file mode 100644
index 0000000..a8af89f
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/query_cache.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ module QueryCache
+ # Enable the query cache within the block if Active Record is configured.
+ def cache(&block)
+ if ActiveRecord::Base.configurations.blank?
+ yield
+ else
+ connection.cache(&block)
+ end
+ end
+
+ # Disable the query cache within the block if Active Record is configured.
+ def uncached(&block)
+ if ActiveRecord::Base.configurations.blank?
+ yield
+ else
+ connection.uncached(&block)
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/reflection.rb b/vendor/rails/activerecord/lib/active_record/reflection.rb
new file mode 100644
index 0000000..8614ef8
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/reflection.rb
@@ -0,0 +1,239 @@
+module ActiveRecord
+ module Reflection # :nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ # Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
+ # This information can, for example, be used in a form builder that took an Active Record object and created input
+ # fields for all of the attributes depending on their type and displayed the associations to other objects.
+ #
+ # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
+ module ClassMethods
+ def create_reflection(macro, name, options, active_record)
+ case macro
+ when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
+ reflection = AssociationReflection.new(macro, name, options, active_record)
+ when :composed_of
+ reflection = AggregateReflection.new(macro, name, options, active_record)
+ end
+ write_inheritable_hash :reflections, name => reflection
+ reflection
+ end
+
+ # Returns a hash containing all AssociationReflection objects for the current class
+ # Example:
+ #
+ # Invoice.reflections
+ # Account.reflections
+ #
+ def reflections
+ read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
+ end
+
+ # Returns an array of AggregateReflection objects for all the aggregations in the class.
+ def reflect_on_all_aggregations
+ reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
+ end
+
+ # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
+ #
+ # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
+ #
+ def reflect_on_aggregation(aggregation)
+ reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
+ end
+
+ # Returns an array of AssociationReflection objects for all the associations in the class. If you only want to reflect on a
+ # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter.
+ # Example:
+ #
+ # Account.reflect_on_all_associations # returns an array of all associations
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
+ #
+ def reflect_on_all_associations(macro = nil)
+ association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
+ macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
+ end
+
+ # Returns the AssociationReflection object for the named +association+ (use the symbol). Example:
+ #
+ # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
+ # Invoice.reflect_on_association(:line_items).macro # returns :has_many
+ #
+ def reflect_on_association(association)
+ reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
+ end
+ end
+
+
+ # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
+ # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ class MacroReflection
+ attr_reader :active_record
+
+ def initialize(macro, name, options, active_record)
+ @macro, @name, @options, @active_record = macro, name, options, active_record
+ end
+
+ # Returns the name of the macro. For example, composed_of :balance, :class_name => 'Money' will return
+ # :balance or for has_many :clients it will return :clients.
+ def name
+ @name
+ end
+
+ # Returns the macro type. For example, composed_of :balance, :class_name => 'Money' will return :composed_of
+ # or for has_many :clients will return :has_many.
+ def macro
+ @macro
+ end
+
+ # Returns the hash of options used for the macro. For example, it would return { :class_name => "Money" } for
+ # composed_of :balance, :class_name => 'Money' or +{}+ for has_many :clients.
+ def options
+ @options
+ end
+
+ # Returns the class for the macro. For example, composed_of :balance, :class_name => 'Money' returns the Money
+ # class and has_many :clients returns the Client class.
+ def klass
+ @klass ||= class_name.constantize
+ end
+
+ # Returns the class name for the macro. For example, composed_of :balance, :class_name => 'Money' returns 'Money'
+ # and has_many :clients returns 'Client'.
+ def class_name
+ @class_name ||= options[:class_name] || derive_class_name
+ end
+
+ # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
+ # and +other_aggregation+ has an options hash assigned to it.
+ def ==(other_aggregation)
+ name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
+ end
+
+ private
+ def derive_class_name
+ name.to_s.camelize
+ end
+ end
+
+
+ # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
+ class AggregateReflection < MacroReflection #:nodoc:
+ end
+
+ # Holds all the meta-data about an association as it was specified in the Active Record class.
+ class AssociationReflection < MacroReflection #:nodoc:
+ def klass
+ @klass ||= active_record.send(:compute_type, class_name)
+ end
+
+ def table_name
+ @table_name ||= klass.table_name
+ end
+
+ def quoted_table_name
+ @quoted_table_name ||= klass.quoted_table_name
+ end
+
+ def primary_key_name
+ @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
+ end
+
+ def association_foreign_key
+ @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
+ end
+
+ def counter_cache_column
+ if options[:counter_cache] == true
+ "#{active_record.name.underscore.pluralize}_count"
+ elsif options[:counter_cache]
+ options[:counter_cache]
+ end
+ end
+
+ # Returns the AssociationReflection object specified in the :through option
+ # of a HasManyThrough or HasOneThrough association. Example:
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, :through => :taggings
+ # end
+ #
+ # tags_reflection = Post.reflect_on_association(:tags)
+ # taggings_reflection = tags_reflection.through_reflection
+ #
+ def through_reflection
+ @through_reflection ||= options[:through] ? active_record.reflect_on_association(options[:through]) : false
+ end
+
+ # Gets an array of possible :through source reflection names:
+ #
+ # [:singularized, :pluralized]
+ #
+ def source_reflection_names
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
+ end
+
+ # Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
+ # (The :tags association on Tagging below.)
+ #
+ # class Post < ActiveRecord::Base
+ # has_many :taggings
+ # has_many :tags, :through => :taggings
+ # end
+ #
+ def source_reflection
+ return nil unless through_reflection
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
+ end
+
+ def check_validity!
+ if options[:through]
+ if through_reflection.nil?
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
+ end
+
+ if source_reflection.nil?
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
+ end
+
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
+ end
+
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
+ raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
+ end
+
+ unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
+ raise HasManyThroughSourceAssociationMacroError.new(self)
+ end
+ end
+ end
+
+ private
+ def derive_class_name
+ # get the class_name of the belongs_to association of the through reflection
+ if through_reflection
+ options[:source_type] || source_reflection.class_name
+ else
+ class_name = name.to_s.camelize
+ class_name = class_name.singularize if [ :has_many, :has_and_belongs_to_many ].include?(macro)
+ class_name
+ end
+ end
+
+ def derive_primary_key_name
+ if macro == :belongs_to
+ "#{name}_id"
+ elsif options[:as]
+ "#{options[:as]}_id"
+ else
+ active_record.name.foreign_key
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/schema.rb b/vendor/rails/activerecord/lib/active_record/schema.rb
new file mode 100644
index 0000000..8a32cf1
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/schema.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ # Allows programmers to programmatically define a schema in a portable
+ # DSL. This means you can define tables, indexes, etc. without using SQL
+ # directly, so your applications can more easily support multiple
+ # databases.
+ #
+ # Usage:
+ #
+ # ActiveRecord::Schema.define do
+ # create_table :authors do |t|
+ # t.string :name, :null => false
+ # end
+ #
+ # add_index :authors, :name, :unique
+ #
+ # create_table :posts do |t|
+ # t.integer :author_id, :null => false
+ # t.string :subject
+ # t.text :body
+ # t.boolean :private, :default => false
+ # end
+ #
+ # add_index :posts, :author_id
+ # end
+ #
+ # ActiveRecord::Schema is only supported by database adapters that also
+ # support migrations, the two features being very similar.
+ class Schema < Migration
+ private_class_method :new
+
+ # Eval the given block. All methods available to the current connection
+ # adapter are available within the block, so you can easily use the
+ # database definition DSL to build up your schema (+create_table+,
+ # +add_index+, etc.).
+ #
+ # The +info+ hash is optional, and if given is used to define metadata
+ # about the current schema (currently, only the schema's version):
+ #
+ # ActiveRecord::Schema.define(:version => 20380119000001) do
+ # ...
+ # end
+ def self.define(info={}, &block)
+ instance_eval(&block)
+
+ unless info[:version].blank?
+ initialize_schema_migrations_table
+ assume_migrated_upto_version info[:version]
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/schema_dumper.rb b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb
new file mode 100644
index 0000000..b90ed88
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/schema_dumper.rb
@@ -0,0 +1,171 @@
+require 'stringio'
+require 'bigdecimal'
+
+module ActiveRecord
+ # This class is used to dump the database schema for some connection to some
+ # output format (i.e., ActiveRecord::Schema).
+ class SchemaDumper #:nodoc:
+ private_class_method :new
+
+ # A list of tables which should not be dumped to the schema.
+ # Acceptable values are strings as well as regexp.
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
+ cattr_accessor :ignore_tables
+ @@ignore_tables = []
+
+ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
+ new(connection).dump(stream)
+ stream
+ end
+
+ def dump(stream)
+ header(stream)
+ tables(stream)
+ trailer(stream)
+ stream
+ end
+
+ private
+
+ def initialize(connection)
+ @connection = connection
+ @types = @connection.native_database_types
+ @version = Migrator::current_version rescue nil
+ end
+
+ def header(stream)
+ define_params = @version ? ":version => #{@version}" : ""
+
+ stream.puts < "#{pk}")
+ end
+ else
+ tbl.print ", :id => false"
+ end
+ tbl.print ", :force => true"
+ tbl.puts " do |t|"
+
+ column_specs = columns.map do |column|
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
+ next if column.name == pk
+ spec = {}
+ spec[:name] = column.name.inspect
+ spec[:type] = column.type.to_s
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
+ spec[:null] = 'false' if !column.null
+ spec[:default] = default_string(column.default) if !column.default.nil?
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
+ spec
+ end.compact
+
+ # find all migration keys used in this table
+ keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
+
+ # figure out the lengths for each column based on above keys
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
+
+ # the string we're going to sprintf our values against, with standardized column widths
+ format_string = lengths.map{ |len| "%-#{len}s" }
+
+ # find the max length for the 'type' column, which is special
+ type_length = column_specs.map{ |column| column[:type].length }.max
+
+ # add column type definition to our format string
+ format_string.unshift " t.%-#{type_length}s "
+
+ format_string *= ''
+
+ column_specs.each do |colspec|
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
+ values.unshift colspec[:type]
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
+ tbl.puts
+ end
+
+ tbl.puts " end"
+ tbl.puts
+
+ indexes(table, tbl)
+
+ tbl.rewind
+ stream.print tbl.read
+ rescue => e
+ stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
+ stream.puts "# #{e.message}"
+ stream.puts
+ end
+
+ stream
+ end
+
+ def default_string(value)
+ case value
+ when BigDecimal
+ value.to_s
+ when Date, DateTime, Time
+ "'" + value.to_s(:db) + "'"
+ else
+ value.inspect
+ end
+ end
+
+ def indexes(table, stream)
+ indexes = @connection.indexes(table)
+ indexes.each do |index|
+ stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}"
+ stream.print ", :unique => true" if index.unique
+ stream.puts
+ end
+ stream.puts unless indexes.empty?
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/serialization.rb b/vendor/rails/activerecord/lib/active_record/serialization.rb
new file mode 100644
index 0000000..332cda1
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/serialization.rb
@@ -0,0 +1,98 @@
+module ActiveRecord #:nodoc:
+ module Serialization
+ class Serializer #:nodoc:
+ attr_reader :options
+
+ def initialize(record, options = {})
+ @record, @options = record, options.dup
+ end
+
+ # To replicate the behavior in ActiveRecord#attributes,
+ # :except takes precedence over :only. If :only is not set
+ # for a N level model but is set for the N+1 level models,
+ # then because :except is set to a default value, the second
+ # level model can have both :except and :only set. So if
+ # :only is set, always delete :except.
+ def serializable_attribute_names
+ attribute_names = @record.attribute_names
+
+ if options[:only]
+ options.delete(:except)
+ attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
+ else
+ options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
+ attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
+ end
+
+ attribute_names
+ end
+
+ def serializable_method_names
+ Array(options[:methods]).inject([]) do |method_attributes, name|
+ method_attributes << name if @record.respond_to?(name.to_s)
+ method_attributes
+ end
+ end
+
+ def serializable_names
+ serializable_attribute_names + serializable_method_names
+ end
+
+ # Add associations specified via the :includes option.
+ # Expects a block that takes as arguments:
+ # +association+ - name of the association
+ # +records+ - the association record(s) to be serialized
+ # +opts+ - options for the association records
+ def add_includes(&block)
+ if include_associations = options.delete(:include)
+ base_only_or_except = { :except => options[:except],
+ :only => options[:only] }
+
+ include_has_options = include_associations.is_a?(Hash)
+ associations = include_has_options ? include_associations.keys : Array(include_associations)
+
+ for association in associations
+ records = case @record.class.reflect_on_association(association).macro
+ when :has_many, :has_and_belongs_to_many
+ @record.send(association).to_a
+ when :has_one, :belongs_to
+ @record.send(association)
+ end
+
+ unless records.nil?
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
+ opts = options.merge(association_options)
+ yield(association, records, opts)
+ end
+ end
+
+ options[:include] = include_associations
+ end
+ end
+
+ def serializable_record
+ returning(serializable_record = {}) do
+ serializable_names.each { |name| serializable_record[name] = @record.send(name) }
+ add_includes do |association, records, opts|
+ if records.is_a?(Enumerable)
+ serializable_record[association] = records.collect { |r| self.class.new(r, opts).serializable_record }
+ else
+ serializable_record[association] = self.class.new(records, opts).serializable_record
+ end
+ end
+ end
+ end
+
+ def serialize
+ # overwrite to implement
+ end
+
+ def to_s(&block)
+ serialize(&block)
+ end
+ end
+ end
+end
+
+require 'active_record/serializers/xml_serializer'
+require 'active_record/serializers/json_serializer'
\ No newline at end of file
diff --git a/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb b/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb
new file mode 100644
index 0000000..419b45d
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/serializers/json_serializer.rb
@@ -0,0 +1,80 @@
+module ActiveRecord #:nodoc:
+ module Serialization
+ def self.included(base)
+ base.cattr_accessor :include_root_in_json, :instance_writer => false
+ base.extend ClassMethods
+ end
+
+ # Returns a JSON string representing the model. Some configuration is
+ # available through +options+.
+ #
+ # Without any +options+, the returned JSON string will include all
+ # the model's attributes. For example:
+ #
+ # konata = User.find(1)
+ # konata.to_json
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true}
+ #
+ # The :only and :except options can be used to limit the attributes
+ # included, and work similar to the +attributes+ method. For example:
+ #
+ # konata.to_json(:only => [ :id, :name ])
+ # # => {"id": 1, "name": "Konata Izumi"}
+ #
+ # konata.to_json(:except => [ :id, :created_at, :age ])
+ # # => {"name": "Konata Izumi", "awesome": true}
+ #
+ # To include any methods on the model, use :methods.
+ #
+ # konata.to_json(:methods => :permalink)
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "permalink": "1-konata-izumi"}
+ #
+ # To include associations, use :include.
+ #
+ # konata.to_json(:include => :posts)
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
+ #
+ # 2nd level and higher order associations work as well:
+ #
+ # konata.to_json(:include => { :posts => {
+ # :include => { :comments => {
+ # :only => :body } },
+ # :only => :title } })
+ # # => {"id": 1, "name": "Konata Izumi", "age": 16,
+ # "created_at": "2006/08/01", "awesome": true,
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
+ # "title": "Welcome to the weblog"},
+ # {"comments": [{"body": "Don't think too hard"}],
+ # "title": "So I was thinking"}]}
+ def to_json(options = {})
+ if include_root_in_json
+ "{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
+ else
+ JsonSerializer.new(self, options).to_s
+ end
+ end
+
+ def from_json(json)
+ self.attributes = ActiveSupport::JSON.decode(json)
+ self
+ end
+
+ class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
+ def serialize
+ serializable_record.to_json
+ end
+ end
+
+ module ClassMethods
+ def json_class_name
+ @json_class_name ||= name.demodulize.underscore.inspect
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb b/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb
new file mode 100644
index 0000000..d171b74
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -0,0 +1,338 @@
+module ActiveRecord #:nodoc:
+ module Serialization
+ # Builds an XML document to represent the model. Some configuration is
+ # available through +options+. However more complicated cases should
+ # override ActiveRecord::Base#to_xml.
+ #
+ # By default the generated XML document will include the processing
+ # instruction and all the object's attributes. For example:
+ #
+ #
+ #
+ # The First Topic
+ # David
+ # 1
+ # false
+ # 0
+ # 2000-01-01T08:28:00+12:00
+ # 2003-07-16T09:28:00+1200
+ # Have a nice day
+ # david@loudthinking.com
+ #
+ # 2004-04-15
+ #
+ #
+ # This behavior can be controlled with :only, :except,
+ # :skip_instruct, :skip_types and :dasherize.
+ # The :only and :except options are the same as for the
+ # +attributes+ method. The default is to dasherize all column names, but you
+ # can disable this setting :dasherize to +false+. To not have the
+ # column type included in the XML output set :skip_types to +true+.
+ #
+ # For instance:
+ #
+ # topic.to_xml(:skip_instruct => true, :except => [ :id, :bonus_time, :written_on, :replies_count ])
+ #
+ #
+ # The First Topic
+ # David
+ # false
+ # Have a nice day
+ # david@loudthinking.com
+ #
+ # 2004-04-15
+ #
+ #
+ # To include first level associations use :include:
+ #
+ # firm.to_xml :include => [ :account, :clients ]
+ #
+ #
+ #
+ # 1
+ # 1
+ # 37signals
+ #
+ #
+ # 1
+ # Summit
+ #
+ #
+ # 1
+ # Microsoft
+ #
+ #
+ #
+ # 1
+ # 50
+ #
+ #
+ #
+ # To include deeper levels of associations pass a hash like this:
+ #
+ # firm.to_xml :include => {:account => {}, :clients => {:include => :address}}
+ #
+ #
+ # 1
+ # 1
+ # 37signals
+ #
+ #
+ # 1
+ # Summit
+ #
+ # ...
+ #
+ #
+ #
+ # 1
+ # Microsoft
+ #
+ # ...
+ #
+ #
+ #
+ #
+ # 1
+ # 50
+ #
+ #
+ #
+ # To include any methods on the model being called use :methods:
+ #
+ # firm.to_xml :methods => [ :calculated_earnings, :real_earnings ]
+ #
+ #
+ # # ... normal attributes as shown above ...
+ # 100000000000000000
+ # 5
+ #
+ #
+ # To call any additional Procs use :procs. The Procs are passed a
+ # modified version of the options hash that was given to +to_xml+:
+ #
+ # proc = Proc.new { |options| options[:builder].tag!('abc', 'def') }
+ # firm.to_xml :procs => [ proc ]
+ #
+ #
+ # # ... normal attributes as shown above ...
+ # def
+ #
+ #
+ # Alternatively, you can yield the builder object as part of the +to_xml+ call:
+ #
+ # firm.to_xml do |xml|
+ # xml.creator do
+ # xml.first_name "David"
+ # xml.last_name "Heinemeier Hansson"
+ # end
+ # end
+ #
+ #
+ # # ... normal attributes as shown above ...
+ #
+ # David
+ # Heinemeier Hansson
+ #
+ #
+ #
+ # As noted above, you may override +to_xml+ in your ActiveRecord::Base
+ # subclasses to have complete control about what's generated. The general
+ # form of doing this is:
+ #
+ # class IHaveMyOwnXML < ActiveRecord::Base
+ # def to_xml(options = {})
+ # options[:indent] ||= 2
+ # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+ # xml.instruct! unless options[:skip_instruct]
+ # xml.level_one do
+ # xml.tag!(:second_level, 'content')
+ # end
+ # end
+ # end
+ def to_xml(options = {}, &block)
+ serializer = XmlSerializer.new(self, options)
+ block_given? ? serializer.to_s(&block) : serializer.to_s
+ end
+
+ def from_xml(xml)
+ self.attributes = Hash.from_xml(xml).values.first
+ self
+ end
+ end
+
+ class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
+ def builder
+ @builder ||= begin
+ options[:indent] ||= 2
+ builder = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+
+ unless options[:skip_instruct]
+ builder.instruct!
+ options[:skip_instruct] = true
+ end
+
+ builder
+ end
+ end
+
+ def root
+ root = (options[:root] || @record.class.to_s.underscore).to_s
+ dasherize? ? root.dasherize : root
+ end
+
+ def dasherize?
+ !options.has_key?(:dasherize) || options[:dasherize]
+ end
+
+ def serializable_attributes
+ serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
+ end
+
+ def serializable_method_attributes
+ Array(options[:methods]).inject([]) do |method_attributes, name|
+ method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
+ method_attributes
+ end
+ end
+
+ def add_attributes
+ (serializable_attributes + serializable_method_attributes).each do |attribute|
+ add_tag(attribute)
+ end
+ end
+
+ def add_procs
+ if procs = options.delete(:procs)
+ [ *procs ].each do |proc|
+ proc.call(options)
+ end
+ end
+ end
+
+ def add_tag(attribute)
+ builder.tag!(
+ dasherize? ? attribute.name.dasherize : attribute.name,
+ attribute.value.to_s,
+ attribute.decorations(!options[:skip_types])
+ )
+ end
+
+ def add_associations(association, records, opts)
+ if records.is_a?(Enumerable)
+ tag = association.to_s
+ tag = tag.dasherize if dasherize?
+ if records.empty?
+ builder.tag!(tag, :type => :array)
+ else
+ builder.tag!(tag, :type => :array) do
+ association_name = association.to_s.singularize
+ records.each do |record|
+ record.to_xml opts.merge(
+ :root => association_name,
+ :type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
+ )
+ end
+ end
+ end
+ else
+ if record = @record.send(association)
+ record.to_xml(opts.merge(:root => association))
+ end
+ end
+ end
+
+ def serialize
+ args = [root]
+ if options[:namespace]
+ args << {:xmlns=>options[:namespace]}
+ end
+
+ if options[:type]
+ args << {:type=>options[:type]}
+ end
+
+ builder.tag!(*args) do
+ add_attributes
+ procs = options.delete(:procs)
+ add_includes { |association, records, opts| add_associations(association, records, opts) }
+ options[:procs] = procs
+ add_procs
+ yield builder if block_given?
+ end
+ end
+
+ class Attribute #:nodoc:
+ attr_reader :name, :value, :type
+
+ def initialize(name, record)
+ @name, @record = name, record
+
+ @type = compute_type
+ @value = compute_value
+ end
+
+ # There is a significant speed improvement if the value
+ # does not need to be escaped, as tag! escapes all values
+ # to ensure that valid XML is generated. For known binary
+ # values, it is at least an order of magnitude faster to
+ # Base64 encode binary values and directly put them in the
+ # output XML than to pass the original value or the Base64
+ # encoded value to the tag! method. It definitely makes
+ # no sense to Base64 encode the value and then give it to
+ # tag!, since that just adds additional overhead.
+ def needs_encoding?
+ ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
+ end
+
+ def decorations(include_types = true)
+ decorations = {}
+
+ if type == :binary
+ decorations[:encoding] = 'base64'
+ end
+
+ if include_types && type != :string
+ decorations[:type] = type
+ end
+
+ if value.nil?
+ decorations[:nil] = true
+ end
+
+ decorations
+ end
+
+ protected
+ def compute_type
+ type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
+
+ case type
+ when :text
+ :string
+ when :time
+ :datetime
+ else
+ type
+ end
+ end
+
+ def compute_value
+ value = @record.send(name)
+
+ if formatter = Hash::XML_FORMATTING[type.to_s]
+ value ? formatter.call(value) : nil
+ else
+ value
+ end
+ end
+ end
+
+ class MethodAttribute < Attribute #:nodoc:
+ protected
+ def compute_type
+ Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/test_case.rb b/vendor/rails/activerecord/lib/active_record/test_case.rb
new file mode 100644
index 0000000..7dee962
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/test_case.rb
@@ -0,0 +1,36 @@
+require "active_support/test_case"
+
+module ActiveRecord
+ class TestCase < ActiveSupport::TestCase #:nodoc:
+ self.fixture_path = FIXTURES_ROOT
+ self.use_instantiated_fixtures = false
+ self.use_transactional_fixtures = true
+
+ def create_fixtures(*table_names, &block)
+ Fixtures.create_fixtures(FIXTURES_ROOT, table_names, {}, &block)
+ end
+
+ def assert_date_from_db(expected, actual, message = nil)
+ # SQL Server doesn't have a separate column type just for dates,
+ # so the time is in the string and incorrectly formatted
+ if current_adapter?(:SQLServerAdapter)
+ assert_equal expected.strftime("%Y/%m/%d 00:00:00"), actual.strftime("%Y/%m/%d 00:00:00")
+ elsif current_adapter?(:SybaseAdapter)
+ assert_equal expected.to_s, actual.to_date.to_s, message
+ else
+ assert_equal expected.to_s, actual.to_s, message
+ end
+ end
+
+ def assert_queries(num = 1)
+ $query_count = 0
+ yield
+ ensure
+ assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
+ end
+
+ def assert_no_queries(&block)
+ assert_queries(0, &block)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/timestamp.rb b/vendor/rails/activerecord/lib/active_record/timestamp.rb
new file mode 100644
index 0000000..a9e0efa
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/timestamp.rb
@@ -0,0 +1,41 @@
+module ActiveRecord
+ # Active Record automatically timestamps create and update operations if the table has fields
+ # named created_at/created_on or updated_at/updated_on.
+ #
+ # Timestamping can be turned off by setting
+ # ActiveRecord::Base.record_timestamps = false
+ #
+ # Timestamps are in the local timezone by default but you can use UTC by setting
+ # ActiveRecord::Base.default_timezone = :utc
+ module Timestamp
+ def self.included(base) #:nodoc:
+ base.alias_method_chain :create, :timestamps
+ base.alias_method_chain :update, :timestamps
+
+ base.class_inheritable_accessor :record_timestamps, :instance_writer => false
+ base.record_timestamps = true
+ end
+
+ private
+ def create_with_timestamps #:nodoc:
+ if record_timestamps
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
+ write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
+
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
+ end
+ create_without_timestamps
+ end
+
+ def update_with_timestamps(*args) #:nodoc:
+ if record_timestamps && (!partial_updates? || changed?)
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
+ end
+ update_without_timestamps(*args)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/transactions.rb b/vendor/rails/activerecord/lib/active_record/transactions.rb
new file mode 100644
index 0000000..3b68357
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/transactions.rb
@@ -0,0 +1,130 @@
+require 'thread'
+
+module ActiveRecord
+ module Transactions # :nodoc:
+ class TransactionError < ActiveRecordError # :nodoc:
+ end
+
+ def self.included(base)
+ base.extend(ClassMethods)
+
+ base.class_eval do
+ [:destroy, :save, :save!].each do |method|
+ alias_method_chain method, :transactions
+ end
+ end
+ end
+
+ # Transactions are protective blocks where SQL statements are only permanent if they can all succeed as one atomic action.
+ # The classic example is a transfer between two accounts where you can only have a deposit if the withdrawal succeeded and
+ # vice versa. Transactions enforce the integrity of the database and guard the data against program errors or database break-downs.
+ # So basically you should use transaction blocks whenever you have a number of statements that must be executed together or
+ # not at all. Example:
+ #
+ # transaction do
+ # david.withdrawal(100)
+ # mary.deposit(100)
+ # end
+ #
+ # This example will only take money from David and give to Mary if neither +withdrawal+ nor +deposit+ raises an exception.
+ # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
+ # that the objects will _not_ have their instance data returned to their pre-transactional state.
+ #
+ # == Different Active Record classes in a single transaction
+ #
+ # Though the transaction class method is called on some Active Record class,
+ # the objects within the transaction block need not all be instances of
+ # that class.
+ # In this example a Balance record is transactionally saved even
+ # though transaction is called on the Account class:
+ #
+ # Account.transaction do
+ # balance.save!
+ # account.save!
+ # end
+ #
+ # == Transactions are not distributed across database connections
+ #
+ # A transaction acts on a single database connection. If you have
+ # multiple class-specific databases, the transaction will not protect
+ # interaction among them. One workaround is to begin a transaction
+ # on each class whose models you alter:
+ #
+ # Student.transaction do
+ # Course.transaction do
+ # course.enroll(student)
+ # student.units += course.units
+ # end
+ # end
+ #
+ # This is a poor solution, but full distributed transactions are beyond
+ # the scope of Active Record.
+ #
+ # == Save and destroy are automatically wrapped in a transaction
+ #
+ # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
+ # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
+ # depends on or you can raise exceptions in the callbacks to rollback.
+ #
+ # == Exception handling
+ #
+ # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
+ # should be ready to catch those in your application code. One exception is the ActiveRecord::Rollback exception, which will
+ # trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
+ module ClassMethods
+ def transaction(&block)
+ increment_open_transactions
+
+ begin
+ connection.transaction(Thread.current['start_db_transaction'], &block)
+ ensure
+ decrement_open_transactions
+ end
+ end
+
+ private
+ def increment_open_transactions #:nodoc:
+ open = Thread.current['open_transactions'] ||= 0
+ Thread.current['start_db_transaction'] = open.zero?
+ Thread.current['open_transactions'] = open + 1
+ end
+
+ def decrement_open_transactions #:nodoc:
+ Thread.current['open_transactions'] -= 1
+ end
+ end
+
+ def transaction(&block)
+ self.class.transaction(&block)
+ end
+
+ def destroy_with_transactions #:nodoc:
+ transaction { destroy_without_transactions }
+ end
+
+ def save_with_transactions(perform_validation = true) #:nodoc:
+ rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } }
+ end
+
+ def save_with_transactions! #:nodoc:
+ rollback_active_record_state! { transaction { save_without_transactions! } }
+ end
+
+ # Reset id and @new_record if the transaction rolls back.
+ def rollback_active_record_state!
+ id_present = has_attribute?(self.class.primary_key)
+ previous_id = id
+ previous_new_record = new_record?
+ yield
+ rescue Exception
+ @new_record = previous_new_record
+ if id_present
+ self.id = previous_id
+ else
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
+ end
+ raise
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/validations.rb b/vendor/rails/activerecord/lib/active_record/validations.rb
new file mode 100644
index 0000000..c97aafb
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/validations.rb
@@ -0,0 +1,961 @@
+module ActiveRecord
+ # Raised by save! and create! when the record is invalid. Use the
+ # +record+ method to retrieve the record which did not validate.
+ # begin
+ # complex_operation_that_calls_save!_internally
+ # rescue ActiveRecord::RecordInvalid => invalid
+ # puts invalid.record.errors
+ # end
+ class RecordInvalid < ActiveRecordError
+ attr_reader :record
+ def initialize(record)
+ @record = record
+ super("Validation failed: #{@record.errors.full_messages.join(", ")}")
+ end
+ end
+
+ # Active Record validation is reported to and from this object, which is used by Base#save to
+ # determine whether the object is in a valid state to be saved. See usage example in Validations.
+ class Errors
+ include Enumerable
+
+ def initialize(base) # :nodoc:
+ @base, @errors = base, {}
+ end
+
+ @@default_error_messages = {
+ :inclusion => "is not included in the list",
+ :exclusion => "is reserved",
+ :invalid => "is invalid",
+ :confirmation => "doesn't match confirmation",
+ :accepted => "must be accepted",
+ :empty => "can't be empty",
+ :blank => "can't be blank",
+ :too_long => "is too long (maximum is %d characters)",
+ :too_short => "is too short (minimum is %d characters)",
+ :wrong_length => "is the wrong length (should be %d characters)",
+ :taken => "has already been taken",
+ :not_a_number => "is not a number",
+ :greater_than => "must be greater than %d",
+ :greater_than_or_equal_to => "must be greater than or equal to %d",
+ :equal_to => "must be equal to %d",
+ :less_than => "must be less than %d",
+ :less_than_or_equal_to => "must be less than or equal to %d",
+ :odd => "must be odd",
+ :even => "must be even"
+ }
+
+ # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
+ cattr_accessor :default_error_messages
+
+
+ # Adds an error to the base object instead of any particular attribute. This is used
+ # to report errors that don't tie to any specific attribute, but rather to the object
+ # as a whole. These error messages don't get prepended with any field name when iterating
+ # with each_full, so they should be complete sentences.
+ def add_to_base(msg)
+ add(:base, msg)
+ end
+
+ # Adds an error message (+msg+) to the +attribute+, which will be returned on a call to on(attribute)
+ # for the same attribute and ensure that this error object returns false when asked if empty?. More than one
+ # error can be added to the same +attribute+ in which case an array will be returned on a call to on(attribute).
+ # If no +msg+ is supplied, "invalid" is assumed.
+ def add(attribute, msg = @@default_error_messages[:invalid])
+ @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
+ @errors[attribute.to_s] << msg
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is empty.
+ def add_on_empty(attributes, msg = @@default_error_messages[:empty])
+ for attr in [attributes].flatten
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
+ is_empty = value.respond_to?("empty?") ? value.empty? : false
+ add(attr, msg) unless !value.nil? && !is_empty
+ end
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
+ def add_on_blank(attributes, msg = @@default_error_messages[:blank])
+ for attr in [attributes].flatten
+ value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
+ add(attr, msg) if value.blank?
+ end
+ end
+
+ # Returns true if the specified +attribute+ has errors associated with it.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.invalid?(:name) # => true
+ # company.errors.invalid?(:address) # => false
+ def invalid?(attribute)
+ !@errors[attribute.to_s].nil?
+ end
+
+ # Returns nil, if no errors are associated with the specified +attribute+.
+ # Returns the error message, if one error is associated with the specified +attribute+.
+ # Returns an array of error messages, if more than one error is associated with the specified +attribute+.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.on(:name) # => ["is too short (minimum is 5 characters)", "can't be blank"]
+ # company.errors.on(:email) # => "can't be blank"
+ # company.errors.on(:address) # => nil
+ def on(attribute)
+ errors = @errors[attribute.to_s]
+ return nil if errors.nil?
+ errors.size == 1 ? errors.first : errors
+ end
+
+ alias :[] :on
+
+ # Returns errors assigned to the base object through add_to_base according to the normal rules of on(attribute).
+ def on_base
+ on(:base)
+ end
+
+ # Yields each attribute and associated message per error added.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.each{|attr,msg| puts "#{attr} - #{msg}" } # =>
+ # name - is too short (minimum is 5 characters)
+ # name - can't be blank
+ # address - can't be blank
+ def each
+ @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
+ end
+
+ # Yields each full error message added. So Person.errors.add("first_name", "can't be empty") will be returned
+ # through iteration as "First name can't be empty".
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.each_full{|msg| puts msg } # =>
+ # Name is too short (minimum is 5 characters)
+ # Name can't be blank
+ # Address can't be blank
+ def each_full
+ full_messages.each { |msg| yield msg }
+ end
+
+ # Returns all the full error messages in an array.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.full_messages # =>
+ # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ def full_messages
+ full_messages = []
+
+ @errors.each_key do |attr|
+ @errors[attr].each do |msg|
+ next if msg.nil?
+
+ if attr == "base"
+ full_messages << msg
+ else
+ full_messages << @base.class.human_attribute_name(attr) + " " + msg
+ end
+ end
+ end
+ full_messages
+ end
+
+ # Returns true if no errors have been added.
+ def empty?
+ @errors.empty?
+ end
+
+ # Removes all errors that have been added.
+ def clear
+ @errors = {}
+ end
+
+ # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
+ def size
+ @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
+ end
+
+ alias_method :count, :size
+ alias_method :length, :size
+
+ # Returns an XML representation of this error object.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.to_xml # =>
+ #
+ #
+ # Name is too short (minimum is 5 characters)
+ # Name can't be blank
+ # Address can't be blank
+ #
+ def to_xml(options={})
+ options[:root] ||= "errors"
+ options[:indent] ||= 2
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
+
+ options[:builder].instruct! unless options.delete(:skip_instruct)
+ options[:builder].errors do |e|
+ full_messages.each { |msg| e.error(msg) }
+ end
+ end
+ end
+
+
+ # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and
+ # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring
+ # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression).
+ #
+ # Example:
+ #
+ # class Person < ActiveRecord::Base
+ # protected
+ # def validate
+ # errors.add_on_empty %w( first_name last_name )
+ # errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/
+ # end
+ #
+ # def validate_on_create # is only run the first time a new object is saved
+ # unless valid_discount?(membership_discount)
+ # errors.add("membership_discount", "has expired")
+ # end
+ # end
+ #
+ # def validate_on_update
+ # errors.add_to_base("No changes have occurred") if unchanged_attributes?
+ # end
+ # end
+ #
+ # person = Person.new("first_name" => "David", "phone_number" => "what?")
+ # person.save # => false (and doesn't do the save)
+ # person.errors.empty? # => false
+ # person.errors.count # => 2
+ # person.errors.on "last_name" # => "can't be empty"
+ # person.errors.on "phone_number" # => "has invalid format"
+ # person.errors.each_full { |msg| puts msg }
+ # # => "Last name can't be empty\n" +
+ # "Phone number has invalid format"
+ #
+ # person.attributes = { "last_name" => "Heinemeier", "phone_number" => "555-555" }
+ # person.save # => true (and person is now saved in the database)
+ #
+ # An Errors object is automatically created for every Active Record.
+ #
+ # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations.
+ module Validations
+ VALIDATIONS = %w( validate validate_on_create validate_on_update )
+
+ def self.included(base) # :nodoc:
+ base.extend ClassMethods
+ base.class_eval do
+ alias_method_chain :save, :validation
+ alias_method_chain :save!, :validation
+ alias_method_chain :update_attribute, :validation_skipping
+ end
+
+ base.send :include, ActiveSupport::Callbacks
+ base.define_callbacks *VALIDATIONS
+ end
+
+ # All of the following validations are defined in the class scope of the model that you're interested in validating.
+ # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
+ # these over the low-level calls to +validate+ and +validate_on_create+ when possible.
+ module ClassMethods
+ DEFAULT_VALIDATION_OPTIONS = {
+ :on => :save,
+ :allow_nil => false,
+ :allow_blank => false,
+ :message => nil
+ }.freeze
+
+ ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
+ ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
+ :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
+ :odd => 'odd?', :even => 'even?' }.freeze
+
+ # Adds a validation method or block to the class. This is useful when
+ # overriding the +validate+ instance method becomes too unwieldly and
+ # you're looking for more descriptive declaration of your validations.
+ #
+ # This can be done with a symbol pointing to a method:
+ #
+ # class Comment < ActiveRecord::Base
+ # validate :must_be_friends
+ #
+ # def must_be_friends
+ # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # end
+ # end
+ #
+ # Or with a block which is passed the current record to be validated:
+ #
+ # class Comment < ActiveRecord::Base
+ # validate do |comment|
+ # comment.must_be_friends
+ # end
+ #
+ # def must_be_friends
+ # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # end
+ # end
+ #
+ # This usage applies to +validate_on_create+ and +validate_on_update+ as well.
+
+ # Validates each attribute against a block.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_each :first_name, :last_name do |record, attr, value|
+ # record.errors.add attr, 'starts with z.' if value[0] == ?z
+ # end
+ # end
+ #
+ # Options:
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :allow_nil - Skip validation if attribute is +nil+.
+ # * :allow_blank - Skip validation if attribute is blank.
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_each(*attrs)
+ options = attrs.extract_options!.symbolize_keys
+ attrs = attrs.flatten
+
+ # Declare the validation.
+ send(validation_method(options[:on] || :save), options) do |record|
+ attrs.each do |attr|
+ value = record.send(attr)
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
+ yield record, attr, value
+ end
+ end
+ end
+
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
+ #
+ # Model:
+ # class Person < ActiveRecord::Base
+ # validates_confirmation_of :user_name, :password
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
+ # end
+ #
+ # View:
+ # <%= password_field "person", "password" %>
+ # <%= password_field "person", "password_confirmation" %>
+ #
+ # The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password.
+ # To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed
+ # only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence
+ # check for the confirmation attribute:
+ #
+ # validates_presence_of :password_confirmation, :if => :password_changed?
+ #
+ # Configuration options:
+ # * :message - A custom error message (default is: "doesn't match confirmation").
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_confirmation_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
+ configuration.update(attr_names.extract_options!)
+
+ attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
+
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
+ end
+ end
+
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_acceptance_of :terms_of_service
+ # validates_acceptance_of :eula, :message => "must be abided"
+ # end
+ #
+ # If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
+ # performed only if +terms_of_service+ is not +nil+ and by default on save.
+ #
+ # Configuration options:
+ # * :message - A custom error message (default is: "must be accepted").
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :allow_nil - Skip validation if attribute is +nil+ (default is true).
+ # * :accept - Specifies value that is considered accepted. The default value is a string "1", which
+ # makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
+ # column, since the attribute is typecast from "1" to +true+ before validation.
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_acceptance_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:accepted], :on => :save, :allow_nil => true, :accept => "1" }
+ configuration.update(attr_names.extract_options!)
+
+ db_cols = begin
+ column_names
+ rescue ActiveRecord::StatementInvalid
+ []
+ end
+ names = attr_names.reject { |name| db_cols.include?(name.to_s) }
+ attr_accessor(*names)
+
+ validates_each(attr_names,configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message]) unless value == configuration[:accept]
+ end
+ end
+
+ # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_presence_of :first_name
+ # end
+ #
+ # The first_name attribute must be in the object and it cannot be blank.
+ #
+ # If you want to validate the presence of a boolean field (where the real values are true and false),
+ # you will want to use validates_inclusion_of :field_name, :in => [true, false]
+ # This is due to the way Object#blank? handles boolean values. false.blank? # => true
+ #
+ # Configuration options:
+ # * message - A custom error message (default is: "can't be blank").
+ # * on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ #
+ def validates_presence_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save }
+ configuration.update(attr_names.extract_options!)
+
+ # can't use validates_each here, because it cannot cope with nonexistent attributes,
+ # while errors.add_on_empty can
+ send(validation_method(configuration[:on]), configuration) do |record|
+ record.errors.add_on_blank(attr_names, configuration[:message])
+ end
+ end
+
+ # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
+ #
+ # class Person < ActiveRecord::Base
+ # validates_length_of :first_name, :maximum=>30
+ # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
+ # validates_length_of :fax, :in => 7..32, :allow_nil => true
+ # validates_length_of :phone, :in => 7..32, :allow_blank => true
+ # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
+ # validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
+ # validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
+ # end
+ #
+ # Configuration options:
+ # * :minimum - The minimum size of the attribute.
+ # * :maximum - The maximum size of the attribute.
+ # * :is - The exact size of the attribute.
+ # * :within - A range specifying the minimum and maximum size of the attribute.
+ # * :in - A synonym(or alias) for :within.
+ # * :allow_nil - Attribute may be +nil+; skip validation.
+ # * :allow_blank - Attribute may be blank; skip validation.
+ #
+ # * :too_long - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)").
+ # * :too_short - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)").
+ # * :wrong_length - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)").
+ # * :message - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message.
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_length_of(*attrs)
+ # Merge given options with defaults.
+ options = {
+ :too_long => ActiveRecord::Errors.default_error_messages[:too_long],
+ :too_short => ActiveRecord::Errors.default_error_messages[:too_short],
+ :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length]
+ }.merge(DEFAULT_VALIDATION_OPTIONS)
+ options.update(attrs.extract_options!.symbolize_keys)
+
+ # Ensure that one and only one range option is specified.
+ range_options = ALL_RANGE_OPTIONS & options.keys
+ case range_options.size
+ when 0
+ raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ when 1
+ # Valid number of options; do nothing.
+ else
+ raise ArgumentError, 'Too many range options specified. Choose only one.'
+ end
+
+ # Get range option and value.
+ option = range_options.first
+ option_value = options[range_options.first]
+
+ case option
+ when :within, :in
+ raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
+
+ too_short = options[:too_short] % option_value.begin
+ too_long = options[:too_long] % option_value.end
+
+ validates_each(attrs, options) do |record, attr, value|
+ value = value.split(//) if value.kind_of?(String)
+ if value.nil? or value.size < option_value.begin
+ record.errors.add(attr, too_short)
+ elsif value.size > option_value.end
+ record.errors.add(attr, too_long)
+ end
+ end
+ when :is, :minimum, :maximum
+ raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
+
+ # Declare different validations per option.
+ validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
+ message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
+
+ message = (options[:message] || options[message_options[option]]) % option_value
+
+ validates_each(attrs, options) do |record, attr, value|
+ value = value.split(//) if value.kind_of?(String)
+ record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
+ end
+ end
+ end
+
+ alias_method :validates_size_of, :validates_length_of
+
+
+ # Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
+ # can be named "davidhh".
+ #
+ # class Person < ActiveRecord::Base
+ # validates_uniqueness_of :user_name, :scope => :account_id
+ # end
+ #
+ # It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
+ # making sure that a teacher can only be on the schedule once per semester for a particular class.
+ #
+ # class TeacherSchedule < ActiveRecord::Base
+ # validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
+ # end
+ #
+ # When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
+ # attribute (that maps to a column). When the record is updated, the same check is made but disregarding the record itself.
+ #
+ # Because this check is performed outside the database there is still a chance that duplicate values
+ # will be inserted in two parallel transactions. To guarantee against this you should create a
+ # unique index on the field. See +add_index+ for more information.
+ #
+ # Configuration options:
+ # * :message - Specifies a custom error message (default is: "has already been taken").
+ # * :scope - One or more columns by which to limit the scope of the uniqueness constraint.
+ # * :case_sensitive - Looks for an exact match. Ignored by non-text columns (+false+ by default).
+ # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_uniqueness_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true }
+ configuration.update(attr_names.extract_options!)
+
+ validates_each(attr_names,configuration) do |record, attr_name, value|
+ # The check for an existing value should be run from a class that
+ # isn't abstract. This means working down from the current class
+ # (self), to the first non-abstract class. Since classes don't know
+ # their subclasses, we have to build the hierarchy between self and
+ # the record's class.
+ class_hierarchy = [record.class]
+ while class_hierarchy.first != self
+ class_hierarchy.insert(0, class_hierarchy.first.superclass)
+ end
+
+ # Now we can work our way down the tree to the first non-abstract
+ # class (which has a database table to query from).
+ finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
+
+ if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?)
+ condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"
+ condition_params = [value]
+ else
+ # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't.
+ # Hence, this is needed only for sqlite.
+ condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"
+ condition_params = [value.downcase]
+ end
+
+ if scope = configuration[:scope]
+ Array(scope).map do |scope_item|
+ scope_value = record.send(scope_item)
+ condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}"
+ condition_params << scope_value
+ end
+ end
+
+ unless record.new_record?
+ condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"
+ condition_params << record.send(:id)
+ end
+
+ results = finder_class.with_exclusive_scope do
+ connection.select_all(
+ construct_finder_sql(
+ :select => "#{connection.quote_column_name(attr_name)}",
+ :from => "#{finder_class.quoted_table_name}",
+ :conditions => [condition_sql, *condition_params]
+ )
+ )
+ end
+
+ unless results.length.zero?
+ found = true
+
+ # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate
+ # column in ruby when case sensitive option
+ if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text?
+ found = results.any? { |a| a[attr_name.to_s] == value }
+ end
+
+ record.errors.add(attr_name, configuration[:message]) if found
+ end
+ end
+ end
+
+
+ # Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
+ # provided.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
+ # end
+ #
+ # Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line.
+ #
+ # A regular expression must be provided or else an exception will be raised.
+ #
+ # Configuration options:
+ # * :message - A custom error message (default is: "is invalid").
+ # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * :with - The regular expression used to validate the format with (note: must be supplied!).
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_format_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
+ configuration.update(attr_names.extract_options!)
+
+ raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
+
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message] % value) unless value.to_s =~ configuration[:with]
+ end
+ end
+
+ # Validates whether the value of the specified attribute is available in a particular enumerable object.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!"
+ # validates_inclusion_of :age, :in => 0..99
+ # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list"
+ # end
+ #
+ # Configuration options:
+ # * :in - An enumerable object of available items.
+ # * :message - Specifies a custom error message (default is: "is not included in the list").
+ # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_inclusion_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:inclusion], :on => :save }
+ configuration.update(attr_names.extract_options!)
+
+ enum = configuration[:in] || configuration[:within]
+
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value)
+ end
+ end
+
+ # Validates that the value of the specified attribute is not in a particular enumerable object.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
+ # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
+ # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed"
+ # end
+ #
+ # Configuration options:
+ # * :in - An enumerable object of items that the value shouldn't be part of.
+ # * :message - Specifies a custom error message (default is: "is reserved").
+ # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
+ # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_exclusion_of(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:exclusion], :on => :save }
+ configuration.update(attr_names.extract_options!)
+
+ enum = configuration[:in] || configuration[:within]
+
+ raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
+
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value)
+ end
+ end
+
+ # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
+ #
+ # class Book < ActiveRecord::Base
+ # has_many :pages
+ # belongs_to :library
+ #
+ # validates_associated :pages, :library
+ # end
+ #
+ # Warning: If, after the above definition, you then wrote:
+ #
+ # class Page < ActiveRecord::Base
+ # belongs_to :book
+ #
+ # validates_associated :book
+ # end
+ #
+ # this would specify a circular dependency and cause infinite recursion.
+ #
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
+ # is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
+ #
+ # Configuration options:
+ # * :message - A custom error message (default is: "is invalid")
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_associated(*attr_names)
+ configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save }
+ configuration.update(attr_names.extract_options!)
+
+ validates_each(attr_names, configuration) do |record, attr_name, value|
+ record.errors.add(attr_name, configuration[:message]) unless
+ (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
+ end
+ end
+
+ # Validates whether the value of the specified attribute is numeric by trying to convert it to
+ # a float with Kernel.Float (if only_integer is false) or applying it to the regular expression
+ # /\A[\+\-]?\d+\Z/ (if only_integer is set to true).
+ #
+ # class Person < ActiveRecord::Base
+ # validates_numericality_of :value, :on => :create
+ # end
+ #
+ # Configuration options:
+ # * :message - A custom error message (default is: "is not a number").
+ # * :on - Specifies when this validation is active (default is :save, other options :create, :update).
+ # * :only_integer - Specifies whether the value has to be an integer, e.g. an integral value (default is +false+).
+ # * :allow_nil - Skip validation if attribute is +nil+ (default is +false+). Notice that for fixnum and float columns empty strings are converted to +nil+.
+ # * :greater_than - Specifies the value must be greater than the supplied value.
+ # * :greater_than_or_equal_to - Specifies the value must be greater than or equal the supplied value.
+ # * :equal_to - Specifies the value must be equal to the supplied value.
+ # * :less_than - Specifies the value must be less than the supplied value.
+ # * :less_than_or_equal_to - Specifies the value must be less than or equal the supplied value.
+ # * :odd - Specifies the value must be an odd number.
+ # * :even - Specifies the value must be an even number.
+ # * :if - Specifies a method, proc or string to call to determine if the validation should
+ # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ # * :unless - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
+ # method, proc or string should return or evaluate to a true or false value.
+ def validates_numericality_of(*attr_names)
+ configuration = { :on => :save, :only_integer => false, :allow_nil => false }
+ configuration.update(attr_names.extract_options!)
+
+
+ numericality_options = ALL_NUMERICALITY_CHECKS.keys & configuration.keys
+
+ (numericality_options - [ :odd, :even ]).each do |option|
+ raise ArgumentError, ":#{option} must be a number" unless configuration[option].is_a?(Numeric)
+ end
+
+ validates_each(attr_names,configuration) do |record, attr_name, value|
+ raw_value = record.send("#{attr_name}_before_type_cast") || value
+
+ next if configuration[:allow_nil] and raw_value.nil?
+
+ if configuration[:only_integer]
+ unless raw_value.to_s =~ /\A[+-]?\d+\Z/
+ record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ next
+ end
+ raw_value = raw_value.to_i
+ else
+ begin
+ raw_value = Kernel.Float(raw_value.to_s)
+ rescue ArgumentError, TypeError
+ record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[:not_a_number])
+ next
+ end
+ end
+
+ numericality_options.each do |option|
+ case option
+ when :odd, :even
+ record.errors.add(attr_name, configuration[:message] || ActiveRecord::Errors.default_error_messages[option]) unless raw_value.to_i.method(ALL_NUMERICALITY_CHECKS[option])[]
+ else
+ message = configuration[:message] || ActiveRecord::Errors.default_error_messages[option]
+ message = message % configuration[option] if configuration[option]
+ record.errors.add(attr_name, message) unless raw_value.method(ALL_NUMERICALITY_CHECKS[option])[configuration[option]]
+ end
+ end
+ end
+ end
+
+ # Creates an object just like Base.create but calls save! instead of save
+ # so an exception is raised if the record is invalid.
+ def create!(attributes = nil, &block)
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ object = new(attributes)
+ yield(object) if block_given?
+ object.save!
+ object
+ end
+ end
+
+ private
+ def validation_method(on)
+ case on
+ when :save then :validate
+ when :create then :validate_on_create
+ when :update then :validate_on_update
+ end
+ end
+ end
+
+ # The validation process on save can be skipped by passing false. The regular Base#save method is
+ # replaced with this when the validations module is mixed in, which it is by default.
+ def save_with_validation(perform_validation = true)
+ if perform_validation && valid? || !perform_validation
+ save_without_validation
+ else
+ false
+ end
+ end
+
+ # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
+ # if the record is not valid.
+ def save_with_validation!
+ if valid?
+ save_without_validation!
+ else
+ raise RecordInvalid.new(self)
+ end
+ end
+
+ # Updates a single attribute and saves the record without going through the normal validation procedure.
+ # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
+ # in Base is replaced with this when the validations module is mixed in, which it is by default.
+ def update_attribute_with_validation_skipping(name, value)
+ send(name.to_s + '=', value)
+ save(false)
+ end
+
+ # Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false.
+ def valid?
+ errors.clear
+
+ run_callbacks(:validate)
+ validate
+
+ if new_record?
+ run_callbacks(:validate_on_create)
+ validate_on_create
+ else
+ run_callbacks(:validate_on_update)
+ validate_on_update
+ end
+
+ errors.empty?
+ end
+
+ # Returns the Errors object that holds all information about attribute error messages.
+ def errors
+ @errors ||= Errors.new(self)
+ end
+
+ protected
+ # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
+ def validate #:doc:
+ end
+
+ # Overwrite this method for validation checks used only on creation.
+ def validate_on_create #:doc:
+ end
+
+ # Overwrite this method for validation checks used only on updates.
+ def validate_on_update # :doc:
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/vendor/db2.rb b/vendor/rails/activerecord/lib/active_record/vendor/db2.rb
new file mode 100644
index 0000000..812c8cc
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/vendor/db2.rb
@@ -0,0 +1,362 @@
+require 'db2/db2cli.rb'
+
+module DB2
+ module DB2Util
+ include DB2CLI
+
+ def free() SQLFreeHandle(@handle_type, @handle); end
+ def handle() @handle; end
+
+ def check_rc(rc)
+ if ![SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NO_DATA_FOUND].include?(rc)
+ rec = 1
+ msg = ''
+ loop do
+ a = SQLGetDiagRec(@handle_type, @handle, rec, 500)
+ break if a[0] != SQL_SUCCESS
+ msg << a[3] if !a[3].nil? and a[3] != '' # Create message.
+ rec += 1
+ end
+ raise "DB2 error: #{msg}"
+ end
+ end
+ end
+
+ class Environment
+ include DB2Util
+
+ def initialize
+ @handle_type = SQL_HANDLE_ENV
+ rc, @handle = SQLAllocHandle(@handle_type, SQL_NULL_HANDLE)
+ check_rc(rc)
+ end
+
+ def data_sources(buffer_length = 1024)
+ retval = []
+ max_buffer_length = buffer_length
+
+ a = SQLDataSources(@handle, SQL_FETCH_FIRST, SQL_MAX_DSN_LENGTH + 1, buffer_length)
+ retval << [a[1], a[3]]
+ max_buffer_length = [max_buffer_length, a[4]].max
+
+ loop do
+ a = SQLDataSources(@handle, SQL_FETCH_NEXT, SQL_MAX_DSN_LENGTH + 1, buffer_length)
+ break if a[0] == SQL_NO_DATA_FOUND
+
+ retval << [a[1], a[3]]
+ max_buffer_length = [max_buffer_length, a[4]].max
+ end
+
+ if max_buffer_length > buffer_length
+ get_data_sources(max_buffer_length)
+ else
+ retval
+ end
+ end
+ end
+
+ class Connection
+ include DB2Util
+
+ def initialize(environment)
+ @env = environment
+ @handle_type = SQL_HANDLE_DBC
+ rc, @handle = SQLAllocHandle(@handle_type, @env.handle)
+ check_rc(rc)
+ end
+
+ def connect(server_name, user_name = '', auth = '')
+ check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
+ end
+
+ def set_connect_attr(attr, value)
+ value += "\0" if value.class == String
+ check_rc(SQLSetConnectAttr(@handle, attr, value))
+ end
+
+ def set_auto_commit_on
+ set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_ON)
+ end
+
+ def set_auto_commit_off
+ set_connect_attr(SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF)
+ end
+
+ def disconnect
+ check_rc(SQLDisconnect(@handle))
+ end
+
+ def rollback
+ check_rc(SQLEndTran(@handle_type, @handle, SQL_ROLLBACK))
+ end
+
+ def commit
+ check_rc(SQLEndTran(@handle_type, @handle, SQL_COMMIT))
+ end
+ end
+
+ class Statement
+ include DB2Util
+
+ def initialize(connection)
+ @conn = connection
+ @handle_type = SQL_HANDLE_STMT
+ @parms = [] #yun
+ @sql = '' #yun
+ @numParms = 0 #yun
+ @prepared = false #yun
+ @parmArray = [] #yun. attributes of the parameter markers
+ rc, @handle = SQLAllocHandle(@handle_type, @conn.handle)
+ check_rc(rc)
+ end
+
+ def columns(table_name, schema_name = '%')
+ check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
+ fetch_all
+ end
+
+ def tables(schema_name = '%')
+ check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
+ fetch_all
+ end
+
+ def indexes(table_name, schema_name = '')
+ check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
+ fetch_all
+ end
+
+ def prepare(sql)
+ @sql = sql
+ check_rc(SQLPrepare(@handle, sql))
+ rc, @numParms = SQLNumParams(@handle) #number of question marks
+ check_rc(rc)
+ #--------------------------------------------------------------------------
+ # parameter attributes are stored in instance variable @parmArray so that
+ # they are available when execute method is called.
+ #--------------------------------------------------------------------------
+ if @numParms > 0 # get parameter marker attributes
+ 1.upto(@numParms) do |i| # parameter number starts from 1
+ rc, type, size, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ @parmArray << Parameter.new(type, size, decimalDigits)
+ end
+ end
+ @prepared = true
+ self
+ end
+
+ def execute(*parms)
+ raise "The statement was not prepared" if @prepared == false
+
+ if parms.size == 1 and parms[0].class == Array
+ parms = parms[0]
+ end
+
+ if @numParms != parms.size
+ raise "Number of parameters supplied does not match with the SQL statement"
+ end
+
+ if @numParms > 0 #need to bind parameters
+ #--------------------------------------------------------------------
+ #calling bindParms may not be safe. Look comment below.
+ #--------------------------------------------------------------------
+ #bindParms(parms)
+
+ valueArray = []
+ 1.upto(@numParms) do |i| # parameter number starts from 1
+ type = @parmArray[i - 1].class
+ size = @parmArray[i - 1].size
+ decimalDigits = @parmArray[i - 1].decimalDigits
+
+ if parms[i - 1].class == String
+ valueArray << parms[i - 1]
+ else
+ valueArray << parms[i - 1].to_s
+ end
+
+ rc = SQLBindParameter(@handle, i, type, size, decimalDigits, valueArray[i - 1])
+ check_rc(rc)
+ end
+ end
+
+ check_rc(SQLExecute(@handle))
+
+ if @numParms != 0
+ check_rc(SQLFreeStmt(@handle, SQL_RESET_PARAMS)) # Reset parameters
+ end
+
+ self
+ end
+
+ #-------------------------------------------------------------------------------
+ # The last argument(value) to SQLBindParameter is a deferred argument, that is,
+ # it should be available when SQLExecute is called. Even though "value" is
+ # local to bindParms method, it seems that it is available when SQLExecute
+ # is called. I am not sure whether it would still work if garbage collection
+ # is done between bindParms call and SQLExecute call inside the execute method
+ # above.
+ #-------------------------------------------------------------------------------
+ def bindParms(parms) # This is the real thing. It uses SQLBindParms
+ 1.upto(@numParms) do |i| # parameter number starts from 1
+ rc, dataType, parmSize, decimalDigits = SQLDescribeParam(@handle, i)
+ check_rc(rc)
+ if parms[i - 1].class == String
+ value = parms[i - 1]
+ else
+ value = parms[i - 1].to_s
+ end
+ rc = SQLBindParameter(@handle, i, dataType, parmSize, decimalDigits, value)
+ check_rc(rc)
+ end
+ end
+
+ #------------------------------------------------------------------------------
+ # bind method does not use DB2's SQLBindParams, but replaces "?" in the
+ # SQL statement with the value before passing the SQL statement to DB2.
+ # It is not efficient and can handle only strings since it puts everything in
+ # quotes.
+ #------------------------------------------------------------------------------
+ def bind(sql, args) #does not use SQLBindParams
+ arg_index = 0
+ result = ""
+ tokens(sql).each do |part|
+ case part
+ when '?'
+ result << "'" + (args[arg_index]) + "'" #put it into quotes
+ arg_index += 1
+ when '??'
+ result << "?"
+ else
+ result << part
+ end
+ end
+ if arg_index < args.size
+ raise "Too many SQL parameters"
+ elsif arg_index > args.size
+ raise "Not enough SQL parameters"
+ end
+ result
+ end
+
+ ## Break the sql string into parts.
+ #
+ # This is NOT a full lexer for SQL. It just breaks up the SQL
+ # string enough so that question marks, double question marks and
+ # quoted strings are separated. This is used when binding
+ # arguments to "?" in the SQL string. Note: comments are not
+ # handled.
+ #
+ def tokens(sql)
+ toks = sql.scan(/('([^'\\]|''|\\.)*'|"([^"\\]|""|\\.)*"|\?\??|[^'"?]+)/)
+ toks.collect { |t| t[0] }
+ end
+
+ def exec_direct(sql)
+ check_rc(SQLExecDirect(@handle, sql))
+ self
+ end
+
+ def set_cursor_name(name)
+ check_rc(SQLSetCursorName(@handle, name))
+ self
+ end
+
+ def get_cursor_name
+ rc, name = SQLGetCursorName(@handle)
+ check_rc(rc)
+ name
+ end
+
+ def row_count
+ rc, rowcount = SQLRowCount(@handle)
+ check_rc(rc)
+ rowcount
+ end
+
+ def num_result_cols
+ rc, cols = SQLNumResultCols(@handle)
+ check_rc(rc)
+ cols
+ end
+
+ def fetch_all
+ if block_given?
+ while row = fetch do
+ yield row
+ end
+ else
+ res = []
+ while row = fetch do
+ res << row
+ end
+ res
+ end
+ end
+
+ def fetch
+ cols = get_col_desc
+ rc = SQLFetch(@handle)
+ if rc == SQL_NO_DATA_FOUND
+ SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
+ SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
+ return nil
+ end
+ raise "ERROR" unless rc == SQL_SUCCESS
+
+ retval = []
+ cols.each_with_index do |c, i|
+ rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
+ retval << adjust_content(content)
+ end
+ retval
+ end
+
+ def fetch_as_hash
+ cols = get_col_desc
+ rc = SQLFetch(@handle)
+ if rc == SQL_NO_DATA_FOUND
+ SQLFreeStmt(@handle, SQL_CLOSE) # Close cursor
+ SQLFreeStmt(@handle, SQL_RESET_PARAMS) # Reset parameters
+ return nil
+ end
+ raise "ERROR" unless rc == SQL_SUCCESS
+
+ retval = {}
+ cols.each_with_index do |c, i|
+ rc, content = SQLGetData(@handle, i + 1, c[1], c[2] + 1) #yun added 1 to c[2]
+ retval[c[0]] = adjust_content(content)
+ end
+ retval
+ end
+
+ def get_col_desc
+ rc, nr_cols = SQLNumResultCols(@handle)
+ cols = (1..nr_cols).collect do |c|
+ rc, name, bl, type, col_sz = SQLDescribeCol(@handle, c, 1024)
+ [name.downcase, type, col_sz]
+ end
+ end
+
+ def adjust_content(c)
+ case c.class.to_s
+ when 'DB2CLI::NullClass'
+ return nil
+ when 'DB2CLI::Time'
+ "%02d:%02d:%02d" % [c.hour, c.minute, c.second]
+ when 'DB2CLI::Date'
+ "%04d-%02d-%02d" % [c.year, c.month, c.day]
+ when 'DB2CLI::Timestamp'
+ "%04d-%02d-%02d %02d:%02d:%02d" % [c.year, c.month, c.day, c.hour, c.minute, c.second]
+ else
+ return c
+ end
+ end
+ end
+
+ class Parameter
+ attr_reader :type, :size, :decimalDigits
+ def initialize(type, size, decimalDigits)
+ @type, @size, @decimalDigits = type, size, decimalDigits
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb b/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb
new file mode 100644
index 0000000..1c3294c
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/vendor/mysql.rb
@@ -0,0 +1,1214 @@
+# $Id: mysql.rb,v 1.24 2005/02/12 11:37:15 tommy Exp $
+#
+# Copyright (C) 2003-2005 TOMITA Masahiro
+# tommy@tmtm.org
+#
+
+class Mysql
+
+ VERSION = "4.0-ruby-0.2.6-plus-changes"
+
+ require "socket"
+ require "digest/sha1"
+
+ MAX_PACKET_LENGTH = 256*256*256-1
+ MAX_ALLOWED_PACKET = 1024*1024*1024
+
+ MYSQL_UNIX_ADDR = "/tmp/mysql.sock"
+ MYSQL_PORT = 3306
+ PROTOCOL_VERSION = 10
+
+ SCRAMBLE_LENGTH = 20
+ SCRAMBLE_LENGTH_323 = 8
+
+ # Command
+ COM_SLEEP = 0
+ COM_QUIT = 1
+ COM_INIT_DB = 2
+ COM_QUERY = 3
+ COM_FIELD_LIST = 4
+ COM_CREATE_DB = 5
+ COM_DROP_DB = 6
+ COM_REFRESH = 7
+ COM_SHUTDOWN = 8
+ COM_STATISTICS = 9
+ COM_PROCESS_INFO = 10
+ COM_CONNECT = 11
+ COM_PROCESS_KILL = 12
+ COM_DEBUG = 13
+ COM_PING = 14
+ COM_TIME = 15
+ COM_DELAYED_INSERT = 16
+ COM_CHANGE_USER = 17
+ COM_BINLOG_DUMP = 18
+ COM_TABLE_DUMP = 19
+ COM_CONNECT_OUT = 20
+ COM_REGISTER_SLAVE = 21
+
+ # Client flag
+ CLIENT_LONG_PASSWORD = 1
+ CLIENT_FOUND_ROWS = 1 << 1
+ CLIENT_LONG_FLAG = 1 << 2
+ CLIENT_CONNECT_WITH_DB= 1 << 3
+ CLIENT_NO_SCHEMA = 1 << 4
+ CLIENT_COMPRESS = 1 << 5
+ CLIENT_ODBC = 1 << 6
+ CLIENT_LOCAL_FILES = 1 << 7
+ CLIENT_IGNORE_SPACE = 1 << 8
+ CLIENT_PROTOCOL_41 = 1 << 9
+ CLIENT_INTERACTIVE = 1 << 10
+ CLIENT_SSL = 1 << 11
+ CLIENT_IGNORE_SIGPIPE = 1 << 12
+ CLIENT_TRANSACTIONS = 1 << 13
+ CLIENT_RESERVED = 1 << 14
+ CLIENT_SECURE_CONNECTION = 1 << 15
+ CLIENT_CAPABILITIES = CLIENT_LONG_PASSWORD|CLIENT_LONG_FLAG|CLIENT_TRANSACTIONS
+ PROTO_AUTH41 = CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION
+
+ # Connection Option
+ OPT_CONNECT_TIMEOUT = 0
+ OPT_COMPRESS = 1
+ OPT_NAMED_PIPE = 2
+ INIT_COMMAND = 3
+ READ_DEFAULT_FILE = 4
+ READ_DEFAULT_GROUP = 5
+ SET_CHARSET_DIR = 6
+ SET_CHARSET_NAME = 7
+ OPT_LOCAL_INFILE = 8
+
+ # Server Status
+ SERVER_STATUS_IN_TRANS = 1
+ SERVER_STATUS_AUTOCOMMIT = 2
+
+ # Refresh parameter
+ REFRESH_GRANT = 1
+ REFRESH_LOG = 2
+ REFRESH_TABLES = 4
+ REFRESH_HOSTS = 8
+ REFRESH_STATUS = 16
+ REFRESH_THREADS = 32
+ REFRESH_SLAVE = 64
+ REFRESH_MASTER = 128
+
+ def initialize(*args)
+ @client_flag = 0
+ @max_allowed_packet = MAX_ALLOWED_PACKET
+ @query_with_result = true
+ @status = :STATUS_READY
+ if args[0] != :INIT then
+ real_connect(*args)
+ end
+ end
+
+ def real_connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
+ @server_status = SERVER_STATUS_AUTOCOMMIT
+ if (host == nil or host == "localhost") and defined? UNIXSocket then
+ unix_socket = socket || ENV["MYSQL_UNIX_PORT"] || MYSQL_UNIX_ADDR
+ sock = UNIXSocket::new(unix_socket)
+ @host_info = Error::err(Error::CR_LOCALHOST_CONNECTION)
+ @unix_socket = unix_socket
+ else
+ sock = TCPSocket::new(host, port||ENV["MYSQL_TCP_PORT"]||(Socket::getservbyname("mysql","tcp") rescue MYSQL_PORT))
+ @host_info = sprintf Error::err(Error::CR_TCP_CONNECTION), host
+ end
+ @host = host ? host.dup : nil
+ sock.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true
+ @net = Net::new sock
+
+ a = read
+ @protocol_version = a.slice!(0)
+ @server_version, a = a.split(/\0/,2)
+ @thread_id, @scramble_buff = a.slice!(0,13).unpack("La8")
+ if a.size >= 2 then
+ @server_capabilities, = a.slice!(0,2).unpack("v")
+ end
+ if a.size >= 16 then
+ @server_language, @server_status = a.slice!(0,3).unpack("cv")
+ end
+
+ flag = 0 if flag == nil
+ flag |= @client_flag | CLIENT_CAPABILITIES
+ flag |= CLIENT_CONNECT_WITH_DB if db
+
+ @pre_411 = (0 == @server_capabilities & PROTO_AUTH41)
+ if @pre_411
+ data = Net::int2str(flag)+Net::int3str(@max_allowed_packet)+
+ (user||"")+"\0"+
+ scramble(passwd, @scramble_buff, @protocol_version==9)
+ else
+ dummy, @salt2 = a.unpack("a13a12")
+ @scramble_buff += @salt2
+ flag |= PROTO_AUTH41
+ data = Net::int4str(flag) + Net::int4str(@max_allowed_packet) +
+ ([8] + Array.new(23, 0)).pack("c24") + (user||"")+"\0"+
+ scramble41(passwd, @scramble_buff)
+ end
+
+ if db and @server_capabilities & CLIENT_CONNECT_WITH_DB != 0
+ data << "\0" if @pre_411
+ data << db
+ @db = db.dup
+ end
+ write data
+ pkt = read
+ handle_auth_fallback(pkt, passwd)
+ ObjectSpace.define_finalizer(self, Mysql.finalizer(@net))
+ self
+ end
+ alias :connect :real_connect
+
+ def handle_auth_fallback(pkt, passwd)
+ # A packet like this means that we need to send an old-format password
+ if pkt.size == 1 and pkt[0] == 254 and
+ @server_capabilities & CLIENT_SECURE_CONNECTION != 0 then
+ data = scramble(passwd, @scramble_buff, @protocol_version == 9)
+ write data + "\0"
+ read
+ end
+ end
+
+ def escape_string(str)
+ Mysql::escape_string str
+ end
+ alias :quote :escape_string
+
+ def get_client_info()
+ VERSION
+ end
+ alias :client_info :get_client_info
+
+ def options(option, arg=nil)
+ if option == OPT_LOCAL_INFILE then
+ if arg == false or arg == 0 then
+ @client_flag &= ~CLIENT_LOCAL_FILES
+ else
+ @client_flag |= CLIENT_LOCAL_FILES
+ end
+ else
+ raise "not implemented"
+ end
+ end
+
+ def real_query(query)
+ command COM_QUERY, query, true
+ read_query_result
+ self
+ end
+
+ def use_result()
+ if @status != :STATUS_GET_RESULT then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ res = Result::new self, @fields, @field_count
+ @status = :STATUS_USE_RESULT
+ res
+ end
+
+ def store_result()
+ if @status != :STATUS_GET_RESULT then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ @status = :STATUS_READY
+ data = read_rows @field_count
+ res = Result::new self, @fields, @field_count, data
+ @fields = nil
+ @affected_rows = data.length
+ res
+ end
+
+ def change_user(user="", passwd="", db="")
+ if @pre_411
+ data = user+"\0"+scramble(passwd, @scramble_buff, @protocol_version==9)+"\0"+db
+ else
+ data = user+"\0"+scramble41(passwd, @scramble_buff)+db
+ end
+ pkt = command COM_CHANGE_USER, data
+ handle_auth_fallback(pkt, passwd)
+ @user = user
+ @passwd = passwd
+ @db = db
+ end
+
+ def character_set_name()
+ raise "not implemented"
+ end
+
+ def close()
+ @status = :STATUS_READY
+ command COM_QUIT, nil, true
+ @net.close
+ self
+ end
+
+ def create_db(db)
+ command COM_CREATE_DB, db
+ self
+ end
+
+ def drop_db(db)
+ command COM_DROP_DB, db
+ self
+ end
+
+ def dump_debug_info()
+ command COM_DEBUG
+ self
+ end
+
+ def get_host_info()
+ @host_info
+ end
+ alias :host_info :get_host_info
+
+ def get_proto_info()
+ @protocol_version
+ end
+ alias :proto_info :get_proto_info
+
+ def get_server_info()
+ @server_version
+ end
+ alias :server_info :get_server_info
+
+ def kill(id)
+ command COM_PROCESS_KILL, Net::int4str(id)
+ self
+ end
+
+ def list_dbs(db=nil)
+ real_query "show databases #{db}"
+ @status = :STATUS_READY
+ read_rows(1).flatten
+ end
+
+ def list_fields(table, field=nil)
+ command COM_FIELD_LIST, "#{table}\0#{field}", true
+ if @pre_411
+ f = read_rows 6
+ else
+ f = read_rows 7
+ end
+ fields = unpack_fields(f, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ res = Result::new self, fields, f.length
+ res.eof = true
+ res
+ end
+
+ def list_processes()
+ data = command COM_PROCESS_INFO
+ @field_count = get_length data
+ if @pre_411
+ fields = read_rows 5
+ else
+ fields = read_rows 7
+ end
+ @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ @status = :STATUS_GET_RESULT
+ store_result
+ end
+
+ def list_tables(table=nil)
+ real_query "show tables #{table}"
+ @status = :STATUS_READY
+ read_rows(1).flatten
+ end
+
+ def ping()
+ command COM_PING
+ self
+ end
+
+ def query(query)
+ real_query query
+ if not @query_with_result then
+ return self
+ end
+ if @field_count == 0 then
+ return nil
+ end
+ store_result
+ end
+
+ def refresh(r)
+ command COM_REFRESH, r.chr
+ self
+ end
+
+ def reload()
+ refresh REFRESH_GRANT
+ self
+ end
+
+ def select_db(db)
+ command COM_INIT_DB, db
+ @db = db
+ self
+ end
+
+ def shutdown()
+ command COM_SHUTDOWN
+ self
+ end
+
+ def stat()
+ command COM_STATISTICS
+ end
+
+ attr_reader :info, :insert_id, :affected_rows, :field_count, :thread_id
+ attr_accessor :query_with_result, :status
+
+ def read_one_row(field_count)
+ data = read
+ if data[0] == 254 and data.length == 1 ## EOF
+ return
+ elsif data[0] == 254 and data.length == 5
+ return
+ end
+ rec = []
+ field_count.times do
+ len = get_length data
+ if len == nil then
+ rec << len
+ else
+ rec << data.slice!(0,len)
+ end
+ end
+ rec
+ end
+
+ def skip_result()
+ if @status == :STATUS_USE_RESULT then
+ loop do
+ data = read
+ break if data[0] == 254 and data.length == 1
+ end
+ @status = :STATUS_READY
+ end
+ end
+
+ def inspect()
+ "#<#{self.class}>"
+ end
+
+ private
+
+ def read_query_result()
+ data = read
+ @field_count = get_length(data)
+ if @field_count == nil then # LOAD DATA LOCAL INFILE
+ File::open(data) do |f|
+ write f.read
+ end
+ write "" # mark EOF
+ data = read
+ @field_count = get_length(data)
+ end
+ if @field_count == 0 then
+ @affected_rows = get_length(data, true)
+ @insert_id = get_length(data, true)
+ if @server_capabilities & CLIENT_TRANSACTIONS != 0 then
+ a = data.slice!(0,2)
+ @server_status = a[0]+a[1]*256
+ end
+ if data.size > 0 and get_length(data) then
+ @info = data
+ end
+ else
+ @extra_info = get_length(data, true)
+ if @pre_411
+ fields = read_rows(5)
+ else
+ fields = read_rows(7)
+ end
+ @fields = unpack_fields(fields, @server_capabilities & CLIENT_LONG_FLAG != 0)
+ @status = :STATUS_GET_RESULT
+ end
+ self
+ end
+
+ def unpack_fields(data, long_flag_protocol)
+ ret = []
+ data.each do |f|
+ if @pre_411
+ table = org_table = f[0]
+ name = f[1]
+ length = f[2][0]+f[2][1]*256+f[2][2]*256*256
+ type = f[3][0]
+ if long_flag_protocol then
+ flags = f[4][0]+f[4][1]*256
+ decimals = f[4][2]
+ else
+ flags = f[4][0]
+ decimals = f[4][1]
+ end
+ def_value = f[5]
+ max_length = 0
+ else
+ catalog = f[0]
+ db = f[1]
+ table = f[2]
+ org_table = f[3]
+ name = f[4]
+ org_name = f[5]
+ length = f[6][2]+f[6][3]*256+f[6][4]*256*256
+ type = f[6][6]
+ flags = f[6][7]+f[6][8]*256
+ decimals = f[6][9]
+ def_value = ""
+ max_length = 0
+ end
+ ret << Field::new(table, org_table, name, length, type, flags, decimals, def_value, max_length)
+ end
+ ret
+ end
+
+ def read_rows(field_count)
+ ret = []
+ while rec = read_one_row(field_count) do
+ ret << rec
+ end
+ ret
+ end
+
+ def get_length(data, longlong=nil)
+ return if data.length == 0
+ c = data.slice!(0)
+ case c
+ when 251
+ return nil
+ when 252
+ a = data.slice!(0,2)
+ return a[0]+a[1]*256
+ when 253
+ a = data.slice!(0,3)
+ return a[0]+a[1]*256+a[2]*256**2
+ when 254
+ a = data.slice!(0,8)
+ if longlong then
+ return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3+
+ a[4]*256**4+a[5]*256**5+a[6]*256**6+a[7]*256**7
+ else
+ return a[0]+a[1]*256+a[2]*256**2+a[3]*256**3
+ end
+ else
+ c
+ end
+ end
+
+ def command(cmd, arg=nil, skip_check=nil)
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ if @status != :STATUS_READY then
+ error Error::CR_COMMANDS_OUT_OF_SYNC
+ end
+ @net.clear
+ write cmd.chr+(arg||"")
+ read unless skip_check
+ end
+
+ def read()
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ a = @net.read
+ if a[0] == 255 then
+ if a.length > 3 then
+ @errno = a[1]+a[2]*256
+ @error = a[3 .. -1]
+ else
+ @errno = Error::CR_UNKNOWN_ERROR
+ @error = Error::err @errno
+ end
+ raise Error::new(@errno, @error)
+ end
+ a
+ end
+
+ def write(arg)
+ unless @net then
+ error Error::CR_SERVER_GONE_ERROR
+ end
+ @net.write arg
+ end
+
+ def hash_password(password)
+ nr = 1345345333
+ add = 7
+ nr2 = 0x12345671
+ password.each_byte do |i|
+ next if i == 0x20 or i == 9
+ nr ^= (((nr & 63) + add) * i) + (nr << 8)
+ nr2 += (nr2 << 8) ^ nr
+ add += i
+ end
+ [nr & ((1 << 31) - 1), nr2 & ((1 << 31) - 1)]
+ end
+
+ def scramble(password, message, old_ver)
+ return "" if password == nil or password == ""
+ raise "old version password is not implemented" if old_ver
+ hash_pass = hash_password password
+ hash_message = hash_password message.slice(0,SCRAMBLE_LENGTH_323)
+ rnd = Random::new hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1]
+ to = []
+ 1.upto(SCRAMBLE_LENGTH_323) do
+ to << ((rnd.rnd*31)+64).floor
+ end
+ extra = (rnd.rnd*31).floor
+ to.map! do |t| (t ^ extra).chr end
+ to.join
+ end
+
+ def scramble41(password, message)
+ return 0x00.chr if password.nil? or password.empty?
+ buf = [0x14]
+ s1 = Digest::SHA1.digest(password)
+ s2 = Digest::SHA1.digest(s1)
+ x = Digest::SHA1.digest(message + s2)
+ (0..s1.length - 1).each {|i| buf.push(s1[i] ^ x[i])}
+ buf.pack("C*")
+ end
+
+ def error(errno)
+ @errno = errno
+ @error = Error::err errno
+ raise Error::new(@errno, @error)
+ end
+
+ class Result
+ def initialize(mysql, fields, field_count, data=nil)
+ @handle = mysql
+ @fields = fields
+ @field_count = field_count
+ @data = data
+ @current_field = 0
+ @current_row = 0
+ @eof = false
+ @row_count = 0
+ end
+ attr_accessor :eof
+
+ def data_seek(n)
+ @current_row = n
+ end
+
+ def fetch_field()
+ return if @current_field >= @field_count
+ f = @fields[@current_field]
+ @current_field += 1
+ f
+ end
+
+ def fetch_fields()
+ @fields
+ end
+
+ def fetch_field_direct(n)
+ @fields[n]
+ end
+
+ def fetch_lengths()
+ @data ? @data[@current_row].map{|i| i ? i.length : 0} : @lengths
+ end
+
+ def fetch_row()
+ if @data then
+ if @current_row >= @data.length then
+ @handle.status = :STATUS_READY
+ return
+ end
+ ret = @data[@current_row]
+ @current_row += 1
+ else
+ return if @eof
+ ret = @handle.read_one_row @field_count
+ if ret == nil then
+ @eof = true
+ return
+ end
+ @lengths = ret.map{|i| i ? i.length : 0}
+ @row_count += 1
+ end
+ ret
+ end
+
+ def fetch_hash(with_table=nil)
+ row = fetch_row
+ return if row == nil
+ hash = {}
+ @fields.each_index do |i|
+ f = with_table ? @fields[i].table+"."+@fields[i].name : @fields[i].name
+ hash[f] = row[i]
+ end
+ hash
+ end
+
+ def field_seek(n)
+ @current_field = n
+ end
+
+ def field_tell()
+ @current_field
+ end
+
+ def free()
+ @handle.skip_result
+ @handle = @fields = @data = nil
+ end
+
+ def num_fields()
+ @field_count
+ end
+
+ def num_rows()
+ @data ? @data.length : @row_count
+ end
+
+ def row_seek(n)
+ @current_row = n
+ end
+
+ def row_tell()
+ @current_row
+ end
+
+ def each()
+ while row = fetch_row do
+ yield row
+ end
+ end
+
+ def each_hash(with_table=nil)
+ while hash = fetch_hash(with_table) do
+ yield hash
+ end
+ end
+
+ def inspect()
+ "#<#{self.class}>"
+ end
+
+ end
+
+ class Field
+ # Field type
+ TYPE_DECIMAL = 0
+ TYPE_TINY = 1
+ TYPE_SHORT = 2
+ TYPE_LONG = 3
+ TYPE_FLOAT = 4
+ TYPE_DOUBLE = 5
+ TYPE_NULL = 6
+ TYPE_TIMESTAMP = 7
+ TYPE_LONGLONG = 8
+ TYPE_INT24 = 9
+ TYPE_DATE = 10
+ TYPE_TIME = 11
+ TYPE_DATETIME = 12
+ TYPE_YEAR = 13
+ TYPE_NEWDATE = 14
+ TYPE_ENUM = 247
+ TYPE_SET = 248
+ TYPE_TINY_BLOB = 249
+ TYPE_MEDIUM_BLOB = 250
+ TYPE_LONG_BLOB = 251
+ TYPE_BLOB = 252
+ TYPE_VAR_STRING = 253
+ TYPE_STRING = 254
+ TYPE_GEOMETRY = 255
+ TYPE_CHAR = TYPE_TINY
+ TYPE_INTERVAL = TYPE_ENUM
+
+ # Flag
+ NOT_NULL_FLAG = 1
+ PRI_KEY_FLAG = 2
+ UNIQUE_KEY_FLAG = 4
+ MULTIPLE_KEY_FLAG = 8
+ BLOB_FLAG = 16
+ UNSIGNED_FLAG = 32
+ ZEROFILL_FLAG = 64
+ BINARY_FLAG = 128
+ ENUM_FLAG = 256
+ AUTO_INCREMENT_FLAG = 512
+ TIMESTAMP_FLAG = 1024
+ SET_FLAG = 2048
+ NUM_FLAG = 32768
+ PART_KEY_FLAG = 16384
+ GROUP_FLAG = 32768
+ UNIQUE_FLAG = 65536
+
+ def initialize(table, org_table, name, length, type, flags, decimals, def_value, max_length)
+ @table = table
+ @org_table = org_table
+ @name = name
+ @length = length
+ @type = type
+ @flags = flags
+ @decimals = decimals
+ @def = def_value
+ @max_length = max_length
+ if (type <= TYPE_INT24 and (type != TYPE_TIMESTAMP or length == 14 or length == 8)) or type == TYPE_YEAR then
+ @flags |= NUM_FLAG
+ end
+ end
+ attr_reader :table, :org_table, :name, :length, :type, :flags, :decimals, :def, :max_length
+
+ def inspect()
+ "#<#{self.class}:#{@name}>"
+ end
+ end
+
+ class Error < StandardError
+ # Server Error
+ ER_HASHCHK = 1000
+ ER_NISAMCHK = 1001
+ ER_NO = 1002
+ ER_YES = 1003
+ ER_CANT_CREATE_FILE = 1004
+ ER_CANT_CREATE_TABLE = 1005
+ ER_CANT_CREATE_DB = 1006
+ ER_DB_CREATE_EXISTS = 1007
+ ER_DB_DROP_EXISTS = 1008
+ ER_DB_DROP_DELETE = 1009
+ ER_DB_DROP_RMDIR = 1010
+ ER_CANT_DELETE_FILE = 1011
+ ER_CANT_FIND_SYSTEM_REC = 1012
+ ER_CANT_GET_STAT = 1013
+ ER_CANT_GET_WD = 1014
+ ER_CANT_LOCK = 1015
+ ER_CANT_OPEN_FILE = 1016
+ ER_FILE_NOT_FOUND = 1017
+ ER_CANT_READ_DIR = 1018
+ ER_CANT_SET_WD = 1019
+ ER_CHECKREAD = 1020
+ ER_DISK_FULL = 1021
+ ER_DUP_KEY = 1022
+ ER_ERROR_ON_CLOSE = 1023
+ ER_ERROR_ON_READ = 1024
+ ER_ERROR_ON_RENAME = 1025
+ ER_ERROR_ON_WRITE = 1026
+ ER_FILE_USED = 1027
+ ER_FILSORT_ABORT = 1028
+ ER_FORM_NOT_FOUND = 1029
+ ER_GET_ERRNO = 1030
+ ER_ILLEGAL_HA = 1031
+ ER_KEY_NOT_FOUND = 1032
+ ER_NOT_FORM_FILE = 1033
+ ER_NOT_KEYFILE = 1034
+ ER_OLD_KEYFILE = 1035
+ ER_OPEN_AS_READONLY = 1036
+ ER_OUTOFMEMORY = 1037
+ ER_OUT_OF_SORTMEMORY = 1038
+ ER_UNEXPECTED_EOF = 1039
+ ER_CON_COUNT_ERROR = 1040
+ ER_OUT_OF_RESOURCES = 1041
+ ER_BAD_HOST_ERROR = 1042
+ ER_HANDSHAKE_ERROR = 1043
+ ER_DBACCESS_DENIED_ERROR = 1044
+ ER_ACCESS_DENIED_ERROR = 1045
+ ER_NO_DB_ERROR = 1046
+ ER_UNKNOWN_COM_ERROR = 1047
+ ER_BAD_NULL_ERROR = 1048
+ ER_BAD_DB_ERROR = 1049
+ ER_TABLE_EXISTS_ERROR = 1050
+ ER_BAD_TABLE_ERROR = 1051
+ ER_NON_UNIQ_ERROR = 1052
+ ER_SERVER_SHUTDOWN = 1053
+ ER_BAD_FIELD_ERROR = 1054
+ ER_WRONG_FIELD_WITH_GROUP = 1055
+ ER_WRONG_GROUP_FIELD = 1056
+ ER_WRONG_SUM_SELECT = 1057
+ ER_WRONG_VALUE_COUNT = 1058
+ ER_TOO_LONG_IDENT = 1059
+ ER_DUP_FIELDNAME = 1060
+ ER_DUP_KEYNAME = 1061
+ ER_DUP_ENTRY = 1062
+ ER_WRONG_FIELD_SPEC = 1063
+ ER_PARSE_ERROR = 1064
+ ER_EMPTY_QUERY = 1065
+ ER_NONUNIQ_TABLE = 1066
+ ER_INVALID_DEFAULT = 1067
+ ER_MULTIPLE_PRI_KEY = 1068
+ ER_TOO_MANY_KEYS = 1069
+ ER_TOO_MANY_KEY_PARTS = 1070
+ ER_TOO_LONG_KEY = 1071
+ ER_KEY_COLUMN_DOES_NOT_EXITS = 1072
+ ER_BLOB_USED_AS_KEY = 1073
+ ER_TOO_BIG_FIELDLENGTH = 1074
+ ER_WRONG_AUTO_KEY = 1075
+ ER_READY = 1076
+ ER_NORMAL_SHUTDOWN = 1077
+ ER_GOT_SIGNAL = 1078
+ ER_SHUTDOWN_COMPLETE = 1079
+ ER_FORCING_CLOSE = 1080
+ ER_IPSOCK_ERROR = 1081
+ ER_NO_SUCH_INDEX = 1082
+ ER_WRONG_FIELD_TERMINATORS = 1083
+ ER_BLOBS_AND_NO_TERMINATED = 1084
+ ER_TEXTFILE_NOT_READABLE = 1085
+ ER_FILE_EXISTS_ERROR = 1086
+ ER_LOAD_INFO = 1087
+ ER_ALTER_INFO = 1088
+ ER_WRONG_SUB_KEY = 1089
+ ER_CANT_REMOVE_ALL_FIELDS = 1090
+ ER_CANT_DROP_FIELD_OR_KEY = 1091
+ ER_INSERT_INFO = 1092
+ ER_INSERT_TABLE_USED = 1093
+ ER_NO_SUCH_THREAD = 1094
+ ER_KILL_DENIED_ERROR = 1095
+ ER_NO_TABLES_USED = 1096
+ ER_TOO_BIG_SET = 1097
+ ER_NO_UNIQUE_LOGFILE = 1098
+ ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099
+ ER_TABLE_NOT_LOCKED = 1100
+ ER_BLOB_CANT_HAVE_DEFAULT = 1101
+ ER_WRONG_DB_NAME = 1102
+ ER_WRONG_TABLE_NAME = 1103
+ ER_TOO_BIG_SELECT = 1104
+ ER_UNKNOWN_ERROR = 1105
+ ER_UNKNOWN_PROCEDURE = 1106
+ ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107
+ ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108
+ ER_UNKNOWN_TABLE = 1109
+ ER_FIELD_SPECIFIED_TWICE = 1110
+ ER_INVALID_GROUP_FUNC_USE = 1111
+ ER_UNSUPPORTED_EXTENSION = 1112
+ ER_TABLE_MUST_HAVE_COLUMNS = 1113
+ ER_RECORD_FILE_FULL = 1114
+ ER_UNKNOWN_CHARACTER_SET = 1115
+ ER_TOO_MANY_TABLES = 1116
+ ER_TOO_MANY_FIELDS = 1117
+ ER_TOO_BIG_ROWSIZE = 1118
+ ER_STACK_OVERRUN = 1119
+ ER_WRONG_OUTER_JOIN = 1120
+ ER_NULL_COLUMN_IN_INDEX = 1121
+ ER_CANT_FIND_UDF = 1122
+ ER_CANT_INITIALIZE_UDF = 1123
+ ER_UDF_NO_PATHS = 1124
+ ER_UDF_EXISTS = 1125
+ ER_CANT_OPEN_LIBRARY = 1126
+ ER_CANT_FIND_DL_ENTRY = 1127
+ ER_FUNCTION_NOT_DEFINED = 1128
+ ER_HOST_IS_BLOCKED = 1129
+ ER_HOST_NOT_PRIVILEGED = 1130
+ ER_PASSWORD_ANONYMOUS_USER = 1131
+ ER_PASSWORD_NOT_ALLOWED = 1132
+ ER_PASSWORD_NO_MATCH = 1133
+ ER_UPDATE_INFO = 1134
+ ER_CANT_CREATE_THREAD = 1135
+ ER_WRONG_VALUE_COUNT_ON_ROW = 1136
+ ER_CANT_REOPEN_TABLE = 1137
+ ER_INVALID_USE_OF_NULL = 1138
+ ER_REGEXP_ERROR = 1139
+ ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140
+ ER_NONEXISTING_GRANT = 1141
+ ER_TABLEACCESS_DENIED_ERROR = 1142
+ ER_COLUMNACCESS_DENIED_ERROR = 1143
+ ER_ILLEGAL_GRANT_FOR_TABLE = 1144
+ ER_GRANT_WRONG_HOST_OR_USER = 1145
+ ER_NO_SUCH_TABLE = 1146
+ ER_NONEXISTING_TABLE_GRANT = 1147
+ ER_NOT_ALLOWED_COMMAND = 1148
+ ER_SYNTAX_ERROR = 1149
+ ER_DELAYED_CANT_CHANGE_LOCK = 1150
+ ER_TOO_MANY_DELAYED_THREADS = 1151
+ ER_ABORTING_CONNECTION = 1152
+ ER_NET_PACKET_TOO_LARGE = 1153
+ ER_NET_READ_ERROR_FROM_PIPE = 1154
+ ER_NET_FCNTL_ERROR = 1155
+ ER_NET_PACKETS_OUT_OF_ORDER = 1156
+ ER_NET_UNCOMPRESS_ERROR = 1157
+ ER_NET_READ_ERROR = 1158
+ ER_NET_READ_INTERRUPTED = 1159
+ ER_NET_ERROR_ON_WRITE = 1160
+ ER_NET_WRITE_INTERRUPTED = 1161
+ ER_TOO_LONG_STRING = 1162
+ ER_TABLE_CANT_HANDLE_BLOB = 1163
+ ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164
+ ER_DELAYED_INSERT_TABLE_LOCKED = 1165
+ ER_WRONG_COLUMN_NAME = 1166
+ ER_WRONG_KEY_COLUMN = 1167
+ ER_WRONG_MRG_TABLE = 1168
+ ER_DUP_UNIQUE = 1169
+ ER_BLOB_KEY_WITHOUT_LENGTH = 1170
+ ER_PRIMARY_CANT_HAVE_NULL = 1171
+ ER_TOO_MANY_ROWS = 1172
+ ER_REQUIRES_PRIMARY_KEY = 1173
+ ER_NO_RAID_COMPILED = 1174
+ ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175
+ ER_KEY_DOES_NOT_EXITS = 1176
+ ER_CHECK_NO_SUCH_TABLE = 1177
+ ER_CHECK_NOT_IMPLEMENTED = 1178
+ ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179
+ ER_ERROR_DURING_COMMIT = 1180
+ ER_ERROR_DURING_ROLLBACK = 1181
+ ER_ERROR_DURING_FLUSH_LOGS = 1182
+ ER_ERROR_DURING_CHECKPOINT = 1183
+ ER_NEW_ABORTING_CONNECTION = 1184
+ ER_DUMP_NOT_IMPLEMENTED = 1185
+ ER_FLUSH_MASTER_BINLOG_CLOSED = 1186
+ ER_INDEX_REBUILD = 1187
+ ER_MASTER = 1188
+ ER_MASTER_NET_READ = 1189
+ ER_MASTER_NET_WRITE = 1190
+ ER_FT_MATCHING_KEY_NOT_FOUND = 1191
+ ER_LOCK_OR_ACTIVE_TRANSACTION = 1192
+ ER_UNKNOWN_SYSTEM_VARIABLE = 1193
+ ER_CRASHED_ON_USAGE = 1194
+ ER_CRASHED_ON_REPAIR = 1195
+ ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196
+ ER_TRANS_CACHE_FULL = 1197
+ ER_SLAVE_MUST_STOP = 1198
+ ER_SLAVE_NOT_RUNNING = 1199
+ ER_BAD_SLAVE = 1200
+ ER_MASTER_INFO = 1201
+ ER_SLAVE_THREAD = 1202
+ ER_TOO_MANY_USER_CONNECTIONS = 1203
+ ER_SET_CONSTANTS_ONLY = 1204
+ ER_LOCK_WAIT_TIMEOUT = 1205
+ ER_LOCK_TABLE_FULL = 1206
+ ER_READ_ONLY_TRANSACTION = 1207
+ ER_DROP_DB_WITH_READ_LOCK = 1208
+ ER_CREATE_DB_WITH_READ_LOCK = 1209
+ ER_WRONG_ARGUMENTS = 1210
+ ER_NO_PERMISSION_TO_CREATE_USER = 1211
+ ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212
+ ER_LOCK_DEADLOCK = 1213
+ ER_TABLE_CANT_HANDLE_FULLTEXT = 1214
+ ER_CANNOT_ADD_FOREIGN = 1215
+ ER_NO_REFERENCED_ROW = 1216
+ ER_ROW_IS_REFERENCED = 1217
+ ER_CONNECT_TO_MASTER = 1218
+ ER_QUERY_ON_MASTER = 1219
+ ER_ERROR_WHEN_EXECUTING_COMMAND = 1220
+ ER_WRONG_USAGE = 1221
+ ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222
+ ER_CANT_UPDATE_WITH_READLOCK = 1223
+ ER_MIXING_NOT_ALLOWED = 1224
+ ER_DUP_ARGUMENT = 1225
+ ER_USER_LIMIT_REACHED = 1226
+ ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227
+ ER_LOCAL_VARIABLE = 1228
+ ER_GLOBAL_VARIABLE = 1229
+ ER_NO_DEFAULT = 1230
+ ER_WRONG_VALUE_FOR_VAR = 1231
+ ER_WRONG_TYPE_FOR_VAR = 1232
+ ER_VAR_CANT_BE_READ = 1233
+ ER_CANT_USE_OPTION_HERE = 1234
+ ER_NOT_SUPPORTED_YET = 1235
+ ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236
+ ER_SLAVE_IGNORED_TABLE = 1237
+ ER_ERROR_MESSAGES = 238
+
+ # Client Error
+ CR_MIN_ERROR = 2000
+ CR_MAX_ERROR = 2999
+ CR_UNKNOWN_ERROR = 2000
+ CR_SOCKET_CREATE_ERROR = 2001
+ CR_CONNECTION_ERROR = 2002
+ CR_CONN_HOST_ERROR = 2003
+ CR_IPSOCK_ERROR = 2004
+ CR_UNKNOWN_HOST = 2005
+ CR_SERVER_GONE_ERROR = 2006
+ CR_VERSION_ERROR = 2007
+ CR_OUT_OF_MEMORY = 2008
+ CR_WRONG_HOST_INFO = 2009
+ CR_LOCALHOST_CONNECTION = 2010
+ CR_TCP_CONNECTION = 2011
+ CR_SERVER_HANDSHAKE_ERR = 2012
+ CR_SERVER_LOST = 2013
+ CR_COMMANDS_OUT_OF_SYNC = 2014
+ CR_NAMEDPIPE_CONNECTION = 2015
+ CR_NAMEDPIPEWAIT_ERROR = 2016
+ CR_NAMEDPIPEOPEN_ERROR = 2017
+ CR_NAMEDPIPESETSTATE_ERROR = 2018
+ CR_CANT_READ_CHARSET = 2019
+ CR_NET_PACKET_TOO_LARGE = 2020
+ CR_EMBEDDED_CONNECTION = 2021
+ CR_PROBE_SLAVE_STATUS = 2022
+ CR_PROBE_SLAVE_HOSTS = 2023
+ CR_PROBE_SLAVE_CONNECT = 2024
+ CR_PROBE_MASTER_CONNECT = 2025
+ CR_SSL_CONNECTION_ERROR = 2026
+ CR_MALFORMED_PACKET = 2027
+
+ CLIENT_ERRORS = [
+ "Unknown MySQL error",
+ "Can't create UNIX socket (%d)",
+ "Can't connect to local MySQL server through socket '%-.64s' (%d)",
+ "Can't connect to MySQL server on '%-.64s' (%d)",
+ "Can't create TCP/IP socket (%d)",
+ "Unknown MySQL Server Host '%-.64s' (%d)",
+ "MySQL server has gone away",
+ "Protocol mismatch. Server Version = %d Client Version = %d",
+ "MySQL client run out of memory",
+ "Wrong host info",
+ "Localhost via UNIX socket",
+ "%-.64s via TCP/IP",
+ "Error in server handshake",
+ "Lost connection to MySQL server during query",
+ "Commands out of sync; You can't run this command now",
+ "%-.64s via named pipe",
+ "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't open named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%lu)",
+ "Can't initialize character set %-.64s (path: %-.64s)",
+ "Got packet bigger than 'max_allowed_packet'",
+ "Embedded server",
+ "Error on SHOW SLAVE STATUS:",
+ "Error on SHOW SLAVE HOSTS:",
+ "Error connecting to slave:",
+ "Error connecting to master:",
+ "SSL connection error",
+ "Malformed packet"
+ ]
+
+ def initialize(errno, error)
+ @errno = errno
+ @error = error
+ super error
+ end
+ attr_reader :errno, :error
+
+ def Error::err(errno)
+ CLIENT_ERRORS[errno - Error::CR_MIN_ERROR]
+ end
+ end
+
+ class Net
+ def initialize(sock)
+ @sock = sock
+ @pkt_nr = 0
+ end
+
+ def clear()
+ @pkt_nr = 0
+ end
+
+ def read()
+ buf = []
+ len = nil
+ @sock.sync = false
+ while len == nil or len == MAX_PACKET_LENGTH do
+ a = @sock.read(4)
+ len = a[0]+a[1]*256+a[2]*256*256
+ pkt_nr = a[3]
+ if @pkt_nr != pkt_nr then
+ raise "Packets out of order: #{@pkt_nr}<>#{pkt_nr}"
+ end
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ buf << @sock.read(len)
+ end
+ @sock.sync = true
+ buf.join
+ rescue
+ errno = Error::CR_SERVER_LOST
+ raise Error::new(errno, Error::err(errno))
+ end
+
+ def write(data)
+ if data.is_a? Array then
+ data = data.join
+ end
+ @sock.sync = false
+ ptr = 0
+ while data.length >= MAX_PACKET_LENGTH do
+ @sock.write Net::int3str(MAX_PACKET_LENGTH)+@pkt_nr.chr+data[ptr, MAX_PACKET_LENGTH]
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ ptr += MAX_PACKET_LENGTH
+ end
+ @sock.write Net::int3str(data.length-ptr)+@pkt_nr.chr+data[ptr .. -1]
+ @pkt_nr = @pkt_nr + 1 & 0xff
+ @sock.sync = true
+ @sock.flush
+ rescue
+ errno = Error::CR_SERVER_LOST
+ raise Error::new(errno, Error::err(errno))
+ end
+
+ def close()
+ @sock.close
+ end
+
+ def Net::int2str(n)
+ [n].pack("v")
+ end
+
+ def Net::int3str(n)
+ [n%256, n>>8].pack("cv")
+ end
+
+ def Net::int4str(n)
+ [n].pack("V")
+ end
+
+ end
+
+ class Random
+ def initialize(seed1, seed2)
+ @max_value = 0x3FFFFFFF
+ @seed1 = seed1 % @max_value
+ @seed2 = seed2 % @max_value
+ end
+
+ def rnd()
+ @seed1 = (@seed1*3+@seed2) % @max_value
+ @seed2 = (@seed1+@seed2+33) % @max_value
+ @seed1.to_f / @max_value
+ end
+ end
+
+end
+
+class << Mysql
+ def init()
+ Mysql::new :INIT
+ end
+
+ def real_connect(*args)
+ Mysql::new(*args)
+ end
+ alias :connect :real_connect
+
+ def finalizer(net)
+ proc {
+ net.clear
+ begin
+ net.write(Mysql::COM_QUIT.chr)
+ net.close
+ rescue # Ignore IOError if socket is already closed.
+ end
+ }
+ end
+
+ def escape_string(str)
+ str.gsub(/([\0\n\r\032\'\"\\])/) do
+ case $1
+ when "\0" then "\\0"
+ when "\n" then "\\n"
+ when "\r" then "\\r"
+ when "\032" then "\\Z"
+ else "\\"+$1
+ end
+ end
+ end
+ alias :quote :escape_string
+
+ def get_client_info()
+ Mysql::VERSION
+ end
+ alias :client_info :get_client_info
+
+ def debug(str)
+ raise "not implemented"
+ end
+end
+
+#
+# for compatibility
+#
+
+MysqlRes = Mysql::Result
+MysqlField = Mysql::Field
+MysqlError = Mysql::Error
diff --git a/vendor/rails/activerecord/lib/active_record/version.rb b/vendor/rails/activerecord/lib/active_record/version.rb
new file mode 100644
index 0000000..aaadef9
--- /dev/null
+++ b/vendor/rails/activerecord/lib/active_record/version.rb
@@ -0,0 +1,9 @@
+module ActiveRecord
+ module VERSION #:nodoc:
+ MAJOR = 2
+ MINOR = 1
+ TINY = 0
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+end
diff --git a/vendor/rails/activerecord/lib/activerecord.rb b/vendor/rails/activerecord/lib/activerecord.rb
new file mode 100644
index 0000000..cd62b2a
--- /dev/null
+++ b/vendor/rails/activerecord/lib/activerecord.rb
@@ -0,0 +1 @@
+require 'active_record'
diff --git a/vendor/rails/activerecord/test/assets/example.log b/vendor/rails/activerecord/test/assets/example.log
new file mode 100644
index 0000000..f084369
--- /dev/null
+++ b/vendor/rails/activerecord/test/assets/example.log
@@ -0,0 +1 @@
+# Logfile created on Wed Oct 31 16:05:13 +0000 2007 by logger.rb/1.5.2.9
diff --git a/vendor/rails/activerecord/test/assets/flowers.jpg b/vendor/rails/activerecord/test/assets/flowers.jpg
new file mode 100644
index 0000000..fe9df54
Binary files /dev/null and b/vendor/rails/activerecord/test/assets/flowers.jpg differ
diff --git a/vendor/rails/activerecord/test/cases/aaa_create_tables_test.rb b/vendor/rails/activerecord/test/cases/aaa_create_tables_test.rb
new file mode 100644
index 0000000..3911afa
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/aaa_create_tables_test.rb
@@ -0,0 +1,24 @@
+# The filename begins with "aaa" to ensure this is the first test.
+require "cases/helper"
+
+class AAACreateTablesTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def test_load_schema
+ eval(File.read(SCHEMA_ROOT + "/schema.rb"))
+ if File.exists?(adapter_specific_schema_file)
+ eval(File.read(adapter_specific_schema_file))
+ end
+ assert true
+ end
+
+ def test_drop_and_create_courses_table
+ eval(File.read(SCHEMA_ROOT + "/schema2.rb"))
+ assert true
+ end
+
+ private
+ def adapter_specific_schema_file
+ SCHEMA_ROOT + '/' + ActiveRecord::Base.connection.adapter_name.downcase + '_specific_schema.rb'
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/active_schema_test_mysql.rb b/vendor/rails/activerecord/test/cases/active_schema_test_mysql.rb
new file mode 100644
index 0000000..2a42dc3
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/active_schema_test_mysql.rb
@@ -0,0 +1,95 @@
+require "cases/helper"
+
+class ActiveSchemaTest < ActiveRecord::TestCase
+ def setup
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ alias_method :execute_without_stub, :execute
+ def execute(sql, name = nil) return sql end
+ end
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ remove_method :execute
+ alias_method :execute, :execute_without_stub
+ end
+ end
+
+ def test_drop_table
+ assert_equal "DROP TABLE `people`", drop_table(:people)
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_create_mysql_database_with_encoding
+ assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt)
+ assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'})
+ assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci})
+ end
+ end
+
+ def test_add_column
+ assert_equal "ALTER TABLE `people` ADD `last_name` varchar(255)", add_column(:people, :last_name, :string)
+ end
+
+ def test_add_column_with_limit
+ assert_equal "ALTER TABLE `people` ADD `key` varchar(32)", add_column(:people, :key, :string, :limit => 32)
+ end
+
+ def test_drop_table_with_specific_database
+ assert_equal "DROP TABLE `otherdb`.`people`", drop_table('otherdb.people')
+ end
+
+ def test_add_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ end
+ ActiveRecord::Base.connection.add_timestamps :delete_me
+ assert column_present?('delete_me', 'updated_at', 'datetime')
+ assert column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ def test_remove_timestamps
+ with_real_execute do
+ begin
+ ActiveRecord::Base.connection.create_table :delete_me do |t|
+ t.timestamps
+ end
+ ActiveRecord::Base.connection.remove_timestamps :delete_me
+ assert !column_present?('delete_me', 'updated_at', 'datetime')
+ assert !column_present?('delete_me', 'created_at', 'datetime')
+ ensure
+ ActiveRecord::Base.connection.drop_table :delete_me rescue nil
+ end
+ end
+ end
+
+ private
+ def with_real_execute
+ #we need to actually modify some data, so we make execute point to the original method
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ alias_method :execute_with_stub, :execute
+ alias_method :execute, :execute_without_stub
+ end
+ yield
+ ensure
+ #before finishing, we restore the alias to the mock-up method
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ alias_method :execute, :execute_with_stub
+ end
+ end
+
+
+ def method_missing(method_symbol, *arguments)
+ ActiveRecord::Base.connection.send(method_symbol, *arguments)
+ end
+
+ def column_present?(table_name, column_name, type)
+ results = ActiveRecord::Base.connection.select_all("SHOW FIELDS FROM #{table_name} LIKE '#{column_name}'")
+ results.first && results.first['Type'] == type
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/active_schema_test_postgresql.rb b/vendor/rails/activerecord/test/cases/active_schema_test_postgresql.rb
new file mode 100644
index 0000000..db325e3
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/active_schema_test_postgresql.rb
@@ -0,0 +1,24 @@
+require 'cases/helper'
+
+class PostgresqlActiveSchemaTest < Test::Unit::TestCase
+ def setup
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
+ alias_method :real_execute, :execute
+ def execute(sql, name = nil) sql end
+ end
+ end
+
+ def teardown
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:alias_method, :execute, :real_execute)
+ end
+
+ def test_create_database_with_encoding
+ assert_equal "CREATE DATABASE matt ENCODING = 'utf8'", create_database(:matt)
+ assert_equal "CREATE DATABASE aimonetti ENCODING = 'latin1'", create_database(:aimonetti, :encoding => :latin1)
+ end
+
+ private
+ def method_missing(method_symbol, *arguments)
+ ActiveRecord::Base.connection.send(method_symbol, *arguments)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/adapter_test.rb b/vendor/rails/activerecord/test/cases/adapter_test.rb
new file mode 100644
index 0000000..c77446f
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/adapter_test.rb
@@ -0,0 +1,127 @@
+require "cases/helper"
+
+class AdapterTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_tables
+ tables = @connection.tables
+ assert tables.include?("accounts")
+ assert tables.include?("authors")
+ assert tables.include?("tasks")
+ assert tables.include?("topics")
+ end
+
+ def test_table_exists?
+ assert @connection.table_exists?("accounts")
+ assert !@connection.table_exists?("nonexistingtable")
+ end
+
+ def test_indexes
+ idx_name = "accounts_idx"
+
+ if @connection.respond_to?(:indexes)
+ indexes = @connection.indexes("accounts")
+ assert indexes.empty?
+
+ @connection.add_index :accounts, :firm_id, :name => idx_name
+ indexes = @connection.indexes("accounts")
+ assert_equal "accounts", indexes.first.table
+ # OpenBase does not have the concept of a named index
+ # Indexes are merely properties of columns.
+ assert_equal idx_name, indexes.first.name unless current_adapter?(:OpenBaseAdapter)
+ assert !indexes.first.unique
+ assert_equal ["firm_id"], indexes.first.columns
+ else
+ warn "#{@connection.class} does not respond to #indexes"
+ end
+
+ ensure
+ @connection.remove_index(:accounts, :name => idx_name) rescue nil
+ end
+
+ def test_current_database
+ if @connection.respond_to?(:current_database)
+ assert_equal ENV['ARUNIT_DB_NAME'] || "activerecord_unittest", @connection.current_database
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_charset
+ assert_not_nil @connection.charset
+ assert_not_equal 'character_set_database', @connection.charset
+ assert_equal @connection.show_variable('character_set_database'), @connection.charset
+ end
+
+ def test_collation
+ assert_not_nil @connection.collation
+ assert_not_equal 'collation_database', @connection.collation
+ assert_equal @connection.show_variable('collation_database'), @connection.collation
+ end
+
+ def test_show_nonexistent_variable_returns_nil
+ assert_nil @connection.show_variable('foo_bar_baz')
+ end
+ end
+
+ def test_table_alias
+ def @connection.test_table_alias_length() 10; end
+ class << @connection
+ alias_method :old_table_alias_length, :table_alias_length
+ alias_method :table_alias_length, :test_table_alias_length
+ end
+
+ assert_equal 'posts', @connection.table_alias_for('posts')
+ assert_equal 'posts_comm', @connection.table_alias_for('posts_comments')
+ assert_equal 'dbo_posts', @connection.table_alias_for('dbo.posts')
+
+ class << @connection
+ remove_method :table_alias_length
+ alias_method :table_alias_length, :old_table_alias_length
+ end
+ end
+
+ # test resetting sequences in odd tables in postgreSQL
+ if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!)
+ require 'models/movie'
+ require 'models/subscriber'
+
+ def test_reset_empty_table_with_custom_pk
+ Movie.delete_all
+ Movie.connection.reset_pk_sequence! 'movies'
+ assert_equal 1, Movie.create(:name => 'fight club').id
+ end
+
+ if ActiveRecord::Base.connection.adapter_name != "FrontBase"
+ def test_reset_table_with_non_integer_pk
+ Subscriber.delete_all
+ Subscriber.connection.reset_pk_sequence! 'subscribers'
+ sub = Subscriber.new(:name => 'robert drake')
+ sub.id = 'bob drake'
+ assert_nothing_raised { sub.save! }
+ end
+ end
+ end
+
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas
+ sql_inject = "1 select * from schema"
+ assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject)
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ else
+ assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
+
+ def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas
+ sql_inject = "1, 7 procedure help()"
+ if current_adapter?(:MysqlAdapter)
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ else
+ assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject)
+ assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/adapter_test_sqlserver.rb b/vendor/rails/activerecord/test/cases/adapter_test_sqlserver.rb
new file mode 100644
index 0000000..ea270fb
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/adapter_test_sqlserver.rb
@@ -0,0 +1,95 @@
+require "cases/helper"
+require 'models/default'
+require 'models/post'
+require 'models/task'
+
+class SqlServerAdapterTest < ActiveRecord::TestCase
+ class TableWithRealColumn < ActiveRecord::Base; end
+
+ fixtures :posts, :tasks
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.execute("SET LANGUAGE us_english") rescue nil
+ end
+
+ def test_real_column_has_float_type
+ assert_equal :float, TableWithRealColumn.columns_hash["real_number"].type
+ end
+
+ # SQL Server 2000 has a bug where some unambiguous date formats are not
+ # correctly identified if the session language is set to german
+ def test_date_insertion_when_language_is_german
+ @connection.execute("SET LANGUAGE deutsch")
+
+ assert_nothing_raised do
+ Task.create(:starting => Time.utc(2000, 1, 31, 5, 42, 0), :ending => Date.new(2006, 12, 31))
+ end
+ end
+
+ def test_indexes_with_descending_order
+ # Make sure we have an index with descending order
+ @connection.execute "CREATE INDEX idx_credit_limit ON accounts (credit_limit DESC)" rescue nil
+ assert_equal ["credit_limit"], @connection.indexes('accounts').first.columns
+ ensure
+ @connection.execute "DROP INDEX accounts.idx_credit_limit"
+ end
+
+ def test_execute_without_block_closes_statement
+ assert_all_statements_used_are_closed do
+ @connection.execute("SELECT 1")
+ end
+ end
+
+ def test_execute_with_block_closes_statement
+ assert_all_statements_used_are_closed do
+ @connection.execute("SELECT 1") do |sth|
+ assert !sth.finished?, "Statement should still be alive within block"
+ end
+ end
+ end
+
+ def test_insert_with_identity_closes_statement
+ assert_all_statements_used_are_closed do
+ @connection.insert("INSERT INTO accounts ([id], [firm_id],[credit_limit]) values (999, 1, 50)")
+ end
+ end
+
+ def test_insert_without_identity_closes_statement
+ assert_all_statements_used_are_closed do
+ @connection.insert("INSERT INTO accounts ([firm_id],[credit_limit]) values (1, 50)")
+ end
+ end
+
+ def test_active_closes_statement
+ assert_all_statements_used_are_closed do
+ @connection.active?
+ end
+ end
+
+ def assert_all_statements_used_are_closed(&block)
+ existing_handles = []
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| existing_handles << handle}
+ GC.disable
+
+ yield
+
+ used_handles = []
+ ObjectSpace.each_object(DBI::StatementHandle) {|handle| used_handles << handle unless existing_handles.include? handle}
+
+ assert_block "No statements were used within given block" do
+ used_handles.size > 0
+ end
+
+ ObjectSpace.each_object(DBI::StatementHandle) do |handle|
+ assert_block "Statement should have been closed within given block" do
+ handle.finished?
+ end
+ end
+ ensure
+ GC.enable
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/aggregations_test.rb b/vendor/rails/activerecord/test/cases/aggregations_test.rb
new file mode 100644
index 0000000..75d1f27
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/aggregations_test.rb
@@ -0,0 +1,128 @@
+require "cases/helper"
+require 'models/customer'
+
+class AggregationsTest < ActiveRecord::TestCase
+ fixtures :customers
+
+ def test_find_single_value_object
+ assert_equal 50, customers(:david).balance.amount
+ assert_kind_of Money, customers(:david).balance
+ assert_equal 300, customers(:david).balance.exchange_to("DKK").amount
+ end
+
+ def test_find_multiple_value_object
+ assert_equal customers(:david).address_street, customers(:david).address.street
+ assert(
+ customers(:david).address.close_to?(Address.new("Different Street", customers(:david).address_city, customers(:david).address_country))
+ )
+ end
+
+ def test_change_single_value_object
+ customers(:david).balance = Money.new(100)
+ customers(:david).save
+ assert_equal 100, customers(:david).reload.balance.amount
+ end
+
+ def test_immutable_value_objects
+ customers(:david).balance = Money.new(100)
+ assert_raise(ActiveSupport::FrozenObjectError) { customers(:david).balance.instance_eval { @amount = 20 } }
+ end
+
+ def test_inferred_mapping
+ assert_equal "35.544623640962634", customers(:david).gps_location.latitude
+ assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
+
+ customers(:david).gps_location = GpsLocation.new("39x-110")
+
+ assert_equal "39", customers(:david).gps_location.latitude
+ assert_equal "-110", customers(:david).gps_location.longitude
+
+ customers(:david).save
+
+ customers(:david).reload
+
+ assert_equal "39", customers(:david).gps_location.latitude
+ assert_equal "-110", customers(:david).gps_location.longitude
+ end
+
+ def test_reloaded_instance_refreshes_aggregations
+ assert_equal "35.544623640962634", customers(:david).gps_location.latitude
+ assert_equal "-105.9309951055148", customers(:david).gps_location.longitude
+
+ Customer.update_all("gps_location = '24x113'")
+ customers(:david).reload
+ assert_equal '24x113', customers(:david)['gps_location']
+
+ assert_equal GpsLocation.new('24x113'), customers(:david).gps_location
+ end
+
+ def test_gps_equality
+ assert GpsLocation.new('39x110') == GpsLocation.new('39x110')
+ end
+
+ def test_gps_inequality
+ assert GpsLocation.new('39x110') != GpsLocation.new('39x111')
+ end
+
+ def test_allow_nil_gps_is_nil
+ assert_equal nil, customers(:zaphod).gps_location
+ end
+
+ def test_allow_nil_gps_set_to_nil
+ customers(:david).gps_location = nil
+ customers(:david).save
+ customers(:david).reload
+ assert_equal nil, customers(:david).gps_location
+ end
+
+ def test_allow_nil_set_address_attributes_to_nil
+ customers(:zaphod).address = nil
+ assert_equal nil, customers(:zaphod).attributes[:address_street]
+ assert_equal nil, customers(:zaphod).attributes[:address_city]
+ assert_equal nil, customers(:zaphod).attributes[:address_country]
+ end
+
+ def test_allow_nil_address_set_to_nil
+ customers(:zaphod).address = nil
+ customers(:zaphod).save
+ customers(:zaphod).reload
+ assert_equal nil, customers(:zaphod).address
+ end
+
+ def test_nil_raises_error_when_allow_nil_is_false
+ assert_raise(NoMethodError) { customers(:david).balance = nil }
+ end
+
+ def test_allow_nil_address_loaded_when_only_some_attributes_are_nil
+ customers(:zaphod).address_street = nil
+ customers(:zaphod).save
+ customers(:zaphod).reload
+ assert_kind_of Address, customers(:zaphod).address
+ assert customers(:zaphod).address.street.nil?
+ end
+
+ def test_nil_assignment_results_in_nil
+ customers(:david).gps_location = GpsLocation.new('39x111')
+ assert_not_equal nil, customers(:david).gps_location
+ customers(:david).gps_location = nil
+ assert_equal nil, customers(:david).gps_location
+ end
+end
+
+class OverridingAggregationsTest < ActiveRecord::TestCase
+ class Name; end
+ class DifferentName; end
+
+ class Person < ActiveRecord::Base
+ composed_of :composed_of, :mapping => %w(person_first_name first_name)
+ end
+
+ class DifferentPerson < Person
+ composed_of :composed_of, :class_name => 'DifferentName', :mapping => %w(different_person_first_name first_name)
+ end
+
+ def test_composed_of_aggregation_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal Person.reflect_on_aggregation(:composed_of),
+ DifferentPerson.reflect_on_aggregation(:composed_of)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/ar_schema_test.rb b/vendor/rails/activerecord/test/cases/ar_schema_test.rb
new file mode 100644
index 0000000..431dc7a
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/ar_schema_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+require 'active_record/schema'
+
+if ActiveRecord::Base.connection.supports_migrations?
+
+ class ActiveRecordSchemaTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def teardown
+ @connection.drop_table :fruits rescue nil
+ end
+
+ def test_schema_define
+ ActiveRecord::Schema.define(:version => 7) do
+ create_table :fruits do |t|
+ t.column :color, :string
+ t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle
+ t.column :texture, :string
+ t.column :flavor, :string
+ end
+ end
+
+ assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" }
+ assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" }
+ assert_equal 7, ActiveRecord::Migrator::current_version
+ end
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb
new file mode 100644
index 0000000..e0da8bf
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -0,0 +1,412 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+require 'models/computer'
+require 'models/customer'
+require 'models/order'
+require 'models/post'
+require 'models/author'
+require 'models/tag'
+require 'models/tagging'
+require 'models/comment'
+require 'models/sponsor'
+require 'models/member'
+
+class BelongsToAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :developers, :projects, :topics,
+ :developers_projects, :computers, :authors, :posts, :tags, :taggings, :comments
+
+ def test_belongs_to
+ Client.find(3).firm.name
+ assert_equal companies(:first_firm).name, Client.find(3).firm.name
+ assert !Client.find(3).firm.nil?, "Microsoft should have a firm"
+ end
+
+ def test_proxy_assignment
+ account = Account.find(1)
+ assert_nothing_raised { account.firm = account.firm }
+ end
+
+ def test_triple_equality
+ assert Client.find(3).firm === Firm
+ assert Firm === Client.find(3).firm
+ end
+
+ def test_type_mismatch
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = 1 }
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { Account.find(1).firm = Project.find(1) }
+ end
+
+ def test_natural_assignment
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+ citibank.firm = apple
+ assert_equal apple.id, citibank.firm_id
+ 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
+ end
+
+ def test_creating_the_belonging_object
+ citibank = Account.create("credit_limit" => 10)
+ apple = citibank.create_firm("name" => "Apple")
+ assert_equal apple, citibank.firm
+ citibank.save
+ citibank.reload
+ assert_equal apple, citibank.firm
+ end
+
+ def test_building_the_belonging_object
+ citibank = Account.create("credit_limit" => 10)
+ apple = citibank.build_firm("name" => "Apple")
+ citibank.save
+ assert_equal apple.id, citibank.firm_id
+ end
+
+ def test_natural_assignment_to_nil
+ client = Client.find(3)
+ client.firm = nil
+ client.save
+ assert_nil client.firm(true)
+ assert_nil client.client_of
+ end
+
+ def test_with_different_class_name
+ assert_equal Company.find(1).name, Company.find(3).firm_with_other_name.name
+ assert_not_nil Company.find(3).firm_with_other_name, "Microsoft should have a firm"
+ end
+
+ def test_with_condition
+ assert_equal Company.find(1).name, Company.find(3).firm_with_condition.name
+ assert_not_nil Company.find(3).firm_with_condition, "Microsoft should have a firm"
+ 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
+ end
+
+ def test_belongs_to_counter
+ debate = Topic.create("title" => "debate")
+ assert_equal 0, debate.send(:read_attribute, "replies_count"), "No replies yet"
+
+ trash = debate.replies.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)
+
+ assert_equal p.id, c.post_id
+ assert_equal 2, Post.find(p.id).comments.size
+
+ c.post = nil
+
+ assert_equal 1, Post.find(p.id).comments.size
+ end
+
+ def test_belongs_to_counter_with_reassigning
+ t1 = Topic.create("title" => "t1")
+ t2 = Topic.create("title" => "t2")
+ r1 = Reply.new("title" => "r1", "content" => "r1")
+ r1.topic = t1
+
+ assert r1.save
+ assert_equal 1, Topic.find(t1.id).replies.size
+ assert_equal 0, Topic.find(t2.id).replies.size
+
+ r1.topic = Topic.find(t2.id)
+
+ assert r1.save
+ assert_equal 0, Topic.find(t1.id).replies.size
+ assert_equal 1, Topic.find(t2.id).replies.size
+
+ r1.topic = nil
+
+ assert_equal 0, Topic.find(t1.id).replies.size
+ assert_equal 0, Topic.find(t2.id).replies.size
+
+ r1.topic = t1
+
+ assert_equal 1, Topic.find(t1.id).replies.size
+ assert_equal 0, Topic.find(t2.id).replies.size
+
+ r1.destroy
+
+ assert_equal 0, Topic.find(t1.id).replies.size
+ assert_equal 0, Topic.find(t2.id).replies.size
+ end
+
+ def test_belongs_to_counter_after_save
+ topic = Topic.create!(:title => "monday night")
+ topic.replies.create!(:title => "re: monday night", :content => "football")
+ assert_equal 1, Topic.find(topic.id)[:replies_count]
+
+ topic.save!
+ assert_equal 1, Topic.find(topic.id)[:replies_count]
+ end
+
+ def test_belongs_to_counter_after_update_attributes
+ topic = Topic.create!(:title => "37s")
+ topic.replies.create!(:title => "re: 37s", :content => "rails")
+ assert_equal 1, Topic.find(topic.id)[:replies_count]
+
+ topic.update_attributes(:title => "37signals")
+ assert_equal 1, Topic.find(topic.id)[:replies_count]
+ end
+
+ def test_belongs_to_counter_after_save
+ topic = Topic.create("title" => "monday night")
+ topic.replies.create("title" => "re: monday night", "content" => "football")
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
+
+ topic.save
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
+ end
+
+ def test_belongs_to_counter_after_update_attributes
+ topic = Topic.create("title" => "37s")
+ topic.replies.create("title" => "re: 37s", "content" => "rails")
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
+
+ topic.update_attributes("title" => "37signals")
+ assert_equal 1, Topic.find(topic.id).send(:read_attribute, "replies_count")
+ end
+
+ def test_assignment_before_parent_saved
+ client = Client.find(:first)
+ apple = Firm.new("name" => "Apple")
+ client.firm = apple
+ assert_equal apple, client.firm
+ assert apple.new_record?
+ assert client.save
+ assert apple.save
+ assert !apple.new_record?
+ assert_equal apple, client.firm
+ assert_equal apple, client.firm(true)
+ end
+
+ def test_assignment_before_child_saved
+ final_cut = Client.new("name" => "Final Cut")
+ firm = Firm.find(1)
+ final_cut.firm = firm
+ assert final_cut.new_record?
+ assert final_cut.save
+ assert !final_cut.new_record?
+ assert !firm.new_record?
+ assert_equal firm, final_cut.firm
+ assert_equal firm, final_cut.firm(true)
+ end
+
+ def test_assignment_before_either_saved
+ final_cut = Client.new("name" => "Final Cut")
+ apple = Firm.new("name" => "Apple")
+ final_cut.firm = apple
+ assert final_cut.new_record?
+ assert apple.new_record?
+ assert final_cut.save
+ assert !final_cut.new_record?
+ assert !apple.new_record?
+ assert_equal apple, final_cut.firm
+ assert_equal apple, final_cut.firm(true)
+ end
+
+ def test_new_record_with_foreign_key_but_no_object
+ c = Client.new("firm_id" => 1)
+ assert_equal Firm.find(:first), 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
+
+ c.firm_id = 1
+ assert_equal Firm.find(:first), c.firm_with_basic_id
+ end
+
+ def test_field_name_same_as_foreign_key
+ computer = Computer.find(1)
+ assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
+ end
+
+ def test_counter_cache
+ topic = Topic.create :title => "Zoom-zoom-zoom"
+ assert_equal 0, topic[:replies_count]
+
+ reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
+ reply.topic = topic
+
+ assert_equal 1, topic.reload[:replies_count]
+ assert_equal 1, topic.replies.size
+
+ topic[:replies_count] = 15
+ assert_equal 15, topic.replies.size
+ end
+
+ def test_custom_counter_cache
+ reply = Reply.create(:title => "re: zoom", :content => "speedy quick!")
+ assert_equal 0, reply[:replies_count]
+
+ silly = SillyReply.create(:title => "gaga", :content => "boo-boo")
+ silly.reply = reply
+
+ assert_equal 1, reply.reload[:replies_count]
+ assert_equal 1, reply.replies.size
+
+ reply[:replies_count] = 17
+ assert_equal 17, reply.replies.size
+ end
+
+ def test_store_two_association_with_one_save
+ num_orders = Order.count
+ num_customers = Customer.count
+ order = Order.new
+
+ customer1 = order.billing = Customer.new
+ customer2 = order.shipping = Customer.new
+ assert order.save
+ assert_equal customer1, order.billing
+ assert_equal customer2, order.shipping
+
+ order.reload
+
+ assert_equal customer1, order.billing
+ assert_equal customer2, order.shipping
+
+ assert_equal num_orders +1, Order.count
+ assert_equal num_customers +2, Customer.count
+ end
+
+
+ def test_store_association_in_two_relations_with_one_save
+ num_orders = Order.count
+ num_customers = Customer.count
+ order = Order.new
+
+ customer = order.billing = order.shipping = Customer.new
+ assert order.save
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ order.reload
+
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ assert_equal num_orders +1, Order.count
+ assert_equal num_customers +1, Customer.count
+ end
+
+ def test_store_association_in_two_relations_with_one_save_in_existing_object
+ num_orders = Order.count
+ num_customers = Customer.count
+ order = Order.create
+
+ customer = order.billing = order.shipping = Customer.new
+ assert order.save
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ order.reload
+
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ assert_equal num_orders +1, Order.count
+ assert_equal num_customers +1, Customer.count
+ end
+
+ def test_store_association_in_two_relations_with_one_save_in_existing_object_with_values
+ num_orders = Order.count
+ num_customers = Customer.count
+ order = Order.create
+
+ customer = order.billing = order.shipping = Customer.new
+ assert order.save
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ order.reload
+
+ customer = order.billing = order.shipping = Customer.new
+
+ assert order.save
+ order.reload
+
+ assert_equal customer, order.billing
+ assert_equal customer, order.shipping
+
+ assert_equal num_orders +1, Order.count
+ assert_equal num_customers +2, Customer.count
+ end
+
+
+ def test_association_assignment_sticks
+ post = Post.find(:first)
+
+ author1, author2 = Author.find(:all, :limit => 2)
+ assert_not_nil author1
+ assert_not_nil author2
+
+ # make sure the association is loaded
+ post.author
+
+ # set the association by id, directly
+ post.author_id = author2.id
+
+ # save and reload
+ post.save!
+ post.reload
+
+ # the author id of the post should be the id we set
+ assert_equal post.author_id, author2.id
+ end
+
+ def test_cant_save_readonly_association
+ assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_client).readonly_firm.save! }
+ assert companies(:first_client).readonly_firm.readonly?
+ end
+
+ def test_polymorphic_assignment_foreign_type_field_updating
+ # should update when assigning a saved record
+ sponsor = Sponsor.new
+ member = Member.create
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+
+ # should update when assigning a new record
+ sponsor = Sponsor.new
+ member = Member.new
+ sponsor.sponsorable = member
+ assert_equal "Member", sponsor.sponsorable_type
+ end
+
+ def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records
+ sponsor = Sponsor.new
+ saved_member = Member.create
+ new_member = Member.new
+
+ sponsor.sponsorable = saved_member
+ assert_equal saved_member.id, sponsor.sponsorable_id
+
+ sponsor.sponsorable = new_member
+ assert_equal nil, sponsor.sponsorable_id
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/callbacks_test.rb b/vendor/rails/activerecord/test/cases/associations/callbacks_test.rb
new file mode 100644
index 0000000..91b1af1
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/callbacks_test.rb
@@ -0,0 +1,161 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/author'
+require 'models/category'
+require 'models/project'
+require 'models/developer'
+
+class AssociationCallbacksTest < ActiveRecord::TestCase
+ fixtures :posts, :authors, :projects, :developers
+
+ def setup
+ @david = authors(:david)
+ @thinking = posts(:thinking)
+ @authorless = posts(:authorless)
+ assert @david.post_log.empty?
+ end
+
+ def test_adding_macro_callbacks
+ @david.posts_with_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
+ @david.posts_with_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
+ "after_adding#{@thinking.id}"], @david.post_log
+ end
+
+ def test_adding_with_proc_callbacks
+ @david.posts_with_proc_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}"], @david.post_log
+ @david.posts_with_proc_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "after_adding#{@thinking.id}", "before_adding#{@thinking.id}",
+ "after_adding#{@thinking.id}"], @david.post_log
+ end
+
+ def test_removing_with_macro_callbacks
+ first_post, second_post = @david.posts_with_callbacks[0, 2]
+ @david.posts_with_callbacks.delete(first_post)
+ assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
+ @david.posts_with_callbacks.delete(second_post)
+ assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
+ "after_removing#{second_post.id}"], @david.post_log
+ end
+
+ def test_removing_with_proc_callbacks
+ first_post, second_post = @david.posts_with_callbacks[0, 2]
+ @david.posts_with_proc_callbacks.delete(first_post)
+ assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}"], @david.post_log
+ @david.posts_with_proc_callbacks.delete(second_post)
+ assert_equal ["before_removing#{first_post.id}", "after_removing#{first_post.id}", "before_removing#{second_post.id}",
+ "after_removing#{second_post.id}"], @david.post_log
+ end
+
+ def test_multiple_callbacks
+ @david.posts_with_multiple_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
+ "after_adding_proc#{@thinking.id}"], @david.post_log
+ @david.posts_with_multiple_callbacks << @thinking
+ assert_equal ["before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}", "after_adding#{@thinking.id}",
+ "after_adding_proc#{@thinking.id}", "before_adding#{@thinking.id}", "before_adding_proc#{@thinking.id}",
+ "after_adding#{@thinking.id}", "after_adding_proc#{@thinking.id}"], @david.post_log
+ end
+
+ def test_has_many_callbacks_with_create
+ morten = Author.create :name => "Morten"
+ post = morten.posts_with_proc_callbacks.create! :title => "Hello", :body => "How are you doing?"
+ assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log
+ end
+
+ def test_has_many_callbacks_with_create!
+ morten = Author.create! :name => "Morten"
+ post = morten.posts_with_proc_callbacks.create :title => "Hello", :body => "How are you doing?"
+ assert_equal ["before_adding", "after_adding#{post.id}"], morten.post_log
+ end
+
+ def test_has_many_callbacks_for_save_on_parent
+ jack = Author.new :name => "Jack"
+ post = jack.posts_with_callbacks.build :title => "Call me back!", :body => "Before you wake up and after you sleep"
+
+ callback_log = ["before_adding", "after_adding#{jack.posts_with_callbacks.first.id}"]
+ assert_equal callback_log, jack.post_log
+ assert jack.save
+ assert_equal 1, jack.posts_with_callbacks.count
+ assert_equal callback_log, jack.post_log
+ end
+
+ def test_has_and_belongs_to_many_add_callback
+ david = developers(:david)
+ ar = projects(:active_record)
+ assert ar.developers_log.empty?
+ ar.developers_with_callbacks << david
+ assert_equal ["before_adding#{david.id}", "after_adding#{david.id}"], ar.developers_log
+ ar.developers_with_callbacks << david
+ assert_equal ["before_adding#{david.id}", "after_adding#{david.id}", "before_adding#{david.id}",
+ "after_adding#{david.id}"], ar.developers_log
+ end
+
+ def test_has_and_belongs_to_many_after_add_called_after_save
+ ar = projects(:active_record)
+ assert ar.developers_log.empty?
+ alice = Developer.new(:name => 'alice')
+ ar.developers_with_callbacks << alice
+ assert_equal"after_adding#{alice.id}", ar.developers_log.last
+
+ bob = ar.developers_with_callbacks.create(:name => 'bob')
+ assert_equal "after_adding#{bob.id}", ar.developers_log.last
+
+ ar.developers_with_callbacks.build(:name => 'charlie')
+ assert_equal "after_adding", ar.developers_log.last
+ end
+
+
+ def test_has_and_belongs_to_many_remove_callback
+ david = developers(:david)
+ jamis = developers(:jamis)
+ activerecord = projects(:active_record)
+ assert activerecord.developers_log.empty?
+ activerecord.developers_with_callbacks.delete(david)
+ assert_equal ["before_removing#{david.id}", "after_removing#{david.id}"], activerecord.developers_log
+
+ activerecord.developers_with_callbacks.delete(jamis)
+ assert_equal ["before_removing#{david.id}", "after_removing#{david.id}", "before_removing#{jamis.id}",
+ "after_removing#{jamis.id}"], activerecord.developers_log
+ end
+
+ def test_has_and_belongs_to_many_remove_callback_on_clear
+ activerecord = projects(:active_record)
+ assert activerecord.developers_log.empty?
+ if activerecord.developers_with_callbacks.size == 0
+ activerecord.developers << developers(:david)
+ activerecord.developers << developers(:jamis)
+ activerecord.reload
+ assert activerecord.developers_with_callbacks.size == 2
+ end
+ log_array = activerecord.developers_with_callbacks.collect {|d| ["before_removing#{d.id}","after_removing#{d.id}"]}.flatten.sort
+ assert activerecord.developers_with_callbacks.clear
+ assert_equal log_array, activerecord.developers_log.sort
+ end
+
+ def test_has_many_and_belongs_to_many_callbacks_for_save_on_parent
+ project = Project.new :name => "Callbacks"
+ project.developers_with_callbacks.build :name => "Jack", :salary => 95000
+
+ callback_log = ["before_adding", "after_adding"]
+ assert_equal callback_log, project.developers_log
+ assert project.save
+ assert_equal 1, project.developers_with_callbacks.size
+ assert_equal callback_log, project.developers_log
+ end
+
+ def test_dont_add_if_before_callback_raises_exception
+ assert !@david.unchangable_posts.include?(@authorless)
+ begin
+ @david.unchangable_posts << @authorless
+ rescue Exception => e
+ end
+ assert @david.post_log.empty?
+ assert !@david.unchangable_posts.include?(@authorless)
+ @david.reload
+ assert !@david.unchangable_posts.include?(@authorless)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/vendor/rails/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
new file mode 100644
index 0000000..3631be7
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/cascaded_eager_loading_test.rb
@@ -0,0 +1,111 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/author'
+require 'models/category'
+require 'models/categorization'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+
+class CascadedEagerLoadingTest < ActiveRecord::TestCase
+ fixtures :authors, :mixins, :companies, :posts, :topics
+
+ def test_eager_association_loading_with_cascaded_two_levels
+ authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
+ assert_equal 2, authors.size
+ assert_equal 5, authors[0].posts.size
+ assert_equal 1, authors[1].posts.size
+ assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ end
+
+ def test_eager_association_loading_with_cascaded_two_levels_and_one_level
+ authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
+ assert_equal 2, authors.size
+ assert_equal 5, authors[0].posts.size
+ assert_equal 1, authors[1].posts.size
+ assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ assert_equal 1, authors[0].categorizations.size
+ assert_equal 2, authors[1].categorizations.size
+ end
+
+ def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
+ authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
+ assert_equal 2, authors.size
+ assert_equal 5, authors[0].posts.size
+ assert_equal 1, authors[1].posts.size
+ assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
+ end
+
+ def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
+ authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
+ assert_equal 2, authors.size
+ assert_equal 5, authors[0].posts.size
+ assert_equal authors(:david).name, authors[0].name
+ assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
+ end
+
+ def test_eager_association_loading_with_cascaded_two_levels_with_condition
+ authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
+ assert_equal 1, authors.size
+ assert_equal 5, authors[0].posts.size
+ end
+
+ def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
+ firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
+ assert_equal 2, firms.size
+ assert_equal firms.first.account, firms.first.account.firm.account
+ assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
+ assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
+ end
+
+ def test_eager_association_loading_with_has_many_sti
+ topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
+ first, second, = topics(:first).replies.size, topics(:second).replies.size
+ assert_no_queries do
+ assert_equal first, topics[0].replies.size
+ assert_equal second, topics[1].replies.size
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_sti
+ replies = Reply.find(:all, :include => :topic, :order => 'topics.id')
+ assert replies.include?(topics(:second))
+ assert !replies.include?(topics(:first))
+ assert_equal topics(:first), assert_no_queries { replies.first.topic }
+ end
+
+ def test_eager_association_loading_with_multiple_stis_and_order
+ author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ assert_equal authors(:david), author
+ assert_no_queries do
+ author.posts.first.special_comments
+ author.posts.first.very_special_comment
+ end
+ end
+
+ def test_eager_association_loading_of_stis_with_multiple_references
+ authors = Author.find(:all, :include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4')
+ assert_equal [authors(:david)], authors
+ assert_no_queries do
+ authors.first.posts.first.special_comments.first.post.special_comments
+ authors.first.posts.first.special_comments.first.post.very_special_comment
+ end
+ end
+end
+
+require 'models/vertex'
+require 'models/edge'
+class CascadedEagerLoadingTest < ActiveRecord::TestCase
+ fixtures :edges, :vertices
+
+ def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
+ source = Vertex.find(:first, :include=>{:sinks=>{:sinks=>{:sinks=>:sinks}}}, :order => 'vertices.id')
+ assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
+ end
+
+ def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
+ sink = Vertex.find(:first, :include=>{:sources=>{:sources=>{:sources=>:sources}}}, :order => 'vertices.id DESC')
+ assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb
new file mode 100644
index 0000000..80cfc84
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/eager_load_nested_include_test.rb
@@ -0,0 +1,83 @@
+require 'cases/helper'
+
+
+class ShapeExpression < ActiveRecord::Base
+ belongs_to :shape, :polymorphic => true
+ belongs_to :paint, :polymorphic => true
+end
+
+class Circle < ActiveRecord::Base
+ has_many :shape_expressions, :as => :shape
+end
+class Square < ActiveRecord::Base
+ has_many :shape_expressions, :as => :shape
+end
+class Triangle < ActiveRecord::Base
+ has_many :shape_expressions, :as => :shape
+end
+class PaintColor < ActiveRecord::Base
+ has_many :shape_expressions, :as => :paint
+ belongs_to :non_poly, :foreign_key => "non_poly_one_id", :class_name => "NonPolyOne"
+end
+class PaintTexture < ActiveRecord::Base
+ has_many :shape_expressions, :as => :paint
+ belongs_to :non_poly, :foreign_key => "non_poly_two_id", :class_name => "NonPolyTwo"
+end
+class NonPolyOne < ActiveRecord::Base
+ has_many :paint_colors
+end
+class NonPolyTwo < ActiveRecord::Base
+ has_many :paint_textures
+end
+
+
+
+class EagerLoadPolyAssocsTest < ActiveRecord::TestCase
+ NUM_SIMPLE_OBJS = 50
+ NUM_SHAPE_EXPRESSIONS = 100
+
+ def setup
+ generate_test_object_graphs
+ end
+
+ def teardown
+ [Circle, Square, Triangle, PaintColor, PaintTexture,
+ ShapeExpression, NonPolyOne, NonPolyTwo].each do |c|
+ c.delete_all
+ end
+
+ end
+
+
+ # meant to be supplied as an ID, never returns 0
+ def rand_simple
+ val = (NUM_SIMPLE_OBJS * rand).round
+ val == 0 ? 1 : val
+ end
+
+ def generate_test_object_graphs
+ 1.upto(NUM_SIMPLE_OBJS) do
+ [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!)
+ end
+ 1.upto(NUM_SIMPLE_OBJS) do |i|
+ PaintColor.create!(:non_poly_one_id => rand_simple)
+ PaintTexture.create!(:non_poly_two_id => rand_simple)
+ end
+ 1.upto(NUM_SHAPE_EXPRESSIONS) do |i|
+ ShapeExpression.create!(:shape_type => [Circle, Square, Triangle].rand.to_s, :shape_id => rand_simple,
+ :paint_type => [PaintColor, PaintTexture].rand.to_s, :paint_id => rand_simple)
+ end
+ end
+
+ def test_include_query
+ res = 0
+ res = ShapeExpression.find :all, :include => [ :shape, { :paint => :non_poly } ]
+ assert_equal NUM_SHAPE_EXPRESSIONS, res.size
+ assert_queries(0) do
+ res.each do |se|
+ assert_not_nil se.paint.non_poly, "this is the association that was loading incorrectly before the change"
+ assert_not_nil se.shape, "just making sure other associations still work"
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/eager_singularization_test.rb b/vendor/rails/activerecord/test/cases/associations/eager_singularization_test.rb
new file mode 100644
index 0000000..07d0b24
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/eager_singularization_test.rb
@@ -0,0 +1,145 @@
+require "cases/helper"
+
+class Virus < ActiveRecord::Base
+ belongs_to :octopus
+end
+class Octopus < ActiveRecord::Base
+ has_one :virus
+end
+class Pass < ActiveRecord::Base
+ belongs_to :bus
+end
+class Bus < ActiveRecord::Base
+ has_many :passes
+end
+class Mess < ActiveRecord::Base
+ has_and_belongs_to_many :crises
+end
+class Crisis < ActiveRecord::Base
+ has_and_belongs_to_many :messes
+ has_many :analyses, :dependent => :destroy
+ has_many :successes, :through => :analyses
+ has_many :dresses, :dependent => :destroy
+ has_many :compresses, :through => :dresses
+end
+class Analysis < ActiveRecord::Base
+ belongs_to :crisis
+ belongs_to :success
+end
+class Success < ActiveRecord::Base
+ has_many :analyses, :dependent => :destroy
+ has_many :crises, :through => :analyses
+end
+class Dress < ActiveRecord::Base
+ belongs_to :crisis
+ has_many :compresses
+end
+class Compress < ActiveRecord::Base
+ belongs_to :dress
+end
+
+
+class EagerSingularizationTest < ActiveRecord::TestCase
+
+ def setup
+ if ActiveRecord::Base.connection.supports_migrations?
+ ActiveRecord::Base.connection.create_table :viri do |t|
+ t.column :octopus_id, :integer
+ t.column :species, :string
+ end
+ ActiveRecord::Base.connection.create_table :octopi do |t|
+ t.column :species, :string
+ end
+ ActiveRecord::Base.connection.create_table :passes do |t|
+ t.column :bus_id, :integer
+ t.column :rides, :integer
+ end
+ ActiveRecord::Base.connection.create_table :buses do |t|
+ t.column :name, :string
+ end
+ ActiveRecord::Base.connection.create_table :crises_messes, :id => false do |t|
+ t.column :crisis_id, :integer
+ t.column :mess_id, :integer
+ end
+ ActiveRecord::Base.connection.create_table :messes do |t|
+ t.column :name, :string
+ end
+ ActiveRecord::Base.connection.create_table :crises do |t|
+ t.column :name, :string
+ end
+ ActiveRecord::Base.connection.create_table :successes do |t|
+ t.column :name, :string
+ end
+ ActiveRecord::Base.connection.create_table :analyses do |t|
+ t.column :crisis_id, :integer
+ t.column :success_id, :integer
+ end
+ ActiveRecord::Base.connection.create_table :dresses do |t|
+ t.column :crisis_id, :integer
+ end
+ ActiveRecord::Base.connection.create_table :compresses do |t|
+ t.column :dress_id, :integer
+ end
+ @have_tables = true
+ else
+ @have_tables = false
+ end
+ end
+
+ def teardown
+ ActiveRecord::Base.connection.drop_table :viri
+ ActiveRecord::Base.connection.drop_table :octopi
+ ActiveRecord::Base.connection.drop_table :passes
+ ActiveRecord::Base.connection.drop_table :buses
+ ActiveRecord::Base.connection.drop_table :crises_messes
+ ActiveRecord::Base.connection.drop_table :messes
+ ActiveRecord::Base.connection.drop_table :crises
+ ActiveRecord::Base.connection.drop_table :successes
+ ActiveRecord::Base.connection.drop_table :analyses
+ ActiveRecord::Base.connection.drop_table :dresses
+ ActiveRecord::Base.connection.drop_table :compresses
+ end
+
+ def test_eager_no_extra_singularization_belongs_to
+ return unless @have_tables
+ assert_nothing_raised do
+ Virus.find(:all, :include => :octopus)
+ end
+ end
+
+ def test_eager_no_extra_singularization_has_one
+ return unless @have_tables
+ assert_nothing_raised do
+ Octopus.find(:all, :include => :virus)
+ end
+ end
+
+ def test_eager_no_extra_singularization_has_many
+ return unless @have_tables
+ assert_nothing_raised do
+ Bus.find(:all, :include => :passes)
+ end
+ end
+
+ def test_eager_no_extra_singularization_has_and_belongs_to_many
+ return unless @have_tables
+ assert_nothing_raised do
+ Crisis.find(:all, :include => :messes)
+ Mess.find(:all, :include => :crises)
+ end
+ end
+
+ def test_eager_no_extra_singularization_has_many_through_belongs_to
+ return unless @have_tables
+ assert_nothing_raised do
+ Crisis.find(:all, :include => :successes)
+ end
+ end
+
+ def test_eager_no_extra_singularization_has_many_through_has_many
+ return unless @have_tables
+ assert_nothing_raised do
+ Crisis.find(:all, :include => :compresses)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/eager_test.rb b/vendor/rails/activerecord/test/cases/associations/eager_test.rb
new file mode 100644
index 0000000..3a3358e
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/eager_test.rb
@@ -0,0 +1,612 @@
+require "cases/helper"
+require 'models/post'
+require 'models/tagging'
+require 'models/comment'
+require 'models/author'
+require 'models/category'
+require 'models/company'
+require 'models/person'
+require 'models/reader'
+require 'models/owner'
+require 'models/pet'
+require 'models/reference'
+require 'models/job'
+require 'models/subscriber'
+require 'models/subscription'
+require 'models/book'
+
+class EagerAssociationTest < ActiveRecord::TestCase
+ fixtures :posts, :comments, :authors, :categories, :categories_posts,
+ :companies, :accounts, :tags, :taggings, :people, :readers,
+ :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books
+
+ def test_loading_with_one_association
+ posts = Post.find(:all, :include => :comments)
+ post = posts.find { |p| p.id == 1 }
+ assert_equal 2, post.comments.size
+ assert post.comments.include?(comments(:greetings))
+
+ post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'")
+ assert_equal 2, post.comments.size
+ assert post.comments.include?(comments(:greetings))
+
+ posts = Post.find(:all, :include => :last_comment)
+ post = posts.find { |p| p.id == 1 }
+ assert_equal Post.find(1).last_comment, post.last_comment
+ end
+
+ def test_loading_conditions_with_or
+ posts = authors(:david).posts.find(:all, :include => :comments, :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE} = 'SpecialComment'")
+ assert_nil posts.detect { |p| p.author_id != authors(:david).id },
+ "expected to find only david's posts"
+ end
+
+ def test_with_ordering
+ list = Post.find(:all, :include => :comments, :order => "posts.id DESC")
+ [:eager_other, :sti_habtm, :sti_post_and_comments, :sti_comments,
+ :authorless, :thinking, :welcome
+ ].each_with_index do |post, index|
+ assert_equal posts(post), list[index]
+ end
+ end
+
+ def test_with_two_tables_in_from_without_getting_double_quoted
+ posts = Post.find(:all,
+ :select => "posts.*",
+ :from => "authors, posts",
+ :include => :comments,
+ :conditions => "posts.author_id = authors.id",
+ :order => "posts.id"
+ )
+
+ assert_equal 2, posts.first.comments.size
+ end
+
+ def test_loading_with_multiple_associations
+ posts = Post.find(:all, :include => [ :comments, :author, :categories ], :order => "posts.id")
+ assert_equal 2, posts.first.comments.size
+ assert_equal 2, posts.first.categories.size
+ assert posts.first.comments.include?(comments(:greetings))
+ end
+
+ def test_duplicate_middle_objects
+ comments = Comment.find :all, :conditions => 'post_id = 1', :include => [:post => :author]
+ assert_no_queries do
+ comments.each {|comment| comment.post.author.name}
+ end
+ end
+
+ def test_including_duplicate_objects_from_belongs_to
+ popular_post = Post.create!(:title => 'foo', :body => "I like cars!")
+ comment = popular_post.comments.create!(:body => "lol")
+ popular_post.readers.create!(:person => people(:michael))
+ popular_post.readers.create!(:person => people(:david))
+
+ readers = Reader.find(:all, :conditions => ["post_id = ?", popular_post.id],
+ :include => {:post => :comments})
+ readers.each do |reader|
+ assert_equal [comment], reader.post.comments
+ end
+ end
+
+ def test_including_duplicate_objects_from_has_many
+ car_post = Post.create!(:title => 'foo', :body => "I like cars!")
+ car_post.categories << categories(:general)
+ car_post.categories << categories(:technology)
+
+ comment = car_post.comments.create!(:body => "hmm")
+ categories = Category.find(:all, :conditions => ["posts.id=?", car_post.id],
+ :include => {:posts => :comments})
+ categories.each do |category|
+ assert_equal [comment], category.posts[0].comments
+ 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
+ end
+
+ def test_loading_with_no_associations
+ assert_nil Post.find(posts(:authorless).id, :include => :author).author
+ end
+
+ def test_nested_loading_with_no_associations
+ assert_nothing_raised do
+ Post.find(posts(:authorless).id, :include => {:author => :author_addresss})
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_foreign_keys
+ pets = Pet.find(:all, :include => :owner)
+ assert_equal 3, pets.length
+ end
+
+ def test_eager_association_loading_with_belongs_to
+ comments = Comment.find(:all, :include => :post)
+ assert_equal 10, comments.length
+ titles = comments.map { |c| c.post.title }
+ assert titles.include?(posts(:welcome).title)
+ assert titles.include?(posts(:sti_post_and_comments).title)
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit
+ comments = Comment.find(:all, :include => :post, :limit => 5, :order => 'comments.id')
+ assert_equal 5, comments.length
+ assert_equal [1,2,3,5,6], comments.collect { |c| c.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_conditions
+ comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :order => 'comments.id')
+ assert_equal 3, comments.length
+ assert_equal [5,6,7], comments.collect { |c| c.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_offset
+ comments = Comment.find(:all, :include => :post, :limit => 3, :offset => 2, :order => 'comments.id')
+ assert_equal 3, comments.length
+ assert_equal [3,5,6], comments.collect { |c| c.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions
+ comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 4', :limit => 3, :offset => 1, :order => 'comments.id')
+ assert_equal 3, comments.length
+ assert_equal [6,7,8], comments.collect { |c| c.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_conditions_array
+ comments = Comment.find(:all, :include => :post, :conditions => ['post_id = ?',4], :limit => 3, :offset => 1, :order => 'comments.id')
+ assert_equal 3, comments.length
+ assert_equal [6,7,8], comments.collect { |c| c.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_conditions_string_with_unquoted_table_name
+ assert_nothing_raised do
+ Comment.find(:all, :include => :post, :conditions => ['posts.id = ?',4])
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_conditions_string_with_quoted_table_name
+ quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
+ assert_nothing_raised do
+ Comment.find(:all, :include => :post, :conditions => ["#{quoted_posts_id} = ?",4])
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_order_string_with_unquoted_table_name
+ assert_nothing_raised do
+ Comment.find(:all, :include => :post, :order => 'posts.id')
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
+ quoted_posts_id= Comment.connection.quote_table_name('posts') + '.' + Comment.connection.quote_column_name('id')
+ assert_nothing_raised do
+ Comment.find(:all, :include => :post, :order => quoted_posts_id)
+ end
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
+ posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
+ assert_equal 1, posts.length
+ assert_equal [1], posts.collect { |p| p.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
+ posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
+ assert_equal 1, posts.length
+ assert_equal [2], posts.collect { |p| p.id }
+ end
+
+ def test_eager_association_loading_with_belongs_to_inferred_foreign_key_from_association_name
+ author_favorite = AuthorFavorite.find(:first, :include => :favorite_author)
+ assert_equal authors(:mary), assert_no_queries { author_favorite.favorite_author }
+ end
+
+ def test_eager_load_belongs_to_quotes_table_and_column_names
+ job = Job.find jobs(:unicyclist).id, :include => :ideal_reference
+ references(:michael_unicyclist)
+ assert_no_queries{ assert_equal references(:michael_unicyclist), job.ideal_reference}
+ end
+
+ def test_eager_load_has_one_quotes_table_and_column_names
+ michael = Person.find(people(:michael), :include => :favourite_reference)
+ references(:michael_unicyclist)
+ assert_no_queries{ assert_equal references(:michael_unicyclist), michael.favourite_reference}
+ end
+
+ def test_eager_load_has_many_quotes_table_and_column_names
+ michael = Person.find(people(:michael), :include => :references)
+ references(:michael_magician,:michael_unicyclist)
+ assert_no_queries{ assert_equal references(:michael_magician,:michael_unicyclist), michael.references.sort_by(&:id) }
+ end
+
+ def test_eager_load_has_many_through_quotes_table_and_column_names
+ michael = Person.find(people(:michael), :include => :jobs)
+ jobs(:magician, :unicyclist)
+ assert_no_queries{ assert_equal jobs(:unicyclist, :magician), michael.jobs.sort_by(&:id) }
+ end
+
+ def test_eager_load_has_many_with_string_keys
+ subscriptions = subscriptions(:webster_awdr, :webster_rfr)
+ subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions)
+ assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id)
+ end
+
+ def test_eager_load_has_many_through_with_string_keys
+ books = books(:awdr, :rfr)
+ subscriber = Subscriber.find(subscribers(:second).id, :include => :books)
+ assert_equal books, subscriber.books.sort_by(&:id)
+ end
+
+ def test_eager_load_belongs_to_with_string_keys
+ subscriber = subscribers(:second)
+ subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber)
+ assert_equal subscriber, subscription.subscriber
+ end
+
+ def test_eager_association_loading_with_explicit_join
+ posts = Post.find(:all, :include => :comments, :joins => "INNER JOIN authors ON posts.author_id = authors.id AND authors.name = 'Mary'", :limit => 1, :order => 'author_id')
+ assert_equal 1, posts.length
+ end
+
+ def test_eager_with_has_many_through
+ posts_with_comments = people(:michael).posts.find(:all, :include => :comments)
+ posts_with_author = people(:michael).posts.find(:all, :include => :author )
+ posts_with_comments_and_author = people(:michael).posts.find(:all, :include => [ :comments, :author ])
+ assert_equal 2, posts_with_comments.inject(0) { |sum, post| sum += post.comments.size }
+ assert_equal authors(:david), assert_no_queries { posts_with_author.first.author }
+ assert_equal authors(:david), assert_no_queries { posts_with_comments_and_author.first.author }
+ end
+
+ def test_eager_with_has_many_through_an_sti_join_model
+ author = Author.find(:first, :include => :special_post_comments, :order => 'authors.id')
+ assert_equal [comments(:does_it_hurt)], assert_no_queries { author.special_post_comments }
+ end
+
+ def test_eager_with_has_many_through_an_sti_join_model_with_conditions_on_both
+ author = Author.find(:first, :include => :special_nonexistant_post_comments, :order => 'authors.id')
+ assert_equal [], author.special_nonexistant_post_comments
+ end
+
+ def test_eager_with_has_many_through_join_model_with_conditions
+ assert_equal Author.find(:first, :include => :hello_post_comments,
+ :order => 'authors.id').hello_post_comments.sort_by(&:id),
+ Author.find(:first, :order => 'authors.id').hello_post_comments.sort_by(&:id)
+ end
+
+ def test_eager_with_has_many_through_join_model_with_conditions_on_top_level
+ assert_equal comments(:more_greetings), Author.find(authors(:david).id, :include => :comments_with_order_and_conditions).comments_with_order_and_conditions.first
+ end
+
+ def test_eager_with_has_many_through_join_model_with_include
+ author_comments = Author.find(authors(:david).id, :include => :comments_with_include).comments_with_include.to_a
+ assert_no_queries do
+ author_comments.first.post.title
+ end
+ end
+
+ def test_eager_with_has_many_and_limit
+ posts = Post.find(:all, :order => 'posts.id asc', :include => [ :author, :comments ], :limit => 2)
+ assert_equal 2, posts.size
+ assert_equal 3, posts.inject(0) { |sum, post| sum += post.comments.size }
+ end
+
+ def test_eager_with_has_many_and_limit_and_conditions
+ if current_adapter?(:OpenBaseAdapter)
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "FETCHBLOB(posts.body) = 'hello'", :order => "posts.id")
+ else
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.body = 'hello'", :order => "posts.id")
+ end
+ assert_equal 2, posts.size
+ assert_equal [4,5], posts.collect { |p| p.id }
+ end
+
+ def test_eager_with_has_many_and_limit_and_conditions_array
+ if current_adapter?(:OpenBaseAdapter)
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "FETCHBLOB(posts.body) = ?", 'hello' ], :order => "posts.id")
+ else
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "posts.body = ?", 'hello' ], :order => "posts.id")
+ end
+ assert_equal 2, posts.size
+ assert_equal [4,5], posts.collect { |p| p.id }
+ end
+
+ def test_eager_with_has_many_and_limit_and_conditions_array_on_the_eagers
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ assert_equal 2, posts.size
+
+ count = Post.count(:include => [ :author, :comments ], :limit => 2, :conditions => [ "authors.name = ?", 'David' ])
+ assert_equal count, posts.size
+ end
+
+ def test_eager_with_has_many_and_limit_ond_high_offset
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
+ assert_equal 0, posts.size
+ end
+
+ def test_count_eager_with_has_many_and_limit_ond_high_offset
+ posts = Post.count(:all, :include => [ :author, :comments ], :limit => 2, :offset => 10, :conditions => [ "authors.name = ?", 'David' ])
+ assert_equal 0, posts
+ end
+
+ def test_eager_with_has_many_and_limit_with_no_results
+ posts = Post.find(:all, :include => [ :author, :comments ], :limit => 2, :conditions => "posts.title = 'magic forest'")
+ assert_equal 0, posts.size
+ end
+
+ def test_eager_count_performed_on_a_has_many_association_with_multi_table_conditional
+ author = authors(:david)
+ author_posts_without_comments = author.posts.select { |post| post.comments.blank? }
+ assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null')
+ end
+
+ def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional
+ person = people(:michael)
+ person_posts_without_comments = person.posts.select { |post| post.comments.blank? }
+ assert_equal person_posts_without_comments.size, person.posts_with_no_comments.count
+ end
+
+ def test_eager_with_has_and_belongs_to_many_and_limit
+ posts = Post.find(:all, :include => :categories, :order => "posts.id", :limit => 3)
+ assert_equal 3, posts.size
+ assert_equal 2, posts[0].categories.size
+ assert_equal 1, posts[1].categories.size
+ assert_equal 0, posts[2].categories.size
+ assert posts[0].categories.include?(categories(:technology))
+ assert posts[1].categories.include?(categories(:general))
+ end
+
+ def test_eager_with_has_many_and_limit_and_conditions_on_the_eagers
+ posts = authors(:david).posts.find(:all,
+ :include => :comments,
+ :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
+ :limit => 2
+ )
+ assert_equal 2, posts.size
+
+ count = Post.count(
+ :include => [ :comments, :author ],
+ :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
+ :limit => 2
+ )
+ assert_equal count, posts.size
+ end
+
+ def test_eager_with_has_many_and_limit_and_scoped_conditions_on_the_eagers
+ posts = nil
+ Post.with_scope(:find => {
+ :include => :comments,
+ :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'"
+ }) do
+ posts = authors(:david).posts.find(:all, :limit => 2)
+ assert_equal 2, posts.size
+ end
+
+ Post.with_scope(:find => {
+ :include => [ :comments, :author ],
+ :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')"
+ }) do
+ count = Post.count(:limit => 2)
+ assert_equal count, posts.size
+ end
+ end
+
+ def test_eager_with_has_many_and_limit_and_scoped_and_explicit_conditions_on_the_eagers
+ Post.with_scope(:find => { :conditions => "1=1" }) do
+ posts = authors(:david).posts.find(:all,
+ :include => :comments,
+ :conditions => "comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'",
+ :limit => 2
+ )
+ assert_equal 2, posts.size
+
+ count = Post.count(
+ :include => [ :comments, :author ],
+ :conditions => "authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')",
+ :limit => 2
+ )
+ assert_equal count, posts.size
+ end
+ end
+
+ def test_eager_with_scoped_order_using_association_limiting_without_explicit_scope
+ posts_with_explicit_order = Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :order => 'posts.id DESC', :limit => 2)
+ posts_with_scoped_order = Post.with_scope(:find => {:order => 'posts.id DESC'}) do
+ Post.find(:all, :conditions => 'comments.id is not null', :include => :comments, :limit => 2)
+ end
+ assert_equal posts_with_explicit_order, posts_with_scoped_order
+ end
+
+ def test_eager_association_loading_with_habtm
+ posts = Post.find(:all, :include => :categories, :order => "posts.id")
+ assert_equal 2, posts[0].categories.size
+ assert_equal 1, posts[1].categories.size
+ assert_equal 0, posts[2].categories.size
+ assert posts[0].categories.include?(categories(:technology))
+ assert posts[1].categories.include?(categories(:general))
+ end
+
+ def test_eager_with_inheritance
+ posts = SpecialPost.find(:all, :include => [ :comments ])
+ end
+
+ def test_eager_has_one_with_association_inheritance
+ post = Post.find(4, :include => [ :very_special_comment ])
+ assert_equal "VerySpecialComment", post.very_special_comment.class.to_s
+ end
+
+ def test_eager_has_many_with_association_inheritance
+ post = Post.find(4, :include => [ :special_comments ])
+ post.special_comments.each do |special_comment|
+ assert_equal "SpecialComment", special_comment.class.to_s
+ end
+ end
+
+ def test_eager_habtm_with_association_inheritance
+ post = Post.find(6, :include => [ :special_categories ])
+ assert_equal 1, post.special_categories.size
+ post.special_categories.each do |special_category|
+ assert_equal "SpecialCategory", special_category.class.to_s
+ end
+ end
+
+ def test_eager_with_has_one_dependent_does_not_destroy_dependent
+ assert_not_nil companies(:first_firm).account
+ f = Firm.find(:first, :include => :account,
+ :conditions => ["companies.name = ?", "37signals"])
+ assert_not_nil f.account
+ assert_equal companies(:first_firm, :reload).account, f.account
+ end
+
+ def test_eager_with_multi_table_conditional_properly_counts_the_records_when_using_size
+ author = authors(:david)
+ posts_with_no_comments = author.posts.select { |post| post.comments.blank? }
+ assert_equal posts_with_no_comments.size, author.posts_with_no_comments.size
+ assert_equal posts_with_no_comments, author.posts_with_no_comments
+ end
+
+ def test_eager_with_invalid_association_reference
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ post = Post.find(6, :include=> :monkeys )
+ }
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ post = Post.find(6, :include=>[ :monkeys ])
+ }
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") {
+ post = Post.find(6, :include=>[ 'monkeys' ])
+ }
+ assert_raises(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") {
+ post = Post.find(6, :include=>[ :monkeys, :elephants ])
+ }
+ end
+
+ def find_all_ordered(className, include=nil)
+ className.find(:all, :order=>"#{className.table_name}.#{className.primary_key}", :include=>include)
+ end
+
+ def test_limited_eager_with_order
+ assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title)', :limit => 2, :offset => 1)
+ assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC', :limit => 2, :offset => 1)
+ end
+
+ def test_limited_eager_with_multiple_order_columns
+ assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1)
+ assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1)
+ end
+
+ def test_preload_with_interpolation
+ assert_equal [comments(:greetings)], Post.find(posts(:welcome).id, :include => :comments_with_interpolated_conditions).comments_with_interpolated_conditions
+ end
+
+ def test_polymorphic_type_condition
+ post = Post.find(posts(:thinking).id, :include => :taggings)
+ assert post.taggings.include?(taggings(:thinking_general))
+ post = SpecialPost.find(posts(:thinking).id, :include => :taggings)
+ assert post.taggings.include?(taggings(:thinking_general))
+ end
+
+ def test_eager_with_multiple_associations_with_same_table_has_many_and_habtm
+ # Eager includes of has many and habtm associations aren't necessarily sorted in the same way
+ def assert_equal_after_sort(item1, item2, item3 = nil)
+ assert_equal(item1.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id})
+ assert_equal(item3.sort{|a,b| a.id <=> b.id}, item2.sort{|a,b| a.id <=> b.id}) if item3
+ end
+ # Test regular association, association with conditions, association with
+ # STI, and association with conditions assured not to be true
+ post_types = [:posts, :other_posts, :special_posts]
+ # test both has_many and has_and_belongs_to_many
+ [Author, Category].each do |className|
+ d1 = find_all_ordered(className)
+ # test including all post types at once
+ d2 = find_all_ordered(className, post_types)
+ d1.each_index do |i|
+ assert_equal(d1[i], d2[i])
+ assert_equal_after_sort(d1[i].posts, d2[i].posts)
+ post_types[1..-1].each do |post_type|
+ # test including post_types together
+ d3 = find_all_ordered(className, [:posts, post_type])
+ assert_equal(d1[i], d3[i])
+ assert_equal_after_sort(d1[i].posts, d3[i].posts)
+ assert_equal_after_sort(d1[i].send(post_type), d2[i].send(post_type), d3[i].send(post_type))
+ end
+ end
+ end
+ end
+
+ def test_eager_with_multiple_associations_with_same_table_has_one
+ d1 = find_all_ordered(Firm)
+ d2 = find_all_ordered(Firm, :account)
+ d1.each_index do |i|
+ assert_equal(d1[i], d2[i])
+ assert_equal(d1[i].account, d2[i].account)
+ end
+ end
+
+ def test_eager_with_multiple_associations_with_same_table_belongs_to
+ firm_types = [:firm, :firm_with_basic_id, :firm_with_other_name, :firm_with_condition]
+ d1 = find_all_ordered(Client)
+ d2 = find_all_ordered(Client, firm_types)
+ d1.each_index do |i|
+ assert_equal(d1[i], d2[i])
+ firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) }
+ end
+ end
+ def test_eager_with_valid_association_as_string_not_symbol
+ assert_nothing_raised { Post.find(:all, :include => 'comments') }
+ end
+
+ def test_preconfigured_includes_with_belongs_to
+ author = posts(:welcome).author_with_posts
+ assert_no_queries {assert_equal 5, author.posts.size}
+ end
+
+ def test_preconfigured_includes_with_has_one
+ comment = posts(:sti_comments).very_special_comment_with_post
+ assert_no_queries {assert_equal posts(:sti_comments), comment.post}
+ end
+
+ def test_preconfigured_includes_with_has_many
+ posts = authors(:david).posts_with_comments
+ one = posts.detect { |p| p.id == 1 }
+ assert_no_queries do
+ assert_equal 5, posts.size
+ assert_equal 2, one.comments.size
+ end
+ end
+
+ def test_preconfigured_includes_with_habtm
+ posts = authors(:david).posts_with_categories
+ one = posts.detect { |p| p.id == 1 }
+ assert_no_queries do
+ assert_equal 5, posts.size
+ assert_equal 2, one.categories.size
+ end
+ end
+
+ def test_preconfigured_includes_with_has_many_and_habtm
+ posts = authors(:david).posts_with_comments_and_categories
+ one = posts.detect { |p| p.id == 1 }
+ assert_no_queries do
+ assert_equal 5, posts.size
+ assert_equal 2, one.comments.size
+ assert_equal 2, one.categories.size
+ end
+ end
+
+ def test_count_with_include
+ if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "len(comments.body) > 15")
+ elsif current_adapter?(:OpenBaseAdapter)
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(FETCHBLOB(comments.body)) > 15")
+ else
+ assert_equal 3, authors(:david).posts_with_comments.count(:conditions => "length(comments.body) > 15")
+ end
+ end
+
+ def test_load_with_sti_sharing_association
+ assert_queries(2) do #should not do 1 query per subclass
+ Comment.find :all, :include => :post
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/extension_test.rb b/vendor/rails/activerecord/test/cases/associations/extension_test.rb
new file mode 100644
index 0000000..5c01c3c
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/extension_test.rb
@@ -0,0 +1,47 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/project'
+require 'models/developer'
+
+class AssociationsExtensionsTest < ActiveRecord::TestCase
+ fixtures :projects, :developers, :developers_projects, :comments, :posts
+
+ def test_extension_on_has_many
+ assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
+ end
+
+ def test_extension_on_habtm
+ assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
+ end
+
+ def test_named_extension_on_habtm
+ assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
+ end
+
+ def test_named_two_extensions_on_habtm
+ assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_twice.find_most_recent
+ assert_equal projects(:active_record), developers(:david).projects_extended_by_name_twice.find_least_recent
+ end
+
+ def test_named_extension_and_block_on_habtm
+ assert_equal projects(:action_controller), developers(:david).projects_extended_by_name_and_block.find_most_recent
+ assert_equal projects(:active_record), developers(:david).projects_extended_by_name_and_block.find_least_recent
+ end
+
+ def test_marshalling_extensions
+ david = developers(:david)
+ assert_equal projects(:action_controller), david.projects.find_most_recent
+
+ david = Marshal.load(Marshal.dump(david))
+ assert_equal projects(:action_controller), david.projects.find_most_recent
+ end
+
+ def test_marshalling_named_extensions
+ david = developers(:david)
+ assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
+
+ david = Marshal.load(Marshal.dump(david))
+ assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
new file mode 100644
index 0000000..294b993
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -0,0 +1,684 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+require 'models/computer'
+require 'models/customer'
+require 'models/order'
+require 'models/categorization'
+require 'models/category'
+require 'models/post'
+require 'models/author'
+require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/person'
+require 'models/reader'
+require 'models/parrot'
+require 'models/pirate'
+require 'models/treasure'
+require 'models/price_estimate'
+require 'models/club'
+require 'models/member'
+require 'models/membership'
+require 'models/sponsor'
+
+class ProjectWithAfterCreateHook < ActiveRecord::Base
+ set_table_name 'projects'
+ has_and_belongs_to_many :developers,
+ :class_name => "DeveloperForProjectWithAfterCreateHook",
+ :join_table => "developers_projects",
+ :foreign_key => "project_id",
+ :association_foreign_key => "developer_id"
+
+ after_create :add_david
+
+ def add_david
+ david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
+ david.projects << self
+ end
+end
+
+class DeveloperForProjectWithAfterCreateHook < ActiveRecord::Base
+ set_table_name 'developers'
+ has_and_belongs_to_many :projects,
+ :class_name => "ProjectWithAfterCreateHook",
+ :join_table => "developers_projects",
+ :association_foreign_key => "project_id",
+ :foreign_key => "developer_id"
+end
+
+class ProjectWithSymbolsForKeys < ActiveRecord::Base
+ set_table_name 'projects'
+ has_and_belongs_to_many :developers,
+ :class_name => "DeveloperWithSymbolsForKeys",
+ :join_table => :developers_projects,
+ :foreign_key => :project_id,
+ :association_foreign_key => "developer_id"
+end
+
+class DeveloperWithSymbolsForKeys < ActiveRecord::Base
+ set_table_name 'developers'
+ has_and_belongs_to_many :projects,
+ :class_name => "ProjectWithSymbolsForKeys",
+ :join_table => :developers_projects,
+ :association_foreign_key => :project_id,
+ :foreign_key => "developer_id"
+end
+
+class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects,
+ :parrots, :pirates, :treasures, :price_estimates
+
+ def test_has_and_belongs_to_many
+ david = Developer.find(1)
+
+ assert !david.projects.empty?
+ assert_equal 2, david.projects.size
+
+ active_record = Project.find(1)
+ assert !active_record.developers.empty?
+ assert_equal 3, active_record.developers.size
+ assert active_record.developers.include?(david)
+ end
+
+ def test_triple_equality
+ assert !(Array === Developer.find(1).projects)
+ assert Developer.find(1).projects === Array
+ end
+
+ def test_adding_single
+ jamis = Developer.find(2)
+ jamis.projects.reload # causing the collection to load
+ action_controller = Project.find(2)
+ assert_equal 1, jamis.projects.size
+ assert_equal 1, action_controller.developers.size
+
+ jamis.projects << action_controller
+
+ assert_equal 2, jamis.projects.size
+ assert_equal 2, jamis.projects(true).size
+ assert_equal 2, action_controller.developers(true).size
+ end
+
+ def test_adding_type_mismatch
+ jamis = Developer.find(2)
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << nil }
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { jamis.projects << 1 }
+ end
+
+ def test_adding_from_the_project
+ jamis = Developer.find(2)
+ action_controller = Project.find(2)
+ action_controller.developers.reload
+ assert_equal 1, jamis.projects.size
+ assert_equal 1, action_controller.developers.size
+
+ action_controller.developers << jamis
+
+ assert_equal 2, jamis.projects(true).size
+ assert_equal 2, action_controller.developers.size
+ assert_equal 2, action_controller.developers(true).size
+ end
+
+ def test_adding_from_the_project_fixed_timestamp
+ jamis = Developer.find(2)
+ action_controller = Project.find(2)
+ action_controller.developers.reload
+ assert_equal 1, jamis.projects.size
+ assert_equal 1, action_controller.developers.size
+ updated_at = jamis.updated_at
+
+ action_controller.developers << jamis
+
+ assert_equal updated_at, jamis.updated_at
+ assert_equal 2, jamis.projects(true).size
+ assert_equal 2, action_controller.developers.size
+ assert_equal 2, action_controller.developers(true).size
+ end
+
+ def test_adding_multiple
+ aredridel = Developer.new("name" => "Aredridel")
+ aredridel.save
+ aredridel.projects.reload
+ aredridel.projects.push(Project.find(1), Project.find(2))
+ assert_equal 2, aredridel.projects.size
+ assert_equal 2, aredridel.projects(true).size
+ end
+
+ def test_adding_a_collection
+ aredridel = Developer.new("name" => "Aredridel")
+ aredridel.save
+ aredridel.projects.reload
+ aredridel.projects.concat([Project.find(1), Project.find(2)])
+ assert_equal 2, aredridel.projects.size
+ 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
+ aredridel = Developer.new("name" => "Aredridel")
+ aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")])
+ assert aredridel.new_record?
+ assert p.new_record?
+ assert aredridel.save
+ assert !aredridel.new_record?
+ assert_equal no_of_devels+1, Developer.count
+ assert_equal no_of_projects+1, Project.count
+ assert_equal 2, aredridel.projects.size
+ assert_equal 2, aredridel.projects(true).size
+ end
+
+ def test_habtm_saving_multiple_relationships
+ new_project = Project.new("name" => "Grimetime")
+ amount_of_developers = 4
+ developers = (0...amount_of_developers).collect {|i| Developer.create(:name => "JME #{i}") }.reverse
+
+ new_project.developer_ids = [developers[0].id, developers[1].id]
+ new_project.developers_with_callback_ids = [developers[2].id, developers[3].id]
+ assert new_project.save
+
+ new_project.reload
+ assert_equal amount_of_developers, new_project.developers.size
+ assert_equal developers, new_project.developers
+ end
+
+ def test_habtm_unique_order_preserved
+ assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).non_unique_developers
+ assert_equal developers(:poor_jamis, :jamis, :david), projects(:active_record).developers
+ end
+
+ def test_build
+ devel = Developer.find(1)
+ proj = assert_no_queries { devel.projects.build("name" => "Projekt") }
+ assert !devel.projects.loaded?
+
+ assert_equal devel.projects.last, proj
+ assert devel.projects.loaded?
+
+ assert proj.new_record?
+ devel.save
+ assert !proj.new_record?
+ assert_equal devel.projects.last, proj
+ assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
+ end
+
+ def test_build_by_new_record
+ devel = Developer.new(:name => "Marcel", :salary => 75000)
+ proj1 = devel.projects.build(:name => "Make bed")
+ proj2 = devel.projects.build(:name => "Lie in it")
+ assert_equal devel.projects.last, proj2
+ assert proj2.new_record?
+ devel.save
+ assert !devel.new_record?
+ assert !proj2.new_record?
+ assert_equal devel.projects.last, proj2
+ assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
+ end
+
+ def test_create
+ devel = Developer.find(1)
+ proj = devel.projects.create("name" => "Projekt")
+ assert !devel.projects.loaded?
+
+ assert_equal devel.projects.last, proj
+ assert devel.projects.loaded?
+
+ assert !proj.new_record?
+ assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated
+ end
+
+ def test_create_by_new_record
+ devel = Developer.new(:name => "Marcel", :salary => 75000)
+ proj1 = devel.projects.build(:name => "Make bed")
+ proj2 = devel.projects.build(:name => "Lie in it")
+ assert_equal devel.projects.last, proj2
+ assert proj2.new_record?
+ devel.save
+ assert !devel.new_record?
+ assert !proj2.new_record?
+ assert_equal devel.projects.last, proj2
+ assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated
+ end
+
+ def test_creation_respects_hash_condition
+ post = categories(:general).post_with_conditions.build(:body => '')
+
+ assert post.save
+ assert_equal 'Yet Another Testing Title', post.title
+
+ another_post = categories(:general).post_with_conditions.create(:body => '')
+
+ assert !another_post.new_record?
+ assert_equal 'Yet Another Testing Title', another_post.title
+ end
+
+ def test_uniq_after_the_fact
+ dev = developers(:jamis)
+ dev.projects << projects(:active_record)
+ dev.projects << projects(:active_record)
+
+ assert_equal 3, dev.projects.size
+ assert_equal 1, dev.projects.uniq.size
+ end
+
+ def test_uniq_before_the_fact
+ projects(:active_record).developers << developers(:jamis)
+ projects(:active_record).developers << developers(:david)
+ assert_equal 3, projects(:active_record, :reload).developers.size
+ end
+
+ def test_deleting
+ david = Developer.find(1)
+ active_record = Project.find(1)
+ david.projects.reload
+ assert_equal 2, david.projects.size
+ assert_equal 3, active_record.developers.size
+
+ david.projects.delete(active_record)
+
+ assert_equal 1, david.projects.size
+ assert_equal 1, david.projects(true).size
+ assert_equal 2, active_record.developers(true).size
+ end
+
+ def test_deleting_array
+ david = Developer.find(1)
+ david.projects.reload
+ david.projects.delete(Project.find(:all))
+ assert_equal 0, david.projects.size
+ assert_equal 0, david.projects(true).size
+ end
+
+ def test_deleting_with_sql
+ david = Developer.find(1)
+ active_record = Project.find(1)
+ active_record.developers.reload
+ assert_equal 3, active_record.developers_by_sql.size
+
+ active_record.developers_by_sql.delete(david)
+ assert_equal 2, active_record.developers_by_sql(true).size
+ end
+
+ def test_deleting_array_with_sql
+ active_record = Project.find(1)
+ active_record.developers.reload
+ assert_equal 3, active_record.developers_by_sql.size
+
+ active_record.developers_by_sql.delete(Developer.find(:all))
+ assert_equal 0, active_record.developers_by_sql(true).size
+ end
+
+ def test_deleting_all
+ david = Developer.find(1)
+ david.projects.reload
+ david.projects.clear
+ assert_equal 0, david.projects.size
+ assert_equal 0, david.projects(true).size
+ end
+
+ def test_removing_associations_on_destroy
+ david = DeveloperWithBeforeDestroyRaise.find(1)
+ assert !david.projects.empty?
+ assert_nothing_raised { david.destroy }
+ assert david.projects.empty?
+ 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_destroy_all
+ david = Developer.find(1)
+ david.projects.reload
+ assert !david.projects.empty?
+ david.projects.destroy_all
+ assert david.projects.empty?
+ assert david.projects(true).empty?
+ end
+
+ def test_deprecated_push_with_attributes_was_removed
+ jamis = developers(:jamis)
+ assert_raise(NoMethodError) do
+ jamis.projects.push_with_attributes(projects(:action_controller), :joined_on => Date.today)
+ end
+ end
+
+ def test_associations_with_conditions
+ assert_equal 3, projects(:active_record).developers.size
+ assert_equal 1, projects(:active_record).developers_named_david.size
+ assert_equal 1, projects(:active_record).developers_named_david_with_hash_conditions.size
+
+ assert_equal developers(:david), projects(:active_record).developers_named_david.find(developers(:david).id)
+ assert_equal developers(:david), projects(:active_record).developers_named_david_with_hash_conditions.find(developers(:david).id)
+ assert_equal developers(:david), projects(:active_record).salaried_developers.find(developers(:david).id)
+
+ projects(:active_record).developers_named_david.clear
+ assert_equal 2, projects(:active_record, :reload).developers.size
+ end
+
+ def test_find_in_association
+ # Using sql
+ assert_equal developers(:david), projects(:active_record).developers.find(developers(:david).id), "SQL find"
+
+ # Using ruby
+ active_record = projects(:active_record)
+ active_record.developers.reload
+ assert_equal developers(:david), active_record.developers.find(developers(:david).id), "Ruby find"
+ end
+
+ def test_include_uses_array_include_after_loaded
+ project = projects(:active_record)
+ project.developers.class # force load target
+
+ developer = project.developers.first
+
+ assert_no_queries do
+ assert project.developers.loaded?
+ assert project.developers.include?(developer)
+ end
+ end
+
+ def test_include_checks_if_record_exists_if_target_not_loaded
+ project = projects(:active_record)
+ developer = project.developers.first
+
+ project.reload
+ assert ! project.developers.loaded?
+ assert_queries(1) do
+ assert project.developers.include?(developer)
+ end
+ assert ! project.developers.loaded?
+ end
+
+ def test_include_returns_false_for_non_matching_record_to_verify_scoping
+ project = projects(:active_record)
+ developer = Developer.create :name => "Bryan", :salary => 50_000
+
+ assert ! project.developers.loaded?
+ assert ! project.developers.include?(developer)
+ end
+
+ def test_find_in_association_with_custom_finder_sql
+ assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id), "SQL find"
+
+ active_record = projects(:active_record)
+ active_record.developers_with_finder_sql.reload
+ assert_equal developers(:david), active_record.developers_with_finder_sql.find(developers(:david).id), "Ruby find"
+ end
+
+ def test_find_in_association_with_custom_finder_sql_and_string_id
+ assert_equal developers(:david), projects(:active_record).developers_with_finder_sql.find(developers(:david).id.to_s), "SQL find"
+ end
+
+ def test_find_with_merged_options
+ assert_equal 1, projects(:active_record).limited_developers.size
+ assert_equal 1, projects(:active_record).limited_developers.find(:all).size
+ assert_equal 3, projects(:active_record).limited_developers.find(:all, :limit => nil).size
+ end
+
+ def test_dynamic_find_should_respect_association_order
+ # Developers are ordered 'name DESC, id DESC'
+ low_id_jamis = developers(:jamis)
+ middle_id_jamis = developers(:poor_jamis)
+ high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
+
+ assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'")
+ assert_equal high_id_jamis, projects(:active_record).developers.find_by_name('Jamis')
+ end
+
+ def test_dynamic_find_order_should_override_association_order
+ # Developers are ordered 'name DESC, id DESC'
+ low_id_jamis = developers(:jamis)
+ middle_id_jamis = developers(:poor_jamis)
+ high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
+
+ assert_equal low_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'", :order => 'id')
+ assert_equal low_id_jamis, projects(:active_record).developers.find_by_name('Jamis', :order => 'id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_order
+ # Developers are ordered 'name DESC, id DESC'
+ low_id_jamis = developers(:jamis)
+ middle_id_jamis = developers(:poor_jamis)
+ high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
+
+ assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'")
+ assert_equal [high_id_jamis, middle_id_jamis, low_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis')
+ end
+
+ def test_dynamic_find_all_order_should_override_association_order
+ # Developers are ordered 'name DESC, id DESC'
+ low_id_jamis = developers(:jamis)
+ middle_id_jamis = developers(:poor_jamis)
+ high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis')
+
+ assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find(:all, :conditions => "name = 'Jamis'", :order => 'id')
+ assert_equal [low_id_jamis, middle_id_jamis, high_id_jamis], projects(:active_record).developers.find_all_by_name('Jamis', :order => 'id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit
+ assert_equal 1, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'").length
+ assert_equal 1, projects(:active_record).limited_developers.find_all_by_name('Jamis').length
+ end
+
+ def test_dynamic_find_all_order_should_override_association_limit
+ assert_equal 2, projects(:active_record).limited_developers.find(:all, :conditions => "name = 'Jamis'", :limit => 9_000).length
+ assert_equal 2, projects(:active_record).limited_developers.find_all_by_name('Jamis', :limit => 9_000).length
+ end
+
+ def test_dynamic_find_all_should_respect_readonly_access
+ projects(:active_record).readonly_developers.each { |d| assert_raise(ActiveRecord::ReadOnlyRecord) { d.save! } if d.valid?}
+ projects(:active_record).readonly_developers.each { |d| d.readonly? }
+ end
+
+ def test_new_with_values_in_collection
+ jamis = DeveloperForProjectWithAfterCreateHook.find_by_name('Jamis')
+ david = DeveloperForProjectWithAfterCreateHook.find_by_name('David')
+ project = ProjectWithAfterCreateHook.new(:name => "Cooking with Bertie")
+ project.developers << jamis
+ project.save!
+ project.reload
+
+ assert project.developers.include?(jamis)
+ assert project.developers.include?(david)
+ end
+
+ def test_find_in_association_with_options
+ developers = projects(:active_record).developers.find(:all)
+ assert_equal 3, developers.size
+
+ assert_equal developers(:poor_jamis), projects(:active_record).developers.find(:first, :conditions => "salary < 10000")
+ assert_equal developers(:jamis), projects(:active_record).developers.find(:first, :order => "salary DESC")
+ end
+
+ def test_replace_with_less
+ david = developers(:david)
+ david.projects = [projects(:action_controller)]
+ assert david.save
+ assert_equal 1, david.projects.length
+ end
+
+ def test_replace_with_new
+ david = developers(:david)
+ david.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
+ david.save
+ assert_equal 2, david.projects.length
+ assert !david.projects.include?(projects(:active_record))
+ end
+
+ def test_replace_on_new_object
+ new_developer = Developer.new("name" => "Matz")
+ new_developer.projects = [projects(:action_controller), Project.new("name" => "ActionWebSearch")]
+ new_developer.save
+ assert_equal 2, new_developer.projects.length
+ end
+
+ def test_consider_type
+ developer = Developer.find(:first)
+ special_project = SpecialProject.create("name" => "Special Project")
+
+ other_project = developer.projects.first
+ developer.special_projects << special_project
+ developer.reload
+
+ assert developer.projects.include?(special_project)
+ assert developer.special_projects.include?(special_project)
+ assert !developer.special_projects.include?(other_project)
+ end
+
+ def test_update_attributes_after_push_without_duplicate_join_table_rows
+ developer = Developer.new("name" => "Kano")
+ project = SpecialProject.create("name" => "Special Project")
+ assert developer.save
+ developer.projects << project
+ developer.update_attribute("name", "Bruza")
+ assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
+ SELECT count(*) FROM developers_projects
+ WHERE project_id = #{project.id}
+ AND developer_id = #{developer.id}
+ end_sql
+ end
+
+ def test_updating_attributes_on_non_rich_associations
+ welcome = categories(:technology).posts.first
+ welcome.title = "Something else"
+ assert welcome.save!
+ end
+
+ def test_habtm_respects_select
+ categories(:technology).select_testing_posts(true).each do |o|
+ assert_respond_to o, :correctness_marker
+ end
+ 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_raises(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
+
+ def test_join_with_group
+ group = Developer.columns.inject([]) do |g, c|
+ g << "developers.#{c.name}"
+ g << "developers_projects_2.#{c.name}"
+ end
+ Project.columns.each { |c| group << "projects.#{c.name}" }
+
+ assert_equal 3, Developer.find(:all, :include => {:projects => :developers}, :conditions => 'developers_projects_join.joined_on IS NOT NULL', :group => group.join(",")).size
+ end
+
+ def test_get_ids
+ assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort
+ assert_equal [projects(:active_record).id], developers(:jamis).project_ids
+ end
+
+ def test_assign_ids
+ developer = Developer.new("name" => "Joe")
+ developer.project_ids = projects(:active_record, :action_controller).map(&:id)
+ developer.save
+ developer.reload
+ assert_equal 2, developer.projects.length
+ assert_equal projects(:active_record), developer.projects[0]
+ assert_equal projects(:action_controller), developer.projects[1]
+ end
+
+ def test_assign_ids_ignoring_blanks
+ developer = Developer.new("name" => "Joe")
+ developer.project_ids = [projects(:active_record).id, nil, projects(:action_controller).id, '']
+ developer.save
+ developer.reload
+ assert_equal 2, developer.projects.length
+ assert_equal projects(:active_record), developer.projects[0]
+ assert_equal projects(:action_controller), developer.projects[1]
+ end
+
+ def test_select_limited_ids_list
+ # Set timestamps
+ Developer.transaction do
+ Developer.find(:all, :order => 'id').each_with_index do |record, i|
+ record.update_attributes(:created_at => 5.years.ago + (i * 5.minutes))
+ end
+ end
+
+ join_base = ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase.new(Project)
+ join_dep = ActiveRecord::Associations::ClassMethods::JoinDependency.new(join_base, :developers, nil)
+ projects = Project.send(:select_limited_ids_list, {:order => 'developers.created_at'}, join_dep)
+ assert !projects.include?("'"), projects
+ assert_equal %w(1 2), projects.scan(/\d/).sort
+ end
+
+ def test_scoped_find_on_through_association_doesnt_return_read_only_records
+ tag = Post.find(1).tags.find_by_name("General")
+
+ assert_nothing_raised do
+ tag.save!
+ end
+ end
+
+ def test_has_many_through_polymorphic_has_manys_works
+ assert_equal [10, 20].to_set, pirates(:redbeard).treasure_estimates.map(&:price).to_set
+ end
+
+ def test_symbols_as_keys
+ developer = DeveloperWithSymbolsForKeys.new(:name => 'David')
+ project = ProjectWithSymbolsForKeys.new(:name => 'Rails Testing')
+ project.developers << developer
+ project.save!
+
+ assert_equal 1, project.developers.size
+ assert_equal 1, developer.projects.size
+ assert_equal developer, project.developers.find(:first)
+ assert_equal project, developer.projects.find(:first)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb
new file mode 100644
index 0000000..dbfa025
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -0,0 +1,932 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+require 'models/category'
+require 'models/post'
+require 'models/author'
+require 'models/comment'
+require 'models/person'
+require 'models/reader'
+
+class HasManyAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :categories, :companies, :developers, :projects,
+ :developers_projects, :topics, :authors, :comments, :author_addresses,
+ :people, :posts
+
+ def setup
+ Client.destroyed_client_ids.clear
+ end
+
+ def force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.each {|f| }
+ end
+
+ def test_counting_with_counter_sql
+ assert_equal 2, Firm.find(:first).clients.count
+ end
+
+ def test_counting
+ assert_equal 2, Firm.find(:first).plain_clients.count
+ end
+
+ def test_counting_with_empty_hash_conditions
+ assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {})
+ end
+
+ def test_counting_with_single_conditions
+ assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
+ end
+
+ def test_counting_with_single_hash
+ assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => '1=1')
+ end
+
+ def test_counting_with_column_name_and_hash
+ assert_equal 2, Firm.find(:first).plain_clients.count(:all, :conditions => '1=1')
+ end
+
+ def test_finding
+ assert_equal 2, Firm.find(:first).clients.length
+ end
+
+ def test_find_with_blank_conditions
+ [[], {}, nil, ""].each do |blank|
+ assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size
+ end
+ end
+
+ def test_find_many_with_merged_options
+ assert_equal 1, companies(:first_firm).limited_clients.size
+ assert_equal 1, companies(:first_firm).limited_clients.find(:all).size
+ assert_equal 2, companies(:first_firm).limited_clients.find(:all, :limit => nil).size
+ end
+
+ def test_dynamic_find_should_respect_association_order
+ assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'")
+ assert_equal companies(:second_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client')
+ end
+
+ def test_dynamic_find_order_should_override_association_order
+ assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find(:first, :conditions => "type = 'Client'", :order => 'id')
+ assert_equal companies(:first_client), companies(:first_firm).clients_sorted_desc.find_by_type('Client', :order => 'id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_order
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'")
+ assert_equal [companies(:second_client), companies(:first_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client')
+ end
+
+ def test_dynamic_find_all_order_should_override_association_order
+ assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find(:all, :conditions => "type = 'Client'", :order => 'id')
+ assert_equal [companies(:first_client), companies(:second_client)], companies(:first_firm).clients_sorted_desc.find_all_by_type('Client', :order => 'id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit
+ assert_equal 1, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'").length
+ assert_equal 1, companies(:first_firm).limited_clients.find_all_by_type('Client').length
+ end
+
+ def test_dynamic_find_all_limit_should_override_association_limit
+ assert_equal 2, companies(:first_firm).limited_clients.find(:all, :conditions => "type = 'Client'", :limit => 9_000).length
+ assert_equal 2, companies(:first_firm).limited_clients.find_all_by_type('Client', :limit => 9_000).length
+ end
+
+ def test_dynamic_find_all_should_respect_readonly_access
+ companies(:first_firm).readonly_clients.find(:all).each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
+ companies(:first_firm).readonly_clients.find(:all).each { |c| assert c.readonly? }
+ end
+
+ def test_cant_save_has_many_readonly_association
+ authors(:david).readonly_comments.each { |c| assert_raise(ActiveRecord::ReadOnlyRecord) { c.save! } }
+ authors(:david).readonly_comments.each { |c| assert c.readonly? }
+ end
+
+ def test_triple_equality
+ assert !(Array === Firm.find(:first).clients)
+ assert Firm.find(:first).clients === Array
+ end
+
+ def test_finding_default_orders
+ assert_equal "Summit", Firm.find(:first).clients.first.name
+ end
+
+ def test_finding_with_different_class_name_and_order
+ assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name
+ end
+
+ def test_finding_with_foreign_key
+ assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name
+ end
+
+ def test_finding_with_condition
+ assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name
+ end
+
+ def test_finding_with_condition_hash
+ assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name
+ end
+
+ def test_finding_using_sql
+ firm = Firm.find(:first)
+ first_client = firm.clients_using_sql.first
+ assert_not_nil first_client
+ assert_equal "Microsoft", first_client.name
+ assert_equal 1, firm.clients_using_sql.size
+ assert_equal 1, Firm.find(:first).clients_using_sql.size
+ end
+
+ def test_counting_using_sql
+ assert_equal 1, Firm.find(:first).clients_using_counter_sql.size
+ assert Firm.find(:first).clients_using_counter_sql.any?
+ assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size
+ assert !Firm.find(:first).clients_using_zero_counter_sql.any?
+ end
+
+ def test_counting_non_existant_items_using_sql
+ assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size
+ end
+
+ def test_belongs_to_sanity
+ c = Client.new
+ assert_nil c.firm
+
+ if c.firm
+ assert false, "belongs_to failed if check"
+ end
+
+ unless c.firm
+ else
+ assert false, "belongs_to failed unless check"
+ end
+ end
+
+ def test_find_ids
+ firm = Firm.find(:first)
+
+ assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find }
+
+ client = firm.clients.find(2)
+ assert_kind_of Client, client
+
+ client_ary = firm.clients.find([2])
+ assert_kind_of Array, client_ary
+ assert_equal client, client_ary.first
+
+ client_ary = firm.clients.find(2, 3)
+ assert_kind_of Array, client_ary
+ assert_equal 2, client_ary.size
+ assert_equal client, client_ary.first
+
+ assert_raises(ActiveRecord::RecordNotFound) { firm.clients.find(2, 99) }
+ end
+
+ def test_find_string_ids_when_using_finder_sql
+ firm = Firm.find(:first)
+
+ client = firm.clients_using_finder_sql.find("2")
+ assert_kind_of Client, client
+
+ client_ary = firm.clients_using_finder_sql.find(["2"])
+ assert_kind_of Array, client_ary
+ assert_equal client, client_ary.first
+
+ client_ary = firm.clients_using_finder_sql.find("2", "3")
+ assert_kind_of Array, client_ary
+ assert_equal 2, client_ary.size
+ assert client_ary.include?(client)
+ end
+
+ def test_find_all
+ firm = Firm.find(:first)
+ assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length
+ assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length
+ end
+
+ def test_find_all_sanitized
+ firm = Firm.find(:first)
+ summit = firm.clients.find(:all, :conditions => "name = 'Summit'")
+ assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"])
+ assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }])
+ end
+
+ def test_find_first
+ firm = Firm.find(:first)
+ client2 = Client.find(2)
+ assert_equal firm.clients.first, firm.clients.find(:first)
+ assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'")
+ end
+
+ def test_find_first_sanitized
+ firm = Firm.find(:first)
+ client2 = Client.find(2)
+ assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'])
+ assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }])
+ end
+
+ def test_find_in_collection
+ assert_equal Client.find(2).name, companies(:first_firm).clients.find(2).name
+ assert_raises(ActiveRecord::RecordNotFound) { companies(:first_firm).clients.find(6) }
+ end
+
+ def test_find_grouped
+ all_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1")
+ grouped_clients_of_firm1 = Client.find(:all, :conditions => "firm_id = 1", :group => "firm_id", :select => 'firm_id, count(id) as clients_count')
+ assert_equal 2, all_clients_of_firm1.size
+ assert_equal 1, grouped_clients_of_firm1.size
+ end
+
+ def test_adding
+ force_signal37_to_load_all_clients_of_firm
+ natural = Client.new("name" => "Natural Company")
+ companies(:first_firm).clients_of_firm << natural
+ assert_equal 2, companies(:first_firm).clients_of_firm.size # checking via the collection
+ assert_equal 2, companies(:first_firm).clients_of_firm(true).size # checking using the db
+ assert_equal natural, companies(:first_firm).clients_of_firm.last
+ end
+
+ def test_adding_using_create
+ first_firm = companies(:first_firm)
+ assert_equal 2, first_firm.plain_clients.size
+ natural = first_firm.plain_clients.create(:name => "Natural Company")
+ assert_equal 3, first_firm.plain_clients.length
+ assert_equal 3, first_firm.plain_clients.size
+ end
+
+ def test_create_with_bang_on_has_many_when_parent_is_new_raises
+ assert_raises(ActiveRecord::RecordNotSaved) do
+ firm = Firm.new
+ firm.plain_clients.create! :name=>"Whoever"
+ end
+ end
+
+ def test_regular_create_on_has_many_when_parent_is_new_raises
+ assert_raises(ActiveRecord::RecordNotSaved) do
+ firm = Firm.new
+ firm.plain_clients.create :name=>"Whoever"
+ end
+ end
+
+ def test_create_with_bang_on_has_many_raises_when_record_not_saved
+ assert_raises(ActiveRecord::RecordInvalid) do
+ firm = Firm.find(:first)
+ firm.plain_clients.create!
+ end
+ end
+
+ def test_create_with_bang_on_habtm_when_parent_is_new_raises
+ assert_raises(ActiveRecord::RecordNotSaved) do
+ Developer.new("name" => "Aredridel").projects.create!
+ end
+ end
+
+ def test_adding_a_mismatch_class
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << nil }
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << 1 }
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).clients_of_firm << Topic.find(1) }
+ end
+
+ def test_adding_a_collection
+ force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
+ assert_equal 3, companies(:first_firm).clients_of_firm.size
+ assert_equal 3, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_adding_before_save
+ no_of_firms = Firm.count
+ no_of_clients = Client.count
+
+ new_firm = Firm.new("name" => "A New Firm, Inc")
+ c = Client.new("name" => "Apple")
+
+ new_firm.clients_of_firm.push Client.new("name" => "Natural Company")
+ assert_equal 1, new_firm.clients_of_firm.size
+ new_firm.clients_of_firm << c
+ assert_equal 2, new_firm.clients_of_firm.size
+
+ assert_equal no_of_firms, Firm.count # Firm was not saved to database.
+ assert_equal no_of_clients, Client.count # Clients were not saved to database.
+ assert new_firm.save
+ assert !new_firm.new_record?
+ assert !c.new_record?
+ assert_equal new_firm, c.firm
+ assert_equal no_of_firms+1, Firm.count # Firm was saved to database.
+ assert_equal no_of_clients+2, Client.count # Clients were saved to database.
+
+ assert_equal 2, new_firm.clients_of_firm.size
+ assert_equal 2, new_firm.clients_of_firm(true).size
+ end
+
+ def test_invalid_adding
+ firm = Firm.find(1)
+ assert !(firm.clients_of_firm << c = Client.new)
+ assert c.new_record?
+ assert !firm.valid?
+ assert !firm.save
+ assert c.new_record?
+ end
+
+ def test_invalid_adding_before_save
+ no_of_firms = Firm.count
+ no_of_clients = Client.count
+ new_firm = Firm.new("name" => "A New Firm, Inc")
+ new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")])
+ assert c.new_record?
+ assert !c.valid?
+ assert !new_firm.valid?
+ assert !new_firm.save
+ assert c.new_record?
+ assert new_firm.new_record?
+ end
+
+ def test_build
+ company = companies(:first_firm)
+ new_client = assert_no_queries { company.clients_of_firm.build("name" => "Another Client") }
+ assert !company.clients_of_firm.loaded?
+
+ assert_equal "Another Client", new_client.name
+ assert new_client.new_record?
+ assert_equal new_client, company.clients_of_firm.last
+ company.name += '-changed'
+ assert_queries(2) { assert company.save }
+ assert !new_client.new_record?
+ assert_equal 2, company.clients_of_firm(true).size
+ end
+
+ def test_build_many
+ company = companies(:first_firm)
+ new_clients = assert_no_queries { company.clients_of_firm.build([{"name" => "Another Client"}, {"name" => "Another Client II"}]) }
+
+ assert_equal 2, new_clients.size
+ company.name += '-changed'
+ assert_queries(3) { assert company.save }
+ assert_equal 3, company.clients_of_firm(true).size
+ end
+
+ def test_build_followed_by_save_does_not_load_target
+ new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client")
+ assert companies(:first_firm).save
+ assert !companies(:first_firm).clients_of_firm.loaded?
+ end
+
+ def test_build_without_loading_association
+ first_topic = topics(:first)
+ Reply.column_names
+
+ assert_equal 1, first_topic.replies.length
+
+ assert_no_queries do
+ first_topic.replies.build(:title => "Not saved", :content => "Superstars")
+ assert_equal 2, first_topic.replies.size
+ end
+
+ assert_equal 2, first_topic.replies.to_ary.size
+ end
+
+ def test_create_without_loading_association
+ first_firm = companies(:first_firm)
+ Firm.column_names
+ Client.column_names
+
+ assert_equal 1, first_firm.clients_of_firm.size
+ first_firm.clients_of_firm.reset
+
+ assert_queries(1) do
+ first_firm.clients_of_firm.create(:name => "Superstars")
+ end
+
+ assert_equal 2, first_firm.clients_of_firm.size
+ end
+
+ def test_invalid_build
+ new_client = companies(:first_firm).clients_of_firm.build
+ assert new_client.new_record?
+ assert !new_client.valid?
+ assert_equal new_client, companies(:first_firm).clients_of_firm.last
+ assert !companies(:first_firm).save
+ assert new_client.new_record?
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_create
+ force_signal37_to_load_all_clients_of_firm
+ new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ assert !new_client.new_record?
+ assert_equal new_client, companies(:first_firm).clients_of_firm.last
+ assert_equal new_client, companies(:first_firm).clients_of_firm(true).last
+ end
+
+ def test_create_many
+ companies(:first_firm).clients_of_firm.create([{"name" => "Another Client"}, {"name" => "Another Client II"}])
+ assert_equal 3, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_create_followed_by_save_does_not_load_target
+ new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ assert companies(:first_firm).save
+ assert !companies(:first_firm).clients_of_firm.loaded?
+ end
+
+ def test_find_or_initialize
+ the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client")
+ assert_equal companies(:first_firm).id, the_client.firm_id
+ assert_equal "Yet another client", the_client.name
+ assert the_client.new_record?
+ end
+
+ def test_find_or_create
+ number_of_clients = companies(:first_firm).clients.size
+ the_client = companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ assert_equal the_client, companies(:first_firm).clients.find_or_create_by_name("Yet another client")
+ assert_equal number_of_clients + 1, companies(:first_firm, :reload).clients.size
+ end
+
+ def test_deleting
+ force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.delete(companies(:first_firm).clients_of_firm.first)
+ assert_equal 0, companies(:first_firm).clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_deleting_before_save
+ new_firm = Firm.new("name" => "A New Firm, Inc.")
+ new_client = new_firm.clients_of_firm.build("name" => "Another Client")
+ assert_equal 1, new_firm.clients_of_firm.size
+ new_firm.clients_of_firm.delete(new_client)
+ assert_equal 0, new_firm.clients_of_firm.size
+ end
+
+ def test_deleting_a_collection
+ force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ assert_equal 2, companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.delete([companies(:first_firm).clients_of_firm[0], companies(:first_firm).clients_of_firm[1]])
+ assert_equal 0, companies(:first_firm).clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_delete_all
+ force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ assert_equal 2, companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.delete_all
+ assert_equal 0, companies(:first_firm).clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_delete_all_with_not_yet_loaded_association_collection
+ force_signal37_to_load_all_clients_of_firm
+ companies(:first_firm).clients_of_firm.create("name" => "Another Client")
+ assert_equal 2, companies(:first_firm).clients_of_firm.size
+ companies(:first_firm).clients_of_firm.reset
+ companies(:first_firm).clients_of_firm.delete_all
+ assert_equal 0, companies(:first_firm).clients_of_firm.size
+ assert_equal 0, companies(:first_firm).clients_of_firm(true).size
+ end
+
+ def test_clearing_an_association_collection
+ firm = companies(:first_firm)
+ client_id = firm.clients_of_firm.first.id
+ assert_equal 1, firm.clients_of_firm.size
+
+ firm.clients_of_firm.clear
+
+ assert_equal 0, firm.clients_of_firm.size
+ assert_equal 0, firm.clients_of_firm(true).size
+ assert_equal [], Client.destroyed_client_ids[firm.id]
+
+ # Should not be destroyed since the association is not dependent.
+ assert_nothing_raised do
+ assert Client.find(client_id).firm.nil?
+ end
+ end
+
+ def test_clearing_a_dependent_association_collection
+ firm = companies(:first_firm)
+ client_id = firm.dependent_clients_of_firm.first.id
+ assert_equal 1, firm.dependent_clients_of_firm.size
+
+ # :dependent means destroy is called on each client
+ firm.dependent_clients_of_firm.clear
+
+ assert_equal 0, firm.dependent_clients_of_firm.size
+ assert_equal 0, firm.dependent_clients_of_firm(true).size
+ assert_equal [client_id], Client.destroyed_client_ids[firm.id]
+
+ # Should be destroyed since the association is dependent.
+ assert Client.find_by_id(client_id).nil?
+ end
+
+ def test_clearing_an_exclusively_dependent_association_collection
+ firm = companies(:first_firm)
+ client_id = firm.exclusively_dependent_clients_of_firm.first.id
+ assert_equal 1, firm.exclusively_dependent_clients_of_firm.size
+
+ assert_equal [], Client.destroyed_client_ids[firm.id]
+
+ # :exclusively_dependent means each client is deleted directly from
+ # the database without looping through them calling destroy.
+ firm.exclusively_dependent_clients_of_firm.clear
+
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm.size
+ assert_equal 0, firm.exclusively_dependent_clients_of_firm(true).size
+ # no destroy-filters should have been called
+ assert_equal [], Client.destroyed_client_ids[firm.id]
+
+ # Should be destroyed since the association is exclusively dependent.
+ assert Client.find_by_id(client_id).nil?
+ end
+
+ def test_dependent_association_respects_optional_conditions_on_delete
+ firm = companies(:odegy)
+ Client.create(:client_of => firm.id, :name => "BigShot Inc.")
+ Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
+ # only one of two clients is included in the association due to the :conditions key
+ assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, firm.dependent_conditional_clients_of_firm.size
+ firm.destroy
+ # only the correctly associated client should have been deleted
+ assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ end
+
+ def test_dependent_association_respects_optional_sanitized_conditions_on_delete
+ firm = companies(:odegy)
+ Client.create(:client_of => firm.id, :name => "BigShot Inc.")
+ Client.create(:client_of => firm.id, :name => "SmallTime Inc.")
+ # only one of two clients is included in the association due to the :conditions key
+ assert_equal 2, Client.find_all_by_client_of(firm.id).size
+ assert_equal 1, firm.dependent_sanitized_conditional_clients_of_firm.size
+ firm.destroy
+ # only the correctly associated client should have been deleted
+ assert_equal 1, Client.find_all_by_client_of(firm.id).size
+ end
+
+ def test_creation_respects_hash_condition
+ ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build
+
+ assert ms_client.save
+ assert_equal 'Microsoft', ms_client.name
+
+ another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create
+
+ assert !another_ms_client.new_record?
+ assert_equal 'Microsoft', another_ms_client.name
+ end
+
+ def test_dependent_delete_and_destroy_with_belongs_to
+ author_address = author_addresses(:david_address)
+ assert_equal [], AuthorAddress.destroyed_author_address_ids[authors(:david).id]
+
+ assert_difference "AuthorAddress.count", -2 do
+ authors(:david).destroy
+ end
+
+ assert_equal [author_address.id], AuthorAddress.destroyed_author_address_ids[authors(:david).id]
+ end
+
+ def test_invalid_belongs_to_dependent_option_raises_exception
+ assert_raises ArgumentError do
+ Author.belongs_to :special_author_address, :dependent => :nullify
+ end
+ end
+
+ def test_clearing_without_initial_access
+ firm = companies(:first_firm)
+
+ firm.clients_of_firm.clear
+
+ assert_equal 0, firm.clients_of_firm.size
+ assert_equal 0, firm.clients_of_firm(true).size
+ end
+
+ def test_deleting_a_item_which_is_not_in_the_collection
+ force_signal37_to_load_all_clients_of_firm
+ summit = Client.find_by_name('Summit')
+ companies(:first_firm).clients_of_firm.delete(summit)
+ assert_equal 1, companies(:first_firm).clients_of_firm.size
+ assert_equal 1, companies(:first_firm).clients_of_firm(true).size
+ assert_equal 2, summit.client_of
+ end
+
+ def test_deleting_type_mismatch
+ david = Developer.find(1)
+ david.projects.reload
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(1) }
+ end
+
+ def test_deleting_self_type_mismatch
+ david = Developer.find(1)
+ david.projects.reload
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { david.projects.delete(Project.find(1).developers) }
+ end
+
+ def test_destroy_all
+ force_signal37_to_load_all_clients_of_firm
+ assert !companies(:first_firm).clients_of_firm.empty?, "37signals has clients after load"
+ companies(:first_firm).clients_of_firm.destroy_all
+ assert companies(:first_firm).clients_of_firm.empty?, "37signals has no clients after destroy all"
+ assert companies(:first_firm).clients_of_firm(true).empty?, "37signals has no clients after destroy all and refresh"
+ end
+
+ def test_dependence
+ firm = companies(:first_firm)
+ assert_equal 2, firm.clients.size
+ firm.destroy
+ assert Client.find(:all, :conditions => "firm_id=#{firm.id}").empty?
+ end
+
+ def test_destroy_dependent_when_deleted_from_association
+ firm = Firm.find(:first)
+ assert_equal 2, firm.clients.size
+
+ client = firm.clients.first
+ firm.clients.delete(client)
+
+ assert_raise(ActiveRecord::RecordNotFound) { Client.find(client.id) }
+ assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find(client.id) }
+ assert_equal 1, firm.clients.size
+ end
+
+ def test_three_levels_of_dependence
+ topic = Topic.create "title" => "neat and simple"
+ reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it"
+ silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining"
+
+ assert_nothing_raised { topic.destroy }
+ end
+
+ uses_transaction :test_dependence_with_transaction_support_on_failure
+ def test_dependence_with_transaction_support_on_failure
+ firm = companies(:first_firm)
+ clients = firm.clients
+ assert_equal 2, clients.length
+ clients.last.instance_eval { def before_destroy() raise "Trigger rollback" end }
+
+ firm.destroy rescue "do nothing"
+
+ assert_equal 2, Client.find(:all, :conditions => "firm_id=#{firm.id}").size
+ end
+
+ def test_dependence_on_account
+ num_accounts = Account.count
+ companies(:first_firm).destroy
+ assert_equal num_accounts - 1, Account.count
+ end
+
+ def test_depends_and_nullify
+ num_accounts = Account.count
+ num_companies = Company.count
+
+ core = companies(:rails_core)
+ assert_equal accounts(:rails_core_account), core.account
+ assert_equal companies(:leetsoft, :jadedpixel), core.companies
+ core.destroy
+ assert_nil accounts(:rails_core_account).reload.firm_id
+ assert_nil companies(:leetsoft).reload.client_of
+ assert_nil companies(:jadedpixel).reload.client_of
+
+
+ assert_equal num_accounts, Account.count
+ end
+
+ def test_included_in_collection
+ assert companies(:first_firm).clients.include?(Client.find(2))
+ end
+
+ def test_adding_array_and_collection
+ assert_nothing_raised { Firm.find(:first).clients + Firm.find(:all).last.clients }
+ end
+
+ def test_find_all_without_conditions
+ firm = companies(:first_firm)
+ assert_equal 2, firm.clients.find(:all).length
+ end
+
+ def test_replace_with_less
+ firm = Firm.find(:first)
+ firm.clients = [companies(:first_client)]
+ assert firm.save, "Could not save firm"
+ firm.reload
+ assert_equal 1, firm.clients.length
+ end
+
+ def test_replace_with_less_and_dependent_nullify
+ num_companies = Company.count
+ companies(:rails_core).companies = []
+ assert_equal num_companies, Company.count
+ end
+
+ def test_replace_with_new
+ firm = Firm.find(:first)
+ firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
+ firm.save
+ firm.reload
+ assert_equal 2, firm.clients.length
+ assert !firm.clients.include?(:first_client)
+ end
+
+ def test_replace_on_new_object
+ firm = Firm.new("name" => "New Firm")
+ firm.clients = [companies(:second_client), Client.new("name" => "New Client")]
+ assert firm.save
+ firm.reload
+ assert_equal 2, firm.clients.length
+ assert firm.clients.include?(Client.find_by_name("New Client"))
+ end
+
+ def test_get_ids
+ assert_equal [companies(:first_client).id, companies(:second_client).id], companies(:first_firm).client_ids
+ end
+
+ def test_assign_ids
+ firm = Firm.new("name" => "Apple")
+ firm.client_ids = [companies(:first_client).id, companies(:second_client).id]
+ firm.save
+ firm.reload
+ assert_equal 2, firm.clients.length
+ assert firm.clients.include?(companies(:second_client))
+ end
+
+ def test_assign_ids_ignoring_blanks
+ firm = Firm.create!(:name => 'Apple')
+ firm.client_ids = [companies(:first_client).id, nil, companies(:second_client).id, '']
+ firm.save!
+
+ assert_equal 2, firm.clients(true).size
+ assert firm.clients.include?(companies(:second_client))
+ end
+
+ def test_get_ids_for_through
+ assert_equal [comments(:eager_other_comment1).id], authors(:mary).comment_ids
+ end
+
+ def test_modifying_a_through_a_has_many_should_raise
+ [
+ lambda { authors(:mary).comment_ids = [comments(:greetings).id, comments(:more_greetings).id] },
+ lambda { authors(:mary).comments = [comments(:greetings), comments(:more_greetings)] },
+ lambda { authors(:mary).comments << Comment.create!(:body => "Yay", :post_id => 424242) },
+ lambda { authors(:mary).comments.delete(authors(:mary).comments.first) },
+ ].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection, &block) }
+ end
+
+
+ def test_assign_ids_for_through_a_belongs_to
+ post = Post.new(:title => "Assigning IDs works!", :body => "You heared it here first, folks!")
+ post.person_ids = [people(:david).id, people(:michael).id]
+ post.save
+ post.reload
+ assert_equal 2, post.people.length
+ assert post.people.include?(people(:david))
+ end
+
+ def test_dynamic_find_should_respect_association_order_for_through
+ assert_equal Comment.find(10), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'")
+ assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type('SpecialComment')
+ end
+
+ def test_dynamic_find_order_should_override_association_order_for_through
+ assert_equal Comment.find(3), authors(:david).comments_desc.find(:first, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id')
+ assert_equal Comment.find(3), authors(:david).comments_desc.find_by_type('SpecialComment', :order => 'comments.id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_order_for_through
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'")
+ assert_equal [Comment.find(10), Comment.find(7), Comment.find(6), Comment.find(3)], authors(:david).comments_desc.find_all_by_type('SpecialComment')
+ end
+
+ def test_dynamic_find_all_order_should_override_association_order_for_through
+ assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find(:all, :conditions => "comments.type = 'SpecialComment'", :order => 'comments.id')
+ assert_equal [Comment.find(3), Comment.find(6), Comment.find(7), Comment.find(10)], authors(:david).comments_desc.find_all_by_type('SpecialComment', :order => 'comments.id')
+ end
+
+ def test_dynamic_find_all_should_respect_association_limit_for_through
+ assert_equal 1, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'").length
+ assert_equal 1, authors(:david).limited_comments.find_all_by_type('SpecialComment').length
+ end
+
+ def test_dynamic_find_all_order_should_override_association_limit_for_through
+ assert_equal 4, authors(:david).limited_comments.find(:all, :conditions => "comments.type = 'SpecialComment'", :limit => 9_000).length
+ assert_equal 4, authors(:david).limited_comments.find_all_by_type('SpecialComment', :limit => 9_000).length
+ end
+
+ def test_find_all_include_over_the_same_table_for_through
+ assert_equal 2, people(:michael).posts.find(:all, :include => :people).length
+ end
+
+ def test_has_many_through_respects_hash_conditions
+ assert_equal authors(:david).hello_posts, authors(:david).hello_posts_with_hash_conditions
+ assert_equal authors(:david).hello_post_comments, authors(:david).hello_post_comments_with_hash_conditions
+ end
+
+ def test_include_uses_array_include_after_loaded
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+
+ client = firm.clients.first
+
+ assert_no_queries do
+ assert firm.clients.loaded?
+ assert firm.clients.include?(client)
+ end
+ end
+
+ def test_include_checks_if_record_exists_if_target_not_loaded
+ firm = companies(:first_firm)
+ client = firm.clients.first
+
+ firm.reload
+ assert ! firm.clients.loaded?
+ assert_queries(1) do
+ assert firm.clients.include?(client)
+ end
+ assert ! firm.clients.loaded?
+ end
+
+ def test_include_loads_collection_if_target_uses_finder_sql
+ firm = companies(:first_firm)
+ client = firm.clients_using_sql.first
+
+ firm.reload
+ assert ! firm.clients_using_sql.loaded?
+ assert firm.clients_using_sql.include?(client)
+ assert firm.clients_using_sql.loaded?
+ end
+
+
+ def test_include_returns_false_for_non_matching_record_to_verify_scoping
+ firm = companies(:first_firm)
+ client = Client.create!(:name => 'Not Associated')
+
+ assert ! firm.clients.loaded?
+ assert ! firm.clients.include?(client)
+ end
+
+ def test_calling_first_or_last_on_association_should_not_load_association
+ firm = companies(:first_firm)
+ firm.clients.first
+ firm.clients.last
+ assert !firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_loaded_association_should_not_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+ assert firm.clients.loaded?
+
+ assert_no_queries do
+ firm.clients.first
+ assert_equal 2, firm.clients.first(2).size
+ firm.clients.last
+ assert_equal 2, firm.clients.last(2).size
+ end
+ end
+
+ def test_calling_first_or_last_on_existing_record_with_build_should_load_association
+ firm = companies(:first_firm)
+ firm.clients.build(:name => 'Foo')
+ assert !firm.clients.loaded?
+
+ assert_queries 1 do
+ firm.clients.first
+ firm.clients.last
+ end
+
+ assert firm.clients.loaded?
+ end
+
+ def test_calling_first_or_last_on_new_record_should_not_run_queries
+ firm = Firm.new
+
+ assert_no_queries do
+ firm.clients.first
+ firm.clients.last
+ end
+ end
+
+ def test_calling_first_or_last_with_find_options_on_loaded_association_should_fetch_with_query
+ firm = companies(:first_firm)
+ firm.clients.class # force load target
+
+ assert_queries 2 do
+ assert firm.clients.loaded?
+ firm.clients.first(:order => 'name')
+ firm.clients.last(:order => 'name')
+ end
+ end
+
+ def test_calling_first_or_last_with_integer_on_association_should_load_association
+ firm = companies(:first_firm)
+
+ assert_queries 1 do
+ firm.clients.first(2)
+ firm.clients.last(2)
+ end
+
+ assert firm.clients.loaded?
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb
new file mode 100644
index 0000000..05155f6
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -0,0 +1,190 @@
+require "cases/helper"
+require 'models/post'
+require 'models/person'
+require 'models/reader'
+
+class HasManyThroughAssociationsTest < ActiveRecord::TestCase
+ fixtures :posts, :readers, :people
+
+ def test_associate_existing
+ assert_queries(2) { posts(:thinking);people(:david) }
+
+ assert_queries(1) do
+ posts(:thinking).people << people(:david)
+ end
+
+ assert_queries(1) do
+ assert posts(:thinking).people.include?(people(:david))
+ end
+
+ assert posts(:thinking).reload.people(true).include?(people(:david))
+ end
+
+ def test_associating_new
+ assert_queries(1) { posts(:thinking) }
+ new_person = nil # so block binding catches it
+
+ assert_queries(0) do
+ new_person = Person.new :first_name => 'bob'
+ end
+
+ # Associating new records always saves them
+ # Thus, 1 query for the new person record, 1 query for the new join table record
+ assert_queries(2) do
+ posts(:thinking).people << new_person
+ end
+
+ assert_queries(1) do
+ assert posts(:thinking).people.include?(new_person)
+ end
+
+ assert posts(:thinking).reload.people(true).include?(new_person)
+ end
+
+ def test_associate_new_by_building
+ assert_queries(1) { posts(:thinking) }
+
+ assert_queries(0) do
+ posts(:thinking).people.build(:first_name=>"Bob")
+ posts(:thinking).people.new(:first_name=>"Ted")
+ end
+
+ # Should only need to load the association once
+ assert_queries(1) do
+ assert posts(:thinking).people.collect(&:first_name).include?("Bob")
+ assert posts(:thinking).people.collect(&:first_name).include?("Ted")
+ end
+
+ # 2 queries for each new record (1 to save the record itself, 1 for the join model)
+ # * 2 new records = 4
+ # + 1 query to save the actual post = 5
+ assert_queries(5) do
+ posts(:thinking).body += '-changed'
+ posts(:thinking).save
+ end
+
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Bob")
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Ted")
+ end
+
+ def test_delete_association
+ assert_queries(2){posts(:welcome);people(:michael); }
+
+ assert_queries(1) do
+ posts(:welcome).people.delete(people(:michael))
+ end
+
+ assert_queries(1) do
+ assert posts(:welcome).people.empty?
+ end
+
+ assert posts(:welcome).reload.people(true).empty?
+ end
+
+ def test_replace_association
+ assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)}
+
+ # 1 query to delete the existing reader (michael)
+ # 1 query to associate the new reader (david)
+ assert_queries(2) do
+ posts(:welcome).people = [people(:david)]
+ end
+
+ assert_queries(0){
+ assert posts(:welcome).people.include?(people(:david))
+ assert !posts(:welcome).people.include?(people(:michael))
+ }
+
+ assert posts(:welcome).reload.people(true).include?(people(:david))
+ assert !posts(:welcome).reload.people(true).include?(people(:michael))
+ end
+
+ def test_associate_with_create
+ assert_queries(1) { posts(:thinking) }
+
+ # 1 query for the new record, 1 for the join table record
+ # No need to update the actual collection yet!
+ assert_queries(2) do
+ posts(:thinking).people.create(:first_name=>"Jeb")
+ end
+
+ # *Now* we actually need the collection so it's loaded
+ assert_queries(1) do
+ assert posts(:thinking).people.collect(&:first_name).include?("Jeb")
+ end
+
+ assert posts(:thinking).reload.people(true).collect(&:first_name).include?("Jeb")
+ end
+
+ def test_associate_with_create_and_no_options
+ peeps = posts(:thinking).people.count
+ posts(:thinking).people.create(:first_name => 'foo')
+ assert_equal peeps + 1, posts(:thinking).people.count
+ end
+
+ def test_associate_with_create_exclamation_and_no_options
+ peeps = posts(:thinking).people.count
+ posts(:thinking).people.create!(:first_name => 'foo')
+ assert_equal peeps + 1, posts(:thinking).people.count
+ end
+
+ def test_clear_associations
+ assert_queries(2) { posts(:welcome);posts(:welcome).people(true) }
+
+ assert_queries(1) do
+ posts(:welcome).people.clear
+ end
+
+ assert_queries(0) do
+ assert posts(:welcome).people.empty?
+ end
+
+ assert posts(:welcome).reload.people(true).empty?
+ end
+
+ def test_association_callback_ordering
+ Post.reset_log
+ log = Post.log
+ post = posts(:thinking)
+
+ post.people_with_callbacks << people(:michael)
+ assert_equal [
+ [:added, :before, "Michael"],
+ [:added, :after, "Michael"]
+ ], log.last(2)
+
+ post.people_with_callbacks.push(people(:david), Person.create!(:first_name => "Bob"), Person.new(:first_name => "Lary"))
+ assert_equal [
+ [:added, :before, "David"],
+ [:added, :after, "David"],
+ [:added, :before, "Bob"],
+ [:added, :after, "Bob"],
+ [:added, :before, "Lary"],
+ [:added, :after, "Lary"]
+ ],log.last(6)
+
+ post.people_with_callbacks.build(:first_name => "Ted")
+ assert_equal [
+ [:added, :before, "Ted"],
+ [:added, :after, "Ted"]
+ ], log.last(2)
+
+ post.people_with_callbacks.create(:first_name => "Sam")
+ assert_equal [
+ [:added, :before, "Sam"],
+ [:added, :after, "Sam"]
+ ], log.last(2)
+
+ post.people_with_callbacks = [people(:michael),people(:david), Person.new(:first_name => "Julian"), Person.create!(:first_name => "Roger")]
+ assert_equal (%w(Ted Bob Sam Lary) * 2).sort, log[-12..-5].collect(&:last).sort
+ assert_equal [
+ [:added, :before, "Julian"],
+ [:added, :after, "Julian"],
+ [:added, :before, "Roger"],
+ [:added, :after, "Roger"]
+ ], log.last(4)
+
+ post.people_with_callbacks.clear
+ assert_equal (%w(Michael David Julian Roger) * 2).sort, log.last(8).collect(&:last).sort
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb
new file mode 100644
index 0000000..abc7ee7
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -0,0 +1,323 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/company'
+
+class HasOneAssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects
+
+ def setup
+ Account.destroyed_account_ids.clear
+ end
+
+ def test_has_one
+ assert_equal companies(:first_firm).account, Account.find(1)
+ assert_equal Account.find(1).credit_limit, companies(:first_firm).account.credit_limit
+ end
+
+ def test_has_one_cache_nils
+ firm = companies(:another_firm)
+ assert_queries(1) { assert_nil firm.account }
+ assert_queries(0) { assert_nil firm.account }
+
+ firms = Firm.find(:all, :include => :account)
+ assert_queries(0) { firms.each(&:account) }
+ end
+
+ def test_with_select
+ assert_equal Firm.find(1).account_with_select.attributes.size, 2
+ assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2
+ end
+
+ def test_can_marshal_has_one_association_with_nil_target
+ firm = Firm.new
+ assert_nothing_raised do
+ assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
+ end
+
+ firm.account
+ assert_nothing_raised do
+ assert_equal firm.attributes, Marshal.load(Marshal.dump(firm)).attributes
+ end
+ end
+
+ def test_proxy_assignment
+ company = companies(:first_firm)
+ assert_nothing_raised { company.account = company.account }
+ end
+
+ def test_triple_equality
+ assert Account === companies(:first_firm).account
+ assert companies(:first_firm).account === Account
+ end
+
+ def test_type_mismatch
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = 1 }
+ assert_raises(ActiveRecord::AssociationTypeMismatch) { companies(:first_firm).account = Project.find(1) }
+ end
+
+ def test_natural_assignment
+ apple = Firm.create("name" => "Apple")
+ citibank = Account.create("credit_limit" => 10)
+ apple.account = citibank
+ assert_equal apple.id, citibank.firm_id
+ end
+
+ def test_natural_assignment_to_nil
+ old_account_id = companies(:first_firm).account.id
+ companies(:first_firm).account = nil
+ companies(:first_firm).save
+ assert_nil companies(:first_firm).account
+ # account is dependent, therefore is destroyed when reference to owner is lost
+ assert_raises(ActiveRecord::RecordNotFound) { Account.find(old_account_id) }
+ 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
+
+ firm = Firm.find(1)
+ assert !firm.account.nil?
+ account_id = firm.account.id
+ assert_equal [], Account.destroyed_account_ids[firm.id]
+
+ firm.destroy
+ assert_equal num_accounts - 1, Account.count
+ assert_equal [account_id], Account.destroyed_account_ids[firm.id]
+ end
+
+ def test_exclusive_dependence
+ num_accounts = Account.count
+
+ firm = ExclusivelyDependentFirm.find(9)
+ assert !firm.account.nil?
+ account_id = firm.account.id
+ assert_equal [], Account.destroyed_account_ids[firm.id]
+
+ firm.destroy
+ assert_equal num_accounts - 1, Account.count
+ assert_equal [], Account.destroyed_account_ids[firm.id]
+ end
+
+ def test_dependence_with_nil_associate
+ firm = DependentFirm.new(:name => 'nullify')
+ firm.save!
+ assert_nothing_raised { firm.destroy }
+ end
+
+ def test_succesful_build_association
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.save
+
+ account = firm.build_account("credit_limit" => 1000)
+ assert account.save
+ assert_equal account, firm.account
+ end
+
+ def test_failing_build_association
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.save
+
+ account = firm.build_account
+ assert !account.save
+ assert_equal "can't be empty", account.errors.on("credit_limit")
+ end
+
+ def test_build_association_twice_without_saving_affects_nothing
+ count_of_account = Account.count
+ firm = Firm.find(:first)
+ account1 = firm.build_account("credit_limit" => 1000)
+ account2 = firm.build_account("credit_limit" => 2000)
+
+ assert_equal count_of_account, Account.count
+ end
+
+ def test_create_association
+ firm = Firm.create(:name => "GlobalMegaCorp")
+ account = firm.create_account(:credit_limit => 1000)
+ assert_equal account, firm.reload.account
+ end
+
+ def test_build
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.save
+
+ firm.account = account = Account.new("credit_limit" => 1000)
+ assert_equal account, firm.account
+ assert account.save
+ assert_equal account, firm.account
+ end
+
+ def test_build_before_child_saved
+ firm = Firm.find(1)
+
+ account = firm.account.build("credit_limit" => 1000)
+ assert_equal account, firm.account
+ assert account.new_record?
+ assert firm.save
+ assert_equal account, firm.account
+ assert !account.new_record?
+ end
+
+ def test_build_before_either_saved
+ firm = Firm.new("name" => "GlobalMegaCorp")
+
+ firm.account = account = Account.new("credit_limit" => 1000)
+ assert_equal account, firm.account
+ assert account.new_record?
+ assert firm.save
+ assert_equal account, firm.account
+ assert !account.new_record?
+ 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.on("credit_limit")
+ end
+
+ def test_create
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.save
+ firm.account = account = Account.create("credit_limit" => 1000)
+ assert_equal account, firm.account
+ end
+
+ def test_create_before_save
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.account = account = Account.create("credit_limit" => 1000)
+ assert_equal account, firm.account
+ end
+
+ def test_dependence_with_missing_association
+ Account.destroy_all
+ firm = Firm.find(1)
+ assert firm.account.nil?
+ firm.destroy
+ end
+
+ def test_dependence_with_missing_association_and_nullify
+ Account.destroy_all
+ firm = DependentFirm.find(:first)
+ assert firm.account.nil?
+ firm.destroy
+ end
+
+ def test_assignment_before_parent_saved
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.account = a = Account.find(1)
+ assert firm.new_record?
+ assert_equal a, firm.account
+ assert firm.save
+ assert_equal a, firm.account
+ assert_equal a, firm.account(true)
+ 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)
+ assert !a.new_record?
+ assert_equal a, firm.account
+ assert_equal a, firm.account
+ assert_equal a, firm.account(true)
+ end
+
+ def test_save_fails_for_invalid_has_one
+ firm = Firm.find(:first)
+ assert firm.valid?
+
+ firm.account = Account.new
+
+ assert !firm.account.valid?
+ assert !firm.valid?
+ assert !firm.save
+ assert_equal "is invalid", firm.errors.on("account")
+ end
+
+ def test_assignment_before_either_saved
+ firm = Firm.new("name" => "GlobalMegaCorp")
+ firm.account = a = Account.new("credit_limit" => 1000)
+ assert firm.new_record?
+ assert a.new_record?
+ assert_equal a, firm.account
+ assert firm.save
+ assert !firm.new_record?
+ assert !a.new_record?
+ assert_equal a, firm.account
+ assert_equal a, firm.account(true)
+ end
+
+ def test_not_resaved_when_unchanged
+ firm = Firm.find(:first, :include => :account)
+ firm.name += '-changed'
+ assert_queries(1) { firm.save! }
+
+ firm = Firm.find(:first)
+ firm.account = Account.find(:first)
+ assert_queries(Firm.partial_updates? ? 0 : 1) { firm.save! }
+
+ firm = Firm.find(:first).clone
+ firm.account = Account.find(:first)
+ assert_queries(2) { firm.save! }
+
+ firm = Firm.find(:first).clone
+ firm.account = Account.find(:first).clone
+ assert_queries(2) { firm.save! }
+ end
+
+ def test_save_still_works_after_accessing_nil_has_one
+ jp = Company.new :name => 'Jaded Pixel'
+ jp.dummy_account.nil?
+
+ assert_nothing_raised do
+ jp.save!
+ end
+ end
+
+ def test_cant_save_readonly_association
+ assert_raise(ActiveRecord::ReadOnlyRecord) { companies(:first_firm).readonly_account.save! }
+ assert companies(:first_firm).readonly_account.readonly?
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb b/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb
new file mode 100644
index 0000000..3eb66bc
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -0,0 +1,74 @@
+require "cases/helper"
+require 'models/club'
+require 'models/member'
+require 'models/membership'
+require 'models/sponsor'
+
+class HasOneThroughAssociationsTest < ActiveRecord::TestCase
+ fixtures :members, :clubs, :memberships, :sponsors
+
+ def setup
+ @member = members(:groucho)
+ end
+
+ def test_has_one_through_with_has_one
+ assert_equal clubs(:boring_club), @member.club
+ end
+
+ def test_has_one_through_with_has_many
+ assert_equal clubs(:moustache_club), @member.favourite_club
+ end
+
+ def test_creating_association_creates_through_record
+ new_member = Member.create(:name => "Chris")
+ new_member.club = Club.create(:name => "LRUG")
+ assert_not_nil new_member.current_membership
+ assert_not_nil new_member.club
+ end
+
+ def test_replace_target_record
+ new_club = Club.create(:name => "Marx Bros")
+ @member.club = new_club
+ @member.reload
+ assert_equal new_club, @member.club
+ end
+
+ def test_replacing_target_record_deletes_old_association
+ assert_no_difference "Membership.count" do
+ new_club = Club.create(:name => "Bananarama")
+ @member.club = new_club
+ @member.reload
+ end
+ end
+
+ def test_has_one_through_polymorphic
+ assert_equal clubs(:moustache_club), @member.sponsor_club
+ end
+
+ def has_one_through_to_has_many
+ assert_equal 2, @member.fellow_members.size
+ end
+
+ def test_has_one_through_eager_loading
+ members = Member.find(:all, :include => :club, :conditions => ["name = ?", "Groucho Marx"])
+ assert_equal 1, members.size
+ assert_not_nil assert_no_queries {members[0].club}
+ end
+
+ def test_has_one_through_eager_loading_through_polymorphic
+ members = Member.find(:all, :include => :sponsor_club, :conditions => ["name = ?", "Groucho Marx"])
+ assert_equal 1, members.size
+ assert_not_nil assert_no_queries {members[0].sponsor_club}
+ end
+
+ def test_has_one_through_polymorphic_with_source_type
+ assert_equal members(:groucho), clubs(:moustache_club).sponsored_member
+ end
+
+ def test_eager_has_one_through_polymorphic_with_source_type
+ clubs = Club.find(:all, :include => :sponsored_member, :conditions => ["name = ?","Moustache and Eyebrow Fancier Club"])
+ # Only the eyebrow fanciers club has a sponsored_member
+ assert_not_nil assert_no_queries {clubs[0].sponsored_member}
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/inner_join_association_test.rb b/vendor/rails/activerecord/test/cases/associations/inner_join_association_test.rb
new file mode 100644
index 0000000..f87c914
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/inner_join_association_test.rb
@@ -0,0 +1,88 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/author'
+require 'models/category'
+require 'models/categorization'
+
+class InnerJoinAssociationTest < ActiveRecord::TestCase
+ fixtures :authors, :posts, :comments, :categories, :categories_posts, :categorizations
+
+ def test_construct_finder_sql_creates_inner_joins
+ sql = Author.send(:construct_finder_sql, :joins => :posts)
+ assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
+ end
+
+ def test_construct_finder_sql_cascades_inner_joins
+ sql = Author.send(:construct_finder_sql, :joins => {:posts => :comments})
+ assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
+ assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = posts.id/, sql
+ end
+
+ def test_construct_finder_sql_inner_joins_through_associations
+ sql = Author.send(:construct_finder_sql, :joins => :categorized_posts)
+ assert_match /INNER JOIN .?categorizations.?.*INNER JOIN .?posts.?/, sql
+ end
+
+ def test_construct_finder_sql_applies_association_conditions
+ sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER")
+ assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql
+ end
+
+ def test_construct_finder_sql_unpacks_nested_joins
+ sql = Author.send(:construct_finder_sql, :joins => {:posts => [[:comments]]})
+ assert_no_match /inner join.*inner join.*inner join/i, sql, "only two join clauses should be present"
+ assert_match /INNER JOIN .?posts.? ON .?posts.?.author_id = authors.id/, sql
+ assert_match /INNER JOIN .?comments.? ON .?comments.?.post_id = .?posts.?.id/, sql
+ end
+
+ def test_construct_finder_sql_ignores_empty_joins_hash
+ sql = Author.send(:construct_finder_sql, :joins => {})
+ assert_no_match /JOIN/i, sql
+ end
+
+ def test_construct_finder_sql_ignores_empty_joins_array
+ sql = Author.send(:construct_finder_sql, :joins => [])
+ assert_no_match /JOIN/i, sql
+ end
+
+ def test_find_with_implicit_inner_joins_honors_readonly_without_select
+ authors = Author.find(:all, :joins => :posts)
+ assert !authors.empty?, "expected authors to be non-empty"
+ assert authors.all? {|a| a.readonly? }, "expected all authors to be readonly"
+ end
+
+ def test_find_with_implicit_inner_joins_honors_readonly_with_select
+ authors = Author.find(:all, :select => 'authors.*', :joins => :posts)
+ assert !authors.empty?, "expected authors to be non-empty"
+ assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
+ end
+
+ def test_find_with_implicit_inner_joins_honors_readonly_false
+ authors = Author.find(:all, :joins => :posts, :readonly => false)
+ assert !authors.empty?, "expected authors to be non-empty"
+ assert authors.all? {|a| !a.readonly? }, "expected no authors to be readonly"
+ end
+
+ def test_find_with_implicit_inner_joins_does_not_set_associations
+ authors = Author.find(:all, :select => 'authors.*', :joins => :posts)
+ assert !authors.empty?, "expected authors to be non-empty"
+ assert authors.all? {|a| !a.send(:instance_variable_names).include?("@posts")}, "expected no authors to have the @posts association loaded"
+ end
+
+ def test_count_honors_implicit_inner_joins
+ real_count = Author.find(:all).sum{|a| a.posts.count }
+ assert_equal real_count, Author.count(:joins => :posts), "plain inner join count should match the number of referenced posts records"
+ end
+
+ def test_calculate_honors_implicit_inner_joins
+ real_count = Author.find(:all).sum{|a| a.posts.count }
+ assert_equal real_count, Author.calculate(:count, 'authors.id', :joins => :posts), "plain inner join count should match the number of referenced posts records"
+ end
+
+ def test_calculate_honors_implicit_inner_joins_and_distinct_and_conditions
+ real_count = Author.find(:all).select {|a| a.posts.any? {|p| p.title =~ /^Welcome/} }.length
+ authors_with_welcoming_post_titles = Author.calculate(:count, 'authors.id', :joins => :posts, :distinct => true, :conditions => "posts.title like 'Welcome%'")
+ assert_equal real_count, authors_with_welcoming_post_titles, "inner join and conditions should have only returned authors posting titles starting with 'Welcome'"
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations/join_model_test.rb b/vendor/rails/activerecord/test/cases/associations/join_model_test.rb
new file mode 100644
index 0000000..9e79d9c
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations/join_model_test.rb
@@ -0,0 +1,707 @@
+require "cases/helper"
+require 'models/tag'
+require 'models/tagging'
+require 'models/post'
+require 'models/item'
+require 'models/comment'
+require 'models/author'
+require 'models/category'
+require 'models/categorization'
+require 'models/vertex'
+require 'models/edge'
+require 'models/book'
+require 'models/citation'
+
+class AssociationsJoinModelTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books
+
+ def test_has_many
+ assert authors(:david).categories.include?(categories(:general))
+ end
+
+ def test_has_many_inherited
+ assert authors(:mary).categories.include?(categories(:sti_test))
+ end
+
+ def test_inherited_has_many
+ assert categories(:sti_test).authors.include?(authors(:mary))
+ end
+
+ def test_has_many_uniq_through_join_model
+ assert_equal 2, authors(:mary).categorized_posts.size
+ assert_equal 1, authors(:mary).unique_categorized_posts.size
+ end
+
+ def test_has_many_uniq_through_count
+ author = authors(:mary)
+ assert !authors(:mary).unique_categorized_posts.loaded?
+ assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count }
+ assert_queries(1) { assert_equal 1, author.unique_categorized_posts.count(:title) }
+ assert_queries(1) { assert_equal 0, author.unique_categorized_posts.count(:title, :conditions => "title is NULL") }
+ assert !authors(:mary).unique_categorized_posts.loaded?
+ end
+
+ def test_has_many_uniq_through_find
+ assert_equal 1, authors(:mary).unique_categorized_posts.find(:all).size
+ end
+
+ def test_has_many_uniq_through_dynamic_find
+ assert_equal 1, authors(:mary).unique_categorized_posts.find_all_by_title("So I was thinking").size
+ end
+
+ def test_polymorphic_has_many
+ assert posts(:welcome).taggings.include?(taggings(:welcome_general))
+ end
+
+ def test_polymorphic_has_one
+ assert_equal taggings(:welcome_general), posts(:welcome).tagging
+ end
+
+ def test_polymorphic_belongs_to
+ assert_equal posts(:welcome), posts(:welcome).taggings.first.taggable
+ end
+
+ def test_polymorphic_has_many_going_through_join_model
+ assert_equal tags(:general), tag = posts(:welcome).tags.first
+ assert_no_queries do
+ tag.tagging
+ end
+ end
+
+ def test_count_polymorphic_has_many
+ assert_equal 1, posts(:welcome).taggings.count
+ assert_equal 1, posts(:welcome).tags.count
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_find
+ assert_equal tags(:general), tag = posts(:welcome).tags.find(:first)
+ assert_no_queries do
+ tag.tagging
+ end
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection
+ assert_equal tags(:general), tag = posts(:welcome).funky_tags.first
+ assert_no_queries do
+ tag.tagging
+ end
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
+ assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
+ assert_no_queries do
+ tag.tagging
+ 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
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_custom_foreign_key
+ assert_equal tags(:misc), taggings(:welcome_general).super_tag
+ assert_equal tags(:misc), posts(:welcome).super_tags.first
+ end
+
+ def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
+ post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body'
+ assert_instance_of SubStiPost, post
+
+ tagging = tags(:misc).taggings.create(:taggable => post)
+ assert_equal "SubStiPost", tagging.taggable_type
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_inheritance
+ assert_equal tags(:general), posts(:thinking).tags.first
+ end
+
+ def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name
+ assert_equal tags(:general), posts(:thinking).funky_tags.first
+ end
+
+ def test_polymorphic_has_many_create_model_with_inheritance
+ post = posts(:thinking)
+ assert_instance_of SpecialPost, post
+
+ tagging = tags(:misc).taggings.create(:taggable => post)
+ assert_equal "Post", tagging.taggable_type
+ end
+
+ def test_polymorphic_has_one_create_model_with_inheritance
+ tagging = tags(:misc).create_tagging(:taggable => posts(:thinking))
+ assert_equal "Post", tagging.taggable_type
+ end
+
+ def test_set_polymorphic_has_many
+ tagging = tags(:misc).taggings.create
+ posts(:thinking).taggings << tagging
+ assert_equal "Post", tagging.taggable_type
+ end
+
+ def test_set_polymorphic_has_one
+ tagging = tags(:misc).taggings.create
+ posts(:thinking).tagging = tagging
+ assert_equal "Post", tagging.taggable_type
+ end
+
+ def test_create_polymorphic_has_many_with_scope
+ old_count = posts(:welcome).taggings.count
+ tagging = posts(:welcome).taggings.create(:tag => tags(:misc))
+ assert_equal "Post", tagging.taggable_type
+ assert_equal old_count+1, posts(:welcome).taggings.count
+ end
+
+ def test_create_bang_polymorphic_with_has_many_scope
+ old_count = posts(:welcome).taggings.count
+ tagging = posts(:welcome).taggings.create!(:tag => tags(:misc))
+ assert_equal "Post", tagging.taggable_type
+ assert_equal old_count+1, posts(:welcome).taggings.count
+ end
+
+ def test_create_polymorphic_has_one_with_scope
+ old_count = Tagging.count
+ tagging = posts(:welcome).tagging.create(:tag => tags(:misc))
+ assert_equal "Post", tagging.taggable_type
+ assert_equal old_count+1, Tagging.count
+ end
+
+ def test_delete_polymorphic_has_many_with_delete_all
+ assert_equal 1, posts(:welcome).taggings.count
+ posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
+ post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
+
+ old_count = Tagging.count
+ post.destroy
+ assert_equal old_count-1, Tagging.count
+ assert_equal 0, posts(:welcome).taggings.count
+ end
+
+ def test_delete_polymorphic_has_many_with_destroy
+ assert_equal 1, posts(:welcome).taggings.count
+ posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
+ post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
+
+ old_count = Tagging.count
+ post.destroy
+ assert_equal old_count-1, Tagging.count
+ assert_equal 0, posts(:welcome).taggings.count
+ end
+
+ def test_delete_polymorphic_has_many_with_nullify
+ assert_equal 1, posts(:welcome).taggings.count
+ posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
+ post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
+
+ old_count = Tagging.count
+ post.destroy
+ assert_equal old_count, Tagging.count
+ assert_equal 0, posts(:welcome).taggings.count
+ end
+
+ def test_delete_polymorphic_has_one_with_destroy
+ assert posts(:welcome).tagging
+ posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
+ post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
+
+ old_count = Tagging.count
+ post.destroy
+ assert_equal old_count-1, Tagging.count
+ assert_nil posts(:welcome).tagging(true)
+ end
+
+ def test_delete_polymorphic_has_one_with_nullify
+ assert posts(:welcome).tagging
+ posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
+ post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
+
+ old_count = Tagging.count
+ post.destroy
+ assert_equal old_count, Tagging.count
+ assert_nil posts(:welcome).tagging(true)
+ end
+
+ def test_has_many_with_piggyback
+ assert_equal "2", categories(:sti_test).authors.first.post_id.to_s
+ end
+
+ def test_include_has_many_through
+ posts = Post.find(:all, :order => 'posts.id')
+ posts_with_authors = Post.find(:all, :include => :authors, :order => 'posts.id')
+ assert_equal posts.length, posts_with_authors.length
+ posts.length.times do |i|
+ assert_equal posts[i].authors.length, assert_no_queries { posts_with_authors[i].authors.length }
+ end
+ end
+
+ def test_include_polymorphic_has_one
+ post = Post.find_by_id(posts(:welcome).id, :include => :tagging)
+ tagging = taggings(:welcome_general)
+ assert_no_queries do
+ assert_equal tagging, post.tagging
+ end
+ end
+
+ def test_include_polymorphic_has_one_defined_in_abstract_parent
+ item = Item.find_by_id(items(:dvd).id, :include => :tagging)
+ tagging = taggings(:godfather)
+ assert_no_queries do
+ assert_equal tagging, item.tagging
+ end
+ end
+
+ def test_include_polymorphic_has_many_through
+ posts = Post.find(:all, :order => 'posts.id')
+ posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ assert_equal posts.length, posts_with_tags.length
+ posts.length.times do |i|
+ assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
+ end
+ end
+
+ def test_include_polymorphic_has_many
+ posts = Post.find(:all, :order => 'posts.id')
+ posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ assert_equal posts.length, posts_with_taggings.length
+ posts.length.times do |i|
+ assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
+ end
+ end
+
+ def test_has_many_find_all
+ assert_equal [categories(:general)], authors(:david).categories.find(:all)
+ end
+
+ def test_has_many_find_first
+ assert_equal categories(:general), authors(:david).categories.find(:first)
+ end
+
+ def test_has_many_with_hash_conditions
+ assert_equal categories(:general), authors(:david).categories_like_general.find(:first)
+ end
+
+ def test_has_many_find_conditions
+ assert_equal categories(:general), authors(:david).categories.find(:first, :conditions => "categories.name = 'General'")
+ assert_equal nil, authors(:david).categories.find(:first, :conditions => "categories.name = 'Technology'")
+ end
+
+ def test_has_many_class_methods_called_by_method_missing
+ assert_equal categories(:general), authors(:david).categories.find_all_by_name('General').first
+ assert_equal nil, authors(:david).categories.find_by_name('Technology')
+ end
+
+ def test_has_many_array_methods_called_by_method_missing
+ assert true, authors(:david).categories.any? { |category| category.name == 'General' }
+ assert_nothing_raised { authors(:david).categories.sort }
+ end
+
+ def test_has_many_going_through_join_model_with_custom_foreign_key
+ assert_equal [], posts(:thinking).authors
+ assert_equal [authors(:mary)], posts(:authorless).authors
+ end
+
+ def test_both_scoped_and_explicit_joins_should_be_respected
+ assert_nothing_raised do
+ Post.send(:with_scope, :find => {:joins => "left outer join comments on comments.id = posts.id"}) do
+ Post.find :all, :select => "comments.id, authors.id", :joins => "left outer join authors on authors.id = posts.author_id"
+ end
+ end
+ end
+
+ def test_belongs_to_polymorphic_with_counter_cache
+ assert_equal 0, posts(:welcome)[:taggings_count]
+ tagging = posts(:welcome).taggings.create(:tag => tags(:general))
+ assert_equal 1, posts(:welcome, :reload)[:taggings_count]
+ tagging.destroy
+ assert posts(:welcome, :reload)[:taggings_count].zero?
+ end
+
+ def test_unavailable_through_reflection
+ assert_raise(ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings }
+ end
+
+ def test_has_many_through_join_model_with_conditions
+ assert_equal [], posts(:welcome).invalid_taggings
+ assert_equal [], posts(:welcome).invalid_tags
+ end
+
+ def test_has_many_polymorphic
+ assert_raise ActiveRecord::HasManyThroughAssociationPolymorphicError do
+ assert_equal posts(:welcome, :thinking), tags(:general).taggables
+ end
+ assert_raise ActiveRecord::EagerLoadPolymorphicError do
+ assert_equal posts(:welcome, :thinking), tags(:general).taggings.find(:all, :include => :taggable, :conditions => 'bogus_table.column = 1')
+ end
+ end
+
+ def test_has_many_polymorphic_with_source_type
+ assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts
+ end
+
+ def test_eager_has_many_polymorphic_with_source_type
+ tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts)
+ desired = posts(:welcome, :thinking)
+ assert_no_queries do
+ assert_equal desired, tag_with_include.tagged_posts
+ end
+ assert_equal 5, tag_with_include.taggings.length
+ end
+
+ def test_has_many_through_has_many_find_all
+ assert_equal comments(:greetings), authors(:david).comments.find(:all, :order => 'comments.id').first
+ end
+
+ def test_has_many_through_has_many_find_all_with_custom_class
+ assert_equal comments(:greetings), authors(:david).funky_comments.find(:all, :order => 'comments.id').first
+ end
+
+ def test_has_many_through_has_many_find_first
+ assert_equal comments(:greetings), authors(:david).comments.find(:first, :order => 'comments.id')
+ end
+
+ def test_has_many_through_has_many_find_conditions
+ options = { :conditions => "comments.#{QUOTED_TYPE}='SpecialComment'", :order => 'comments.id' }
+ assert_equal comments(:does_it_hurt), authors(:david).comments.find(:first, options)
+ end
+
+ def test_has_many_through_has_many_find_by_id
+ assert_equal comments(:more_greetings), authors(:david).comments.find(2)
+ end
+
+ def test_has_many_through_polymorphic_has_one
+ assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
+ end
+
+ def test_has_many_through_polymorphic_has_many
+ assert_equal taggings(:welcome_general, :thinking_general), authors(:david).taggings.uniq.sort_by { |t| t.id }
+ end
+
+ def test_include_has_many_through_polymorphic_has_many
+ author = Author.find_by_id(authors(:david).id, :include => :taggings)
+ expected_taggings = taggings(:welcome_general, :thinking_general)
+ assert_no_queries do
+ assert_equal expected_taggings, author.taggings.uniq.sort_by { |t| t.id }
+ end
+ end
+
+ def test_has_many_through_has_many_through
+ assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
+ end
+
+ def test_has_many_through_habtm
+ assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
+ end
+
+ def test_eager_load_has_many_through_has_many
+ author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
+ SpecialComment.new; VerySpecialComment.new
+ assert_no_queries do
+ assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
+ end
+ end
+
+ def test_eager_load_has_many_through_has_many_with_conditions
+ post = Post.find(:first, :include => :invalid_tags)
+ assert_no_queries do
+ post.invalid_tags
+ end
+ end
+
+ def test_eager_belongs_to_and_has_one_not_singularized
+ assert_nothing_raised do
+ Author.find(:first, :include => :author_address)
+ AuthorAddress.find(:first, :include => :author)
+ end
+ end
+
+ def test_self_referential_has_many_through
+ assert_equal [authors(:mary)], authors(:david).favorite_authors
+ assert_equal [], authors(:mary).favorite_authors
+ end
+
+ def test_add_to_self_referential_has_many_through
+ new_author = Author.create(:name => "Bob")
+ authors(:david).author_favorites.create :favorite_author => new_author
+ assert_equal new_author, authors(:david).reload.favorite_authors.first
+ end
+
+ def test_has_many_through_uses_conditions_specified_on_the_has_many_association
+ author = Author.find(:first)
+ assert !author.comments.blank?
+ assert author.nonexistant_comments.blank?
+ end
+
+ def test_has_many_through_uses_correct_attributes
+ assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
+ end
+
+ def test_associating_unsaved_records_with_has_many_through
+ saved_post = posts(:thinking)
+ new_tag = Tag.new(:name => "new")
+
+ saved_post.tags << new_tag
+ assert !new_tag.new_record? #consistent with habtm!
+ assert !saved_post.new_record?
+ assert saved_post.tags.include?(new_tag)
+
+ assert !new_tag.new_record?
+ assert saved_post.reload.tags(true).include?(new_tag)
+
+
+ new_post = Post.new(:title => "Association replacmenet works!", :body => "You best believe it.")
+ saved_tag = tags(:general)
+
+ new_post.tags << saved_tag
+ assert new_post.new_record?
+ assert !saved_tag.new_record?
+ assert new_post.tags.include?(saved_tag)
+
+ new_post.save!
+ assert !new_post.new_record?
+ assert new_post.reload.tags(true).include?(saved_tag)
+
+ assert posts(:thinking).tags.build.new_record?
+ assert posts(:thinking).tags.new.new_record?
+ end
+
+ def test_create_associate_when_adding_to_has_many_through
+ count = posts(:thinking).tags.count
+ push = Tag.create!(:name => 'pushme')
+ post_thinking = posts(:thinking)
+ assert_nothing_raised { post_thinking.tags << push }
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
+ assert_equal(count + 1, post_thinking.tags.size)
+ assert_equal(count + 1, post_thinking.tags(true).size)
+
+ assert_kind_of Tag, post_thinking.tags.create!(:name => 'foo')
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
+ assert_equal(count + 2, post_thinking.tags.size)
+ assert_equal(count + 2, post_thinking.tags(true).size)
+
+ assert_nothing_raised { post_thinking.tags.concat(Tag.create!(:name => 'abc'), Tag.create!(:name => 'def')) }
+ assert_nil( wrong = post_thinking.tags.detect { |t| t.class != Tag },
+ message = "Expected a Tag in tags collection, got #{wrong.class}.")
+ assert_nil( wrong = post_thinking.taggings.detect { |t| t.class != Tagging },
+ message = "Expected a Tagging in taggings collection, got #{wrong.class}.")
+ assert_equal(count + 4, post_thinking.tags.size)
+ assert_equal(count + 4, post_thinking.tags(true).size)
+
+ # Raises if the wrong reflection name is used to set the Edge belongs_to
+ assert_nothing_raised { vertices(:vertex_1).sinks << vertices(:vertex_5) }
+ end
+
+ def test_has_many_through_collection_size_doesnt_load_target_if_not_loaded
+ author = authors(:david)
+ assert_equal 9, author.comments.size
+ assert !author.comments.loaded?
+ end
+
+ uses_mocha('has_many_through_collection_size_uses_counter_cache_if_it_exists') do
+ 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)
+ assert_equal 100, author.comments.size
+ assert !author.comments.loaded?
+ end
+ end
+
+ def test_adding_junk_to_has_many_through_should_raise_type_mismatch
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags << "Uhh what now?" }
+ end
+
+ def test_adding_to_has_many_through_should_return_self
+ tags = posts(:thinking).tags
+ assert_equal tags, posts(:thinking).tags.push(tags(:general))
+ end
+
+ def test_delete_associate_when_deleting_from_has_many_through_with_nonstandard_id
+ count = books(:awdr).references.count
+ references_before = books(:awdr).references
+ book = Book.create!(:name => 'Getting Real')
+ book_awdr = books(:awdr)
+ book_awdr.references << book
+ assert_equal(count + 1, book_awdr.references(true).size)
+
+ assert_nothing_raised { book_awdr.references.delete(book) }
+ assert_equal(count, book_awdr.references.size)
+ assert_equal(count, book_awdr.references(true).size)
+ assert_equal(references_before.sort, book_awdr.references.sort)
+ end
+
+ def test_delete_associate_when_deleting_from_has_many_through
+ count = posts(:thinking).tags.count
+ tags_before = posts(:thinking).tags
+ tag = Tag.create!(:name => 'doomed')
+ post_thinking = posts(:thinking)
+ post_thinking.tags << tag
+ assert_equal(count + 1, post_thinking.taggings(true).size)
+ assert_equal(count + 1, post_thinking.tags(true).size)
+
+ assert_nothing_raised { post_thinking.tags.delete(tag) }
+ assert_equal(count, post_thinking.tags.size)
+ assert_equal(count, post_thinking.tags(true).size)
+ assert_equal(count, post_thinking.taggings(true).size)
+ assert_equal(tags_before.sort, post_thinking.tags.sort)
+ end
+
+ def test_delete_associate_when_deleting_from_has_many_through_with_multiple_tags
+ count = posts(:thinking).tags.count
+ tags_before = posts(:thinking).tags
+ doomed = Tag.create!(:name => 'doomed')
+ doomed2 = Tag.create!(:name => 'doomed2')
+ quaked = Tag.create!(:name => 'quaked')
+ post_thinking = posts(:thinking)
+ post_thinking.tags << doomed << doomed2
+ assert_equal(count + 2, post_thinking.tags(true).size)
+
+ assert_nothing_raised { post_thinking.tags.delete(doomed, doomed2, quaked) }
+ assert_equal(count, post_thinking.tags.size)
+ assert_equal(count, post_thinking.tags(true).size)
+ assert_equal(tags_before.sort, post_thinking.tags.sort)
+ end
+
+ def test_deleting_junk_from_has_many_through_should_raise_type_mismatch
+ assert_raise(ActiveRecord::AssociationTypeMismatch) { posts(:thinking).tags.delete("Uhh what now?") }
+ end
+
+ def test_has_many_through_sum_uses_calculations
+ assert_nothing_raised { authors(:david).comments.sum(:post_id) }
+ end
+
+ def test_calculations_on_has_many_through_should_disambiguate_fields
+ assert_nothing_raised { authors(:david).categories.maximum(:id) }
+ end
+
+ def test_calculations_on_has_many_through_should_not_disambiguate_fields_unless_necessary
+ assert_nothing_raised { authors(:david).categories.maximum("categories.id") }
+ end
+
+ def test_has_many_through_has_many_with_sti
+ assert_equal [comments(:does_it_hurt)], authors(:david).special_post_comments
+ end
+
+ def test_uniq_has_many_through_should_retain_order
+ comment_ids = authors(:david).comments.map(&:id)
+ assert_equal comment_ids.sort, authors(:david).ordered_uniq_comments.map(&:id)
+ assert_equal comment_ids.sort.reverse, authors(:david).ordered_uniq_comments_desc.map(&:id)
+ end
+
+ def test_polymorphic_has_many
+ expected = taggings(:welcome_general)
+ p = Post.find(posts(:welcome).id, :include => :taggings)
+ assert_no_queries {assert p.taggings.include?(expected)}
+ assert posts(:welcome).taggings.include?(taggings(:welcome_general))
+ end
+
+ def test_polymorphic_has_one
+ expected = posts(:welcome)
+
+ tagging = Tagging.find(taggings(:welcome_general).id, :include => :taggable)
+ assert_no_queries { assert_equal expected, tagging.taggable}
+ end
+
+ def test_polymorphic_belongs_to
+ p = Post.find(posts(:welcome).id, :include => {:taggings => :taggable})
+ assert_no_queries {assert_equal posts(:welcome), p.taggings.first.taggable}
+ end
+
+ def test_preload_polymorphic_has_many_through
+ posts = Post.find(:all, :order => 'posts.id')
+ posts_with_tags = Post.find(:all, :include => :tags, :order => 'posts.id')
+ assert_equal posts.length, posts_with_tags.length
+ posts.length.times do |i|
+ assert_equal posts[i].tags.length, assert_no_queries { posts_with_tags[i].tags.length }
+ end
+ end
+
+ def test_preload_polymorph_many_types
+ taggings = Tagging.find :all, :include => :taggable, :conditions => ['taggable_type != ?', 'FakeModel']
+ assert_no_queries do
+ taggings.first.taggable.id
+ taggings[1].taggable.id
+ end
+
+ taggables = taggings.map(&:taggable)
+ assert taggables.include?(items(:dvd))
+ assert taggables.include?(posts(:welcome))
+ end
+
+ def test_preload_nil_polymorphic_belongs_to
+ assert_nothing_raised do
+ taggings = Tagging.find(:all, :include => :taggable, :conditions => ['taggable_type IS NULL'])
+ end
+ end
+
+ def test_preload_polymorphic_has_many
+ posts = Post.find(:all, :order => 'posts.id')
+ posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
+ assert_equal posts.length, posts_with_taggings.length
+ posts.length.times do |i|
+ assert_equal posts[i].taggings.length, assert_no_queries { posts_with_taggings[i].taggings.length }
+ end
+ end
+
+ def test_belongs_to_shared_parent
+ comments = Comment.find(:all, :include => :post, :conditions => 'post_id = 1')
+ assert_no_queries do
+ assert_equal comments.first.post, comments[1].post
+ end
+ end
+
+ def test_has_many_through_include_uses_array_include_after_loaded
+ david = authors(:david)
+ david.categories.class # force load target
+
+ category = david.categories.first
+
+ assert_no_queries do
+ assert david.categories.loaded?
+ assert david.categories.include?(category)
+ end
+ end
+
+ def test_has_many_through_include_checks_if_record_exists_if_target_not_loaded
+ david = authors(:david)
+ category = david.categories.first
+
+ david.reload
+ assert ! david.categories.loaded?
+ assert_queries(1) do
+ assert david.categories.include?(category)
+ end
+ assert ! david.categories.loaded?
+ end
+
+ def test_has_many_through_include_returns_false_for_non_matching_record_to_verify_scoping
+ david = authors(:david)
+ category = Category.create!(:name => 'Not Associated')
+
+ assert ! david.categories.loaded?
+ assert ! david.categories.include?(category)
+ end
+
+ private
+ # create dynamic Post models to allow different dependency options
+ def find_post_with_dependency(post_id, association, association_name, dependency)
+ class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
+ Post.find(post_id).update_attribute :type, class_name
+ klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
+ klass.set_table_name 'posts'
+ klass.send(association, association_name, :as => :taggable, :dependent => dependency)
+ klass.find(post_id)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/associations_test.rb b/vendor/rails/activerecord/test/cases/associations_test.rb
new file mode 100644
index 0000000..59349dd
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/associations_test.rb
@@ -0,0 +1,262 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+require 'models/computer'
+require 'models/customer'
+require 'models/order'
+require 'models/categorization'
+require 'models/category'
+require 'models/post'
+require 'models/author'
+require 'models/comment'
+require 'models/tag'
+require 'models/tagging'
+require 'models/person'
+require 'models/reader'
+require 'models/parrot'
+require 'models/pirate'
+require 'models/treasure'
+require 'models/price_estimate'
+require 'models/club'
+require 'models/member'
+require 'models/membership'
+require 'models/sponsor'
+
+class AssociationsTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :developers, :projects, :developers_projects,
+ :computers
+
+ def test_include_with_order_works
+ assert_nothing_raised {Account.find(:first, :order => 'id', :include => :firm)}
+ assert_nothing_raised {Account.find(:first, :order => :id, :include => :firm)}
+ end
+
+ def test_bad_collection_keys
+ assert_raise(ArgumentError, 'ActiveRecord should have barked on bad collection keys') do
+ Class.new(ActiveRecord::Base).has_many(:wheels, :name => 'wheels')
+ end
+ end
+
+ def test_should_construct_new_finder_sql_after_create
+ person = Person.new :first_name => 'clark'
+ assert_equal [], person.readers.find(:all)
+ person.save!
+ reader = Reader.create! :person => person, :post => Post.new(:title => "foo", :body => "bar")
+ assert_equal [reader], person.readers.find(:all)
+ end
+
+ def test_force_reload
+ firm = Firm.new("name" => "A New Firm, Inc")
+ firm.save
+ firm.clients.each {|c|} # forcing to load all clients
+ assert firm.clients.empty?, "New firm shouldn't have client objects"
+ assert_equal 0, firm.clients.size, "New firm should have 0 clients"
+
+ client = Client.new("name" => "TheClient.com", "firm_id" => firm.id)
+ client.save
+
+ assert firm.clients.empty?, "New firm should have cached no client objects"
+ assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count"
+
+ assert !firm.clients(true).empty?, "New firm should have reloaded client objects"
+ assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count"
+ end
+
+ def test_storing_in_pstore
+ require "tmpdir"
+ store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test")
+ File.delete(store_filename) if File.exist?(store_filename)
+ require "pstore"
+ apple = Firm.create("name" => "Apple")
+ natural = Client.new("name" => "Natural Company")
+ apple.clients << natural
+
+ db = PStore.new(store_filename)
+ db.transaction do
+ db["apple"] = apple
+ end
+
+ db = PStore.new(store_filename)
+ db.transaction do
+ assert_equal "Natural Company", db["apple"].clients.first.name
+ end
+ end
+end
+
+class AssociationProxyTest < ActiveRecord::TestCase
+ fixtures :authors, :posts, :categorizations, :categories, :developers, :projects, :developers_projects
+
+ def test_proxy_accessors
+ welcome = posts(:welcome)
+ assert_equal welcome, welcome.author.proxy_owner
+ assert_equal welcome.class.reflect_on_association(:author), welcome.author.proxy_reflection
+ welcome.author.class # force load target
+ assert_equal welcome.author, welcome.author.proxy_target
+
+ david = authors(:david)
+ assert_equal david, david.posts.proxy_owner
+ assert_equal david.class.reflect_on_association(:posts), david.posts.proxy_reflection
+ david.posts.class # force load target
+ assert_equal david.posts, david.posts.proxy_target
+
+ assert_equal david, david.posts_with_extension.testing_proxy_owner
+ assert_equal david.class.reflect_on_association(:posts_with_extension), david.posts_with_extension.testing_proxy_reflection
+ david.posts_with_extension.class # force load target
+ assert_equal david.posts_with_extension, david.posts_with_extension.testing_proxy_target
+ end
+
+ def test_push_does_not_load_target
+ david = authors(:david)
+
+ david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!"))
+ assert !david.posts.loaded?
+ assert david.posts.include?(post)
+ end
+
+ def test_push_has_many_through_does_not_load_target
+ david = authors(:david)
+
+ david.categories << categories(:technology)
+ assert !david.categories.loaded?
+ assert david.categories.include?(categories(:technology))
+ end
+
+ def test_push_followed_by_save_does_not_load_target
+ david = authors(:david)
+
+ david.posts << (post = Post.new(:title => "New on Edge", :body => "More cool stuff!"))
+ assert !david.posts.loaded?
+ david.save
+ assert !david.posts.loaded?
+ assert david.posts.include?(post)
+ end
+
+ def test_push_does_not_lose_additions_to_new_record
+ josh = Author.new(:name => "Josh")
+ josh.posts << Post.new(:title => "New on Edge", :body => "More cool stuff!")
+ assert josh.posts.loaded?
+ assert_equal 1, josh.posts.size
+ end
+
+ def test_save_on_parent_does_not_load_target
+ david = developers(:david)
+
+ assert !david.projects.loaded?
+ david.update_attribute(:created_at, Time.now)
+ assert !david.projects.loaded?
+ end
+
+ def test_inspect_does_not_reload_a_not_yet_loaded_target
+ andreas = Developer.new :name => 'Andreas', :log => 'new developer added'
+ assert !andreas.audit_logs.loaded?
+ assert_match(/message: "new developer added"/, andreas.audit_logs.inspect)
+ end
+
+ def test_save_on_parent_saves_children
+ developer = Developer.create :name => "Bryan", :salary => 50_000
+ assert_equal 1, developer.reload.audit_logs.size
+ end
+
+ def test_create_via_association_with_block
+ post = authors(:david).posts.create(:title => "New on Edge") {|p| p.body = "More cool stuff!"}
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
+ def test_create_with_bang_via_association_with_block
+ post = authors(:david).posts.create!(:title => "New on Edge") {|p| p.body = "More cool stuff!"}
+ assert_equal post.title, "New on Edge"
+ assert_equal post.body, "More cool stuff!"
+ end
+
+ def test_failed_reload_returns_nil
+ p = setup_dangling_association
+ assert_nil p.author.reload
+ end
+
+ def test_failed_reset_returns_nil
+ p = setup_dangling_association
+ assert_nil p.author.reset
+ end
+
+ def test_reload_returns_assocition
+ david = developers(:david)
+ assert_nothing_raised do
+ assert_equal david.projects, david.projects.reload.reload
+ end
+ end
+
+ def setup_dangling_association
+ josh = Author.create(:name => "Josh")
+ p = Post.create(:title => "New on Edge", :body => "More cool stuff!", :author => josh)
+ josh.destroy
+ p
+ end
+end
+
+class OverridingAssociationsTest < ActiveRecord::TestCase
+ class Person < ActiveRecord::Base; end
+ class DifferentPerson < ActiveRecord::Base; end
+
+ class PeopleList < ActiveRecord::Base
+ has_and_belongs_to_many :has_and_belongs_to_many, :before_add => :enlist
+ has_many :has_many, :before_add => :enlist
+ belongs_to :belongs_to
+ has_one :has_one
+ end
+
+ class DifferentPeopleList < PeopleList
+ # Different association with the same name, callbacks should be omitted here.
+ has_and_belongs_to_many :has_and_belongs_to_many, :class_name => 'DifferentPerson'
+ has_many :has_many, :class_name => 'DifferentPerson'
+ belongs_to :belongs_to, :class_name => 'DifferentPerson'
+ has_one :has_one, :class_name => 'DifferentPerson'
+ end
+
+ def test_habtm_association_redefinition_callbacks_should_differ_and_not_inherited
+ # redeclared association on AR descendant should not inherit callbacks from superclass
+ callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many)
+ assert_equal([:enlist], callbacks)
+ callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_and_belongs_to_many)
+ assert_equal([], callbacks)
+ end
+
+ def test_has_many_association_redefinition_callbacks_should_differ_and_not_inherited
+ # redeclared association on AR descendant should not inherit callbacks from superclass
+ callbacks = PeopleList.read_inheritable_attribute(:before_add_for_has_many)
+ assert_equal([:enlist], callbacks)
+ callbacks = DifferentPeopleList.read_inheritable_attribute(:before_add_for_has_many)
+ assert_equal([], callbacks)
+ end
+
+ def test_habtm_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_and_belongs_to_many),
+ DifferentPeopleList.reflect_on_association(:has_and_belongs_to_many)
+ )
+ end
+
+ def test_has_many_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_many),
+ DifferentPeopleList.reflect_on_association(:has_many)
+ )
+ end
+
+ def test_belongs_to_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:belongs_to),
+ DifferentPeopleList.reflect_on_association(:belongs_to)
+ )
+ end
+
+ def test_has_one_association_redefinition_reflections_should_differ_and_not_inherited
+ assert_not_equal(
+ PeopleList.reflect_on_association(:has_one),
+ DifferentPeopleList.reflect_on_association(:has_one)
+ )
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/attribute_methods_test.rb b/vendor/rails/activerecord/test/cases/attribute_methods_test.rb
new file mode 100644
index 0000000..c336fd9
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/attribute_methods_test.rb
@@ -0,0 +1,238 @@
+require "cases/helper"
+require 'models/topic'
+
+class AttributeMethodsTest < ActiveRecord::TestCase
+ fixtures :topics
+ def setup
+ @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup
+ @target = Class.new(ActiveRecord::Base)
+ @target.table_name = 'topics'
+ end
+
+ def teardown
+ ActiveRecord::Base.send(:attribute_method_suffixes).clear
+ ActiveRecord::Base.attribute_method_suffix *@old_suffixes
+ end
+
+ def test_match_attribute_method_query_returns_match_data
+ assert_not_nil md = @target.match_attribute_method?('title=')
+ assert_equal 'title', md.pre_match
+ assert_equal ['='], md.captures
+
+ %w(_hello_world ist! _maybe?).each do |suffix|
+ @target.class_eval "def attribute#{suffix}(*args) args end"
+ @target.attribute_method_suffix suffix
+
+ assert_not_nil md = @target.match_attribute_method?("title#{suffix}")
+ assert_equal 'title', md.pre_match
+ assert_equal [suffix], md.captures
+ end
+ end
+
+ def test_declared_attribute_method_affects_respond_to_and_method_missing
+ topic = @target.new(:title => 'Budget')
+ assert topic.respond_to?('title')
+ assert_equal 'Budget', topic.title
+ assert !topic.respond_to?('title_hello_world')
+ assert_raise(NoMethodError) { topic.title_hello_world }
+
+ %w(_hello_world _it! _candidate= able?).each do |suffix|
+ @target.class_eval "def attribute#{suffix}(*args) args end"
+ @target.attribute_method_suffix suffix
+
+ meth = "title#{suffix}"
+ assert topic.respond_to?(meth)
+ assert_equal ['title'], topic.send(meth)
+ assert_equal ['title', 'a'], topic.send(meth, 'a')
+ assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3)
+ end
+ end
+
+ def test_should_unserialize_attributes_for_frozen_records
+ myobj = {:value1 => :value2}
+ topic = Topic.create("content" => myobj)
+ topic.freeze
+ assert_equal myobj, topic.content
+ end
+
+ def test_kernel_methods_not_implemented_in_activerecord
+ %w(test name display y).each do |method|
+ assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined"
+ end
+ end
+
+ def test_primary_key_implemented
+ assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id')
+ end
+
+ def test_defined_kernel_methods_implemented_in_model
+ %w(test name display y).each do |method|
+ klass = Class.new ActiveRecord::Base
+ klass.class_eval "def #{method}() 'defined #{method}' end"
+ assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
+ end
+ end
+
+ def test_defined_kernel_methods_implemented_in_model_abstract_subclass
+ %w(test name display y).each do |method|
+ abstract = Class.new ActiveRecord::Base
+ abstract.class_eval "def #{method}() 'defined #{method}' end"
+ abstract.abstract_class = true
+ klass = Class.new abstract
+ assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined"
+ end
+ end
+
+ def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model
+ %w(save create_or_update).each do |method|
+ klass = Class.new ActiveRecord::Base
+ klass.class_eval "def #{method}() 'defined #{method}' end"
+ assert_raises ActiveRecord::DangerousAttributeError do
+ klass.instance_method_already_implemented?(method)
+ end
+ end
+ end
+
+ def test_only_time_related_columns_are_meant_to_be_cached_by_default
+ expected = %w(datetime timestamp time date).sort
+ assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort
+ end
+
+ def test_declaring_attributes_as_cached_adds_them_to_the_attributes_cached_by_default
+ default_attributes = Topic.cached_attributes
+ Topic.cache_attributes :replies_count
+ expected = default_attributes + ["replies_count"]
+ assert_equal expected.sort, Topic.cached_attributes.sort
+ Topic.instance_variable_set "@cached_attributes", nil
+ end
+
+ def test_time_related_columns_are_actually_cached
+ column_types = %w(datetime timestamp time date).map(&:to_sym)
+ column_names = Topic.columns.select{|c| column_types.include?(c.type) }.map(&:name)
+
+ assert_equal column_names.sort, Topic.cached_attributes.sort
+ assert_equal time_related_columns_on_topic.sort, Topic.cached_attributes.sort
+ end
+
+ def test_accessing_cached_attributes_caches_the_converted_values_and_nothing_else
+ t = topics(:first)
+ cache = t.instance_variable_get "@attributes_cache"
+
+ assert_not_nil cache
+ assert cache.empty?
+
+ all_columns = Topic.columns.map(&:name)
+ cached_columns = time_related_columns_on_topic
+ uncached_columns = all_columns - cached_columns
+
+ all_columns.each do |attr_name|
+ attribute_gets_cached = Topic.cache_attribute?(attr_name)
+ val = t.send attr_name unless attr_name == "type"
+ if attribute_gets_cached
+ assert cached_columns.include?(attr_name)
+ assert_equal val, cache[attr_name]
+ else
+ assert uncached_columns.include?(attr_name)
+ assert !cache.include?(attr_name)
+ end
+ end
+ end
+
+ def test_time_attributes_are_retrieved_in_current_time_zone
+ in_time_zone "Pacific Time (US & Canada)" do
+ utc_time = Time.utc(2008, 1, 1)
+ record = @target.new
+ record[:written_on] = utc_time
+ assert_equal utc_time, record.written_on # record.written on is equal to (i.e., simultaneous with) utc_time
+ assert_kind_of ActiveSupport::TimeWithZone, record.written_on # but is a TimeWithZone
+ assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone # and is in the current Time.zone
+ assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time # and represents time values adjusted accordingly
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_to_utc
+ in_time_zone "Pacific Time (US & Canada)" do
+ utc_time = Time.utc(2008, 1, 1)
+ record = @target.new
+ record.written_on = utc_time
+ assert_equal utc_time, record.written_on
+ assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
+ assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_in_other_time_zone
+ utc_time = Time.utc(2008, 1, 1)
+ cst_time = utc_time.in_time_zone("Central Time (US & Canada)")
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ record.written_on = cst_time
+ assert_equal utc_time, record.written_on
+ assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
+ assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_with_string
+ utc_time = Time.utc(2008, 1, 1)
+ (-11..13).each do |timezone_offset|
+ time_string = utc_time.in_time_zone(timezone_offset).to_s
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ record.written_on = time_string
+ assert_equal Time.zone.parse(time_string), record.written_on
+ assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
+ assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
+ end
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_to_blank_string_returns_nil
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ record.written_on = ' '
+ assert_nil record.written_on
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_interprets_time_zone_unaware_string_in_time_zone
+ time_string = 'Tue Jan 01 00:00:00 2008'
+ (-11..13).each do |timezone_offset|
+ in_time_zone timezone_offset do
+ record = @target.new
+ record.written_on = time_string
+ assert_equal Time.zone.parse(time_string), record.written_on
+ assert_equal TimeZone[timezone_offset], record.written_on.time_zone
+ assert_equal Time.utc(2008, 1, 1), record.written_on.time
+ end
+ end
+ end
+
+ def test_setting_time_zone_aware_attribute_in_current_time_zone
+ utc_time = Time.utc(2008, 1, 1)
+ in_time_zone "Pacific Time (US & Canada)" do
+ record = @target.new
+ record.written_on = utc_time.in_time_zone
+ assert_equal utc_time, record.written_on
+ assert_equal TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
+ assert_equal Time.utc(2007, 12, 31, 16), record.written_on.time
+ end
+ end
+
+ private
+ def time_related_columns_on_topic
+ Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name)
+ end
+
+ def in_time_zone(zone)
+ old_zone = Time.zone
+ old_tz = ActiveRecord::Base.time_zone_aware_attributes
+
+ Time.zone = zone ? TimeZone[zone] : nil
+ ActiveRecord::Base.time_zone_aware_attributes = !zone.nil?
+ yield
+ ensure
+ Time.zone = old_zone
+ ActiveRecord::Base.time_zone_aware_attributes = old_tz
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/base_test.rb b/vendor/rails/activerecord/test/cases/base_test.rb
new file mode 100644
index 0000000..e07ec50
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/base_test.rb
@@ -0,0 +1,1974 @@
+require "cases/helper"
+require 'models/author'
+require 'models/topic'
+require 'models/reply'
+require 'models/category'
+require 'models/company'
+require 'models/customer'
+require 'models/developer'
+require 'models/project'
+require 'models/default'
+require 'models/auto_id'
+require 'models/column_name'
+require 'models/subscriber'
+require 'models/keyboard'
+require 'models/post'
+require 'models/comment'
+require 'models/minimalistic'
+require 'models/warehouse_thing'
+require 'rexml/document'
+
+class Category < ActiveRecord::Base; end
+class Smarts < ActiveRecord::Base; end
+class CreditCard < ActiveRecord::Base
+ class PinNumber < ActiveRecord::Base
+ class CvvCode < ActiveRecord::Base; end
+ class SubCvvCode < CvvCode; end
+ end
+ class SubPinNumber < PinNumber; end
+ class Brand < Category; end
+end
+class MasterCreditCard < ActiveRecord::Base; end
+class Post < ActiveRecord::Base; end
+class Computer < ActiveRecord::Base; end
+class NonExistentTable < ActiveRecord::Base; end
+class TestOracleDefault < ActiveRecord::Base; end
+
+class LoosePerson < ActiveRecord::Base
+ self.table_name = 'people'
+ self.abstract_class = true
+ attr_protected :credit_rating, :administrator
+end
+
+class LooseDescendant < LoosePerson
+ attr_protected :phone_number
+end
+
+class LooseDescendantSecond< LoosePerson
+ attr_protected :phone_number
+ attr_protected :name
+end
+
+class TightPerson < ActiveRecord::Base
+ self.table_name = 'people'
+ attr_accessible :name, :address
+end
+
+class TightDescendant < TightPerson
+ attr_accessible :phone_number
+end
+
+class ReadonlyTitlePost < Post
+ attr_readonly :title
+end
+
+class Booleantest < ActiveRecord::Base; end
+
+class Task < ActiveRecord::Base
+ attr_protected :starting
+end
+
+class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base
+ self.table_name = 'topics'
+ attr_accessible :author_name
+ attr_protected :content
+end
+
+class BasicsTest < ActiveRecord::TestCase
+ fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors
+
+ def test_table_exists
+ assert !NonExistentTable.table_exists?
+ assert Topic.table_exists?
+ end
+
+ def test_set_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "Budget", "author_name" => "Jason" }
+ topic.save
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ assert_equal(topics(:first).author_email_address, Topic.find(1).author_email_address)
+ end
+
+ def test_integers_as_nil
+ test = AutoId.create('value' => '')
+ assert_nil AutoId.find(test.id).value
+ end
+
+ def test_set_attributes_with_block
+ topic = Topic.new do |t|
+ t.title = "Budget"
+ t.author_name = "Jason"
+ end
+
+ assert_equal("Budget", topic.title)
+ assert_equal("Jason", topic.author_name)
+ end
+
+ def test_respond_to?
+ topic = Topic.find(1)
+ assert topic.respond_to?("title")
+ assert topic.respond_to?("title?")
+ assert topic.respond_to?("title=")
+ assert topic.respond_to?(:title)
+ assert topic.respond_to?(:title?)
+ assert topic.respond_to?(:title=)
+ assert topic.respond_to?("author_name")
+ assert topic.respond_to?("attribute_names")
+ assert !topic.respond_to?("nothingness")
+ assert !topic.respond_to?(:nothingness)
+ end
+
+ def test_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+ topic.save
+
+ assert_equal(%w( one two three ), Topic.find(topic.id).content)
+ end
+
+ def test_read_attributes_before_type_cast
+ category = Category.new({:name=>"Test categoty", :type => nil})
+ category_attrs = {"name"=>"Test categoty", "type" => nil}
+ assert_equal category_attrs , category.attributes_before_type_cast
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_read_attributes_before_type_cast_on_boolean
+ bool = Booleantest.create({ "value" => false })
+ assert_equal 0, bool.attributes_before_type_cast["value"]
+ end
+ end
+
+ def test_read_attributes_before_type_cast_on_datetime
+ developer = Developer.find(:first)
+ assert_equal developer.created_at.to_s(:db) , developer.attributes_before_type_cast["created_at"]
+ end
+
+ def test_hash_content
+ topic = Topic.new
+ topic.content = { "one" => 1, "two" => 2 }
+ topic.save
+
+ assert_equal 2, Topic.find(topic.id).content["two"]
+
+ topic.content_will_change!
+ topic.content["three"] = 3
+ topic.save
+
+ assert_equal 3, Topic.find(topic.id).content["three"]
+ end
+
+ def test_update_array_content
+ topic = Topic.new
+ topic.content = %w( one two three )
+
+ topic.content.push "four"
+ assert_equal(%w( one two three four ), topic.content)
+
+ topic.save
+
+ topic = Topic.find(topic.id)
+ topic.content << "five"
+ assert_equal(%w( one two three four five ), topic.content)
+ end
+
+ def test_case_sensitive_attributes_hash
+ # DB2 is not case-sensitive
+ return true if current_adapter?(:DB2Adapter)
+
+ assert_equal @loaded_fixtures['computers']['workstation'].to_hash, Computer.find(:first).attributes
+ end
+
+ def test_create
+ topic = Topic.new
+ topic.title = "New Topic"
+ topic.save
+ topic_reloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic_reloaded.title)
+ end
+
+ def test_save!
+ topic = Topic.new(:title => "New Topic")
+ assert topic.save!
+
+ reply = Reply.new
+ assert_raise(ActiveRecord::RecordInvalid) { reply.save! }
+ end
+
+ def test_save_null_string_attributes
+ topic = Topic.find(1)
+ topic.attributes = { "title" => "null", "author_name" => "null" }
+ topic.save!
+ topic.reload
+ assert_equal("null", topic.title)
+ assert_equal("null", topic.author_name)
+ end
+
+ def test_save_nil_string_attributes
+ topic = Topic.find(1)
+ topic.title = nil
+ topic.save!
+ topic.reload
+ assert_nil topic.title
+ end
+
+ def test_save_for_record_with_only_primary_key
+ minimalistic = Minimalistic.new
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_save_for_record_with_only_primary_key_that_is_provided
+ assert_nothing_raised { Minimalistic.create!(:id => 2) }
+ end
+
+ def test_hashes_not_mangled
+ new_topic = { :title => "New Topic" }
+ new_topic_values = { :title => "AnotherTopic" }
+
+ topic = Topic.new(new_topic)
+ assert_equal new_topic[:title], topic.title
+
+ topic.attributes= new_topic_values
+ assert_equal new_topic_values[:title], topic.title
+ end
+
+ def test_create_many
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
+ assert_equal 2, topics.size
+ assert_equal "first", topics.first.title
+ end
+
+ def test_create_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = 'Another New Topic'
+ topic.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topic.save }
+ end
+
+ def test_create_through_factory
+ topic = Topic.create("title" => "New Topic")
+ topicReloaded = Topic.find(topic.id)
+ assert_equal(topic, topicReloaded)
+ end
+
+ def test_create_through_factory_with_block
+ topic = Topic.create("title" => "New Topic") do |t|
+ t.author_name = "David"
+ end
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("New Topic", topic.title)
+ assert_equal("David", topic.author_name)
+ end
+
+ def test_create_many_through_factory_with_block
+ topics = Topic.create([ { "title" => "first" }, { "title" => "second" }]) do |t|
+ t.author_name = "David"
+ end
+ assert_equal 2, topics.size
+ topic1, topic2 = Topic.find(topics[0].id), Topic.find(topics[1].id)
+ assert_equal "first", topic1.title
+ assert_equal "David", topic1.author_name
+ assert_equal "second", topic2.title
+ assert_equal "David", topic2.author_name
+ end
+
+ def test_update
+ topic = Topic.new
+ topic.title = "Another New Topic"
+ topic.written_on = "2003-12-12 23:23:00"
+ topic.save
+ topicReloaded = Topic.find(topic.id)
+ assert_equal("Another New Topic", topicReloaded.title)
+
+ topicReloaded.title = "Updated topic"
+ topicReloaded.save
+
+ topicReloadedAgain = Topic.find(topic.id)
+
+ assert_equal("Updated topic", topicReloadedAgain.title)
+ end
+
+ def test_update_columns_not_equal_attributes
+ topic = Topic.new
+ topic.title = "Still another topic"
+ topic.save
+
+ topicReloaded = Topic.find(topic.id)
+ topicReloaded.title = "A New Topic"
+ topicReloaded.send :write_attribute, 'does_not_exist', 'test'
+ assert_nothing_raised { topicReloaded.save }
+ end
+
+ def test_update_for_record_with_only_primary_key
+ minimalistic = minimalistics(:first)
+ assert_nothing_raised { minimalistic.save }
+ end
+
+ def test_write_attribute
+ topic = Topic.new
+ topic.send(:write_attribute, :title, "Still another topic")
+ assert_equal "Still another topic", topic.title
+
+ topic.send(:write_attribute, "title", "Still another topic: part 2")
+ assert_equal "Still another topic: part 2", topic.title
+ end
+
+ def test_read_attribute
+ topic = Topic.new
+ topic.title = "Don't change the topic"
+ assert_equal "Don't change the topic", topic.send(:read_attribute, "title")
+ assert_equal "Don't change the topic", topic["title"]
+
+ assert_equal "Don't change the topic", topic.send(:read_attribute, :title)
+ assert_equal "Don't change the topic", topic[:title]
+ end
+
+ def test_read_attribute_when_false
+ topic = topics(:first)
+ topic.approved = false
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ assert !topic.approved?, "approved should be false"
+ end
+
+ def test_read_attribute_when_true
+ topic = topics(:first)
+ topic.approved = true
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ assert topic.approved?, "approved should be true"
+ end
+
+ def test_read_write_boolean_attribute
+ topic = Topic.new
+ # puts ""
+ # puts "New Topic"
+ # puts topic.inspect
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "false"
+ # puts "Expecting false"
+ # puts topic.inspect
+ assert !topic.approved?, "approved should be false"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ topic.approved = "true"
+ # puts "Expecting true"
+ # puts topic.inspect
+ assert topic.approved?, "approved should be true"
+ # puts ""
+ end
+
+ def test_query_attribute_string
+ [nil, "", " "].each do |value|
+ assert_equal false, Topic.new(:author_name => value).author_name?
+ end
+
+ assert_equal true, Topic.new(:author_name => "Name").author_name?
+ end
+
+ def test_query_attribute_number
+ [nil, 0, "0"].each do |value|
+ assert_equal false, Developer.new(:salary => value).salary?
+ end
+
+ assert_equal true, Developer.new(:salary => 1).salary?
+ assert_equal true, Developer.new(:salary => "1").salary?
+ end
+
+ def test_query_attribute_boolean
+ [nil, "", false, "false", "f", 0].each do |value|
+ assert_equal false, Topic.new(:approved => value).approved?
+ end
+
+ [true, "true", "1", 1].each do |value|
+ assert_equal true, Topic.new(:approved => value).approved?
+ end
+ end
+
+ def test_query_attribute_with_custom_fields
+ object = Company.find_by_sql(<<-SQL).first
+ SELECT c1.*, c2.ruby_type as string_value, c2.rating as int_value
+ FROM companies c1, companies c2
+ WHERE c1.firm_id = c2.id
+ AND c1.id = 2
+ SQL
+
+ assert_equal "Firm", object.string_value
+ assert object.string_value?
+
+ object.string_value = " "
+ assert !object.string_value?
+
+ assert_equal 1, object.int_value.to_i
+ assert object.int_value?
+
+ object.int_value = "0"
+ assert !object.int_value?
+ end
+
+
+ def test_reader_for_invalid_column_names
+ Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil)
+ assert !Topic.generated_methods.include?("mumub-jumbo")
+ end
+
+ def test_non_attribute_access_and_assignment
+ topic = Topic.new
+ assert !topic.respond_to?("mumbo")
+ assert_raises(NoMethodError) { topic.mumbo }
+ assert_raises(NoMethodError) { topic.mumbo = 5 }
+ end
+
+ def test_preserving_date_objects
+ # SQL Server doesn't have a separate column type just for dates, so all are returned as time
+ return true if current_adapter?(:SQLServerAdapter)
+
+ if current_adapter?(:SybaseAdapter, :OracleAdapter)
+ # Sybase ctlib does not (yet?) support the date type; use datetime instead.
+ # Oracle treats all dates/times as Time.
+ assert_kind_of(
+ Time, Topic.find(1).last_read,
+ "The last_read attribute should be of the Time class"
+ )
+ else
+ assert_kind_of(
+ Date, Topic.find(1).last_read,
+ "The last_read attribute should be of the Date class"
+ )
+ end
+ end
+
+ def test_preserving_time_objects
+ assert_kind_of(
+ Time, Topic.find(1).bonus_time,
+ "The bonus_time attribute should be of the Time class"
+ )
+
+ assert_kind_of(
+ Time, Topic.find(1).written_on,
+ "The written_on attribute should be of the Time class"
+ )
+
+ # For adapters which support microsecond resolution.
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 11, Topic.find(1).written_on.sec
+ assert_equal 223300, Topic.find(1).written_on.usec
+ assert_equal 9900, Topic.find(2).written_on.usec
+ end
+ end
+
+ def test_custom_mutator
+ topic = Topic.find(1)
+ # This mutator is protected in the class definition
+ topic.send(:approved=, true)
+ assert topic.instance_variable_get("@custom_approved")
+ end
+
+ def test_destroy
+ topic = Topic.find(1)
+ assert_equal topic, topic.destroy, 'topic.destroy did not return self'
+ assert topic.frozen?, 'topic not frozen after destroy'
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) }
+ end
+
+ def test_record_not_found_exception
+ assert_raises(ActiveRecord::RecordNotFound) { topicReloaded = Topic.find(99999) }
+ end
+
+ def test_initialize_with_attributes
+ topic = Topic.new({
+ "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23"
+ })
+
+ assert_equal("initialized from attributes", topic.title)
+ end
+
+ def test_initialize_with_invalid_attribute
+ begin
+ topic = Topic.new({ "title" => "test",
+ "last_read(1i)" => "2005", "last_read(2i)" => "2", "last_read(3i)" => "31"})
+ rescue ActiveRecord::MultiparameterAssignmentErrors => ex
+ assert_equal(1, ex.errors.size)
+ assert_equal("last_read", ex.errors[0].attribute)
+ end
+ end
+
+ def test_load
+ topics = Topic.find(:all, :order => 'id')
+ assert_equal(4, topics.size)
+ assert_equal(topics(:first).title, topics.first.title)
+ end
+
+ def test_load_with_condition
+ topics = Topic.find(:all, :conditions => "author_name = 'Mary'")
+
+ assert_equal(1, topics.size)
+ assert_equal(topics(:second).title, topics.first.title)
+ end
+
+ def test_table_name_guesses
+ classes = [Category, Smarts, CreditCard, CreditCard::PinNumber, CreditCard::PinNumber::CvvCode, CreditCard::SubPinNumber, CreditCard::Brand, MasterCreditCard]
+
+ assert_equal "topics", Topic.table_name
+
+ assert_equal "categories", Category.table_name
+ assert_equal "smarts", Smarts.table_name
+ assert_equal "credit_cards", CreditCard.table_name
+ assert_equal "credit_card_pin_numbers", CreditCard::PinNumber.table_name
+ assert_equal "credit_card_pin_number_cvv_codes", CreditCard::PinNumber::CvvCode.table_name
+ assert_equal "credit_card_pin_numbers", CreditCard::SubPinNumber.table_name
+ assert_equal "categories", CreditCard::Brand.table_name
+ assert_equal "master_credit_cards", MasterCreditCard.table_name
+
+ ActiveRecord::Base.pluralize_table_names = false
+ classes.each(&:reset_table_name)
+
+ assert_equal "category", Category.table_name
+ assert_equal "smarts", Smarts.table_name
+ assert_equal "credit_card", CreditCard.table_name
+ assert_equal "credit_card_pin_number", CreditCard::PinNumber.table_name
+ assert_equal "credit_card_pin_number_cvv_code", CreditCard::PinNumber::CvvCode.table_name
+ assert_equal "credit_card_pin_number", CreditCard::SubPinNumber.table_name
+ assert_equal "category", CreditCard::Brand.table_name
+ assert_equal "master_credit_card", MasterCreditCard.table_name
+
+ ActiveRecord::Base.pluralize_table_names = true
+ classes.each(&:reset_table_name)
+
+ ActiveRecord::Base.table_name_prefix = "test_"
+ Category.reset_table_name
+ assert_equal "test_categories", Category.table_name
+ ActiveRecord::Base.table_name_suffix = "_test"
+ Category.reset_table_name
+ assert_equal "test_categories_test", Category.table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ Category.reset_table_name
+ assert_equal "categories_test", Category.table_name
+ ActiveRecord::Base.table_name_suffix = ""
+ Category.reset_table_name
+ assert_equal "categories", Category.table_name
+
+ ActiveRecord::Base.pluralize_table_names = false
+ ActiveRecord::Base.table_name_prefix = "test_"
+ Category.reset_table_name
+ assert_equal "test_category", Category.table_name
+ ActiveRecord::Base.table_name_suffix = "_test"
+ Category.reset_table_name
+ assert_equal "test_category_test", Category.table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ Category.reset_table_name
+ assert_equal "category_test", Category.table_name
+ ActiveRecord::Base.table_name_suffix = ""
+ Category.reset_table_name
+ assert_equal "category", Category.table_name
+
+ ActiveRecord::Base.pluralize_table_names = true
+ classes.each(&:reset_table_name)
+ end
+
+ def test_destroy_all
+ original_count = Topic.count
+ topics_by_mary = Topic.count(:conditions => mary = "author_name = 'Mary'")
+
+ Topic.destroy_all mary
+ assert_equal original_count - topics_by_mary, Topic.count
+ end
+
+ def test_destroy_many
+ assert_equal 3, Client.count
+ Client.destroy([2, 3])
+ assert_equal 1, Client.count
+ end
+
+ def test_delete_many
+ original_count = Topic.count
+ Topic.delete(deleting = [1, 2])
+ assert_equal original_count - deleting.size, Topic.count
+ end
+
+ def test_boolean_attributes
+ assert ! Topic.find(1).approved?
+ assert Topic.find(2).approved?
+ end
+
+ def test_increment_counter
+ Topic.increment_counter("replies_count", 1)
+ assert_equal 2, Topic.find(1).replies_count
+
+ Topic.increment_counter("replies_count", 1)
+ assert_equal 3, Topic.find(1).replies_count
+ end
+
+ def test_decrement_counter
+ Topic.decrement_counter("replies_count", 2)
+ assert_equal -1, Topic.find(2).replies_count
+
+ Topic.decrement_counter("replies_count", 2)
+ assert_equal -2, Topic.find(2).replies_count
+ end
+
+ def test_update_all
+ assert_equal Topic.count, Topic.update_all("content = 'bulk updated!'")
+ assert_equal "bulk updated!", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', 'bulk updated again!'])
+ assert_equal "bulk updated again!", Topic.find(1).content
+ assert_equal "bulk updated again!", Topic.find(2).content
+
+ assert_equal Topic.count, Topic.update_all(['content = ?', nil])
+ assert_nil Topic.find(1).content
+ end
+
+ def test_update_all_with_hash
+ assert_not_nil Topic.find(1).last_read
+ assert_equal Topic.count, Topic.update_all(:content => 'bulk updated with hash!', :last_read => nil)
+ assert_equal "bulk updated with hash!", Topic.find(1).content
+ assert_equal "bulk updated with hash!", Topic.find(2).content
+ assert_nil Topic.find(1).last_read
+ assert_nil Topic.find(2).last_read
+ end
+
+ def test_update_all_with_non_standard_table_name
+ assert_equal 1, WarehouseThing.update_all(['value = ?', 0], ['id = ?', 1])
+ assert_equal 0, WarehouseThing.find(1).value
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_update_all_with_order_and_limit
+ assert_equal 1, Topic.update_all("content = 'bulk updated!'", nil, :limit => 1, :order => 'id DESC')
+ end
+ end
+
+ def test_update_all_ignores_order_limit_from_association
+ author = Author.find(1)
+ assert_nothing_raised do
+ assert_equal author.posts_with_comments_and_categories.length, author.posts_with_comments_and_categories.update_all("body = 'bulk update!'")
+ end
+ end
+
+ def test_update_many
+ topic_data = { 1 => { "content" => "1 updated" }, 2 => { "content" => "2 updated" } }
+ updated = Topic.update(topic_data.keys, topic_data.values)
+
+ assert_equal 2, updated.size
+ assert_equal "1 updated", Topic.find(1).content
+ assert_equal "2 updated", Topic.find(2).content
+ end
+
+ def test_delete_all
+ assert Topic.count > 0
+
+ assert_equal Topic.count, Topic.delete_all
+ end
+
+ def test_update_by_condition
+ Topic.update_all "content = 'bulk updated!'", ["approved = ?", true]
+ assert_equal "Have a nice day", Topic.find(1).content
+ assert_equal "bulk updated!", Topic.find(2).content
+ end
+
+ def test_attribute_present
+ t = Topic.new
+ t.title = "hello there!"
+ t.written_on = Time.now
+ assert t.attribute_present?("title")
+ assert t.attribute_present?("written_on")
+ assert !t.attribute_present?("content")
+ end
+
+ def test_attribute_keys_on_new_instance
+ t = Topic.new
+ assert_equal nil, t.title, "The topics table has a title column, so it should be nil"
+ assert_raise(NoMethodError) { t.title2 }
+ end
+
+ def test_class_name
+ assert_equal "Firm", ActiveRecord::Base.class_name("firms")
+ assert_equal "Category", ActiveRecord::Base.class_name("categories")
+ assert_equal "AccountHolder", ActiveRecord::Base.class_name("account_holder")
+
+ ActiveRecord::Base.pluralize_table_names = false
+ assert_equal "Firms", ActiveRecord::Base.class_name( "firms" )
+ ActiveRecord::Base.pluralize_table_names = true
+
+ ActiveRecord::Base.table_name_prefix = "test_"
+ assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms" )
+ ActiveRecord::Base.table_name_suffix = "_tests"
+ assert_equal "Firm", ActiveRecord::Base.class_name( "test_firms_tests" )
+ ActiveRecord::Base.table_name_prefix = ""
+ assert_equal "Firm", ActiveRecord::Base.class_name( "firms_tests" )
+ ActiveRecord::Base.table_name_suffix = ""
+ assert_equal "Firm", ActiveRecord::Base.class_name( "firms" )
+ end
+
+ def test_null_fields
+ assert_nil Topic.find(1).parent_id
+ assert_nil Topic.create("title" => "Hey you").parent_id
+ end
+
+ def test_default_values
+ topic = Topic.new
+ assert topic.approved?
+ assert_nil topic.written_on
+ assert_nil topic.bonus_time
+ assert_nil topic.last_read
+
+ topic.save
+
+ topic = Topic.find(topic.id)
+ assert topic.approved?
+ assert_nil topic.last_read
+
+ # Oracle has some funky default handling, so it requires a bit of
+ # extra testing. See ticket #2788.
+ if current_adapter?(:OracleAdapter)
+ test = TestOracleDefault.new
+ assert_equal "X", test.test_char
+ assert_equal "hello", test.test_string
+ assert_equal 3, test.test_int
+ end
+ end
+
+ # Oracle, SQLServer, and Sybase do not have a TIME datatype.
+ unless current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
+ def test_utc_as_time_zone
+ Topic.default_timezone = :utc
+ attributes = { "bonus_time" => "5:42:00AM" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ Topic.default_timezone = :local
+ end
+
+ def test_utc_as_time_zone_and_new
+ Topic.default_timezone = :utc
+ attributes = { "bonus_time(1i)"=>"2000",
+ "bonus_time(2i)"=>"1",
+ "bonus_time(3i)"=>"1",
+ "bonus_time(4i)"=>"10",
+ "bonus_time(5i)"=>"35",
+ "bonus_time(6i)"=>"50" }
+ topic = Topic.new(attributes)
+ assert_equal Time.utc(2000, 1, 1, 10, 35, 50), topic.bonus_time
+ Topic.default_timezone = :local
+ end
+ end
+
+ def test_default_values_on_empty_strings
+ topic = Topic.new
+ topic.approved = nil
+ topic.last_read = nil
+
+ topic.save
+
+ topic = Topic.find(topic.id)
+ assert_nil topic.last_read
+
+ # Sybase adapter does not allow nulls in boolean columns
+ if current_adapter?(:SybaseAdapter)
+ assert topic.approved == false
+ else
+ assert_nil topic.approved
+ end
+ end
+
+ def test_equality
+ assert_equal Topic.find(1), Topic.find(2).topic
+ end
+
+ def test_equality_of_new_records
+ assert_not_equal Topic.new, Topic.new
+ end
+
+ def test_hashing
+ assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ]
+ end
+
+ def test_destroy_new_record
+ client = Client.new
+ client.destroy
+ assert client.frozen?
+ end
+
+ def test_destroy_record_with_associations
+ client = Client.find(3)
+ client.destroy
+ assert client.frozen?
+ assert_kind_of Firm, client.firm
+ assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" }
+ end
+
+ def test_update_attribute
+ assert !Topic.find(1).approved?
+ Topic.find(1).update_attribute("approved", true)
+ assert Topic.find(1).approved?
+
+ Topic.find(1).update_attribute(:approved, false)
+ assert !Topic.find(1).approved?
+ end
+
+ def test_update_attributes
+ topic = Topic.find(1)
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+
+ topic.update_attributes("approved" => true, "title" => "The First Topic Updated")
+ topic.reload
+ assert topic.approved?
+ assert_equal "The First Topic Updated", topic.title
+
+ topic.update_attributes(:approved => false, :title => "The First Topic")
+ topic.reload
+ assert !topic.approved?
+ assert_equal "The First Topic", topic.title
+ end
+
+ def test_update_attributes!
+ reply = Reply.find(2)
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening")
+ reply.reload
+ assert_equal "The Second Topic of the day updated", reply.title
+ assert_equal "Have a nice evening", reply.content
+
+ reply.update_attributes!(:title => "The Second Topic of the day", :content => "Have a nice day")
+ reply.reload
+ assert_equal "The Second Topic of the day", reply.title
+ assert_equal "Have a nice day", reply.content
+
+ assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") }
+ end
+
+ def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used
+ topic = TopicWithProtectedContentAndAccessibleAuthorName.new
+ assert_raises(RuntimeError) { topic.attributes = { "author_name" => "me" } }
+ assert_raises(RuntimeError) { topic.attributes = { "content" => "stuff" } }
+ end
+
+ def test_mass_assignment_protection
+ firm = Firm.new
+ firm.attributes = { "name" => "Next Angle", "rating" => 5 }
+ assert_equal 1, firm.rating
+ end
+
+ def test_mass_assignment_protection_against_class_attribute_writers
+ [:logger, :configurations, :primary_key_prefix_type, :table_name_prefix, :table_name_suffix, :pluralize_table_names, :colorize_logging,
+ :default_timezone, :allow_concurrency, :schema_format, :verification_timeout, :lock_optimistically, :record_timestamps].each do |method|
+ assert Task.respond_to?(method)
+ assert Task.respond_to?("#{method}=")
+ assert Task.new.respond_to?(method)
+ assert !Task.new.respond_to?("#{method}=")
+ end
+ end
+
+ def test_customized_primary_key_remains_protected
+ subscriber = Subscriber.new(:nick => 'webster123', :name => 'nice try')
+ assert_nil subscriber.id
+
+ keyboard = Keyboard.new(:key_number => 9, :name => 'nice try')
+ assert_nil keyboard.id
+ end
+
+ def test_customized_primary_key_remains_protected_when_referred_to_as_id
+ subscriber = Subscriber.new(:id => 'webster123', :name => 'nice try')
+ assert_nil subscriber.id
+
+ keyboard = Keyboard.new(:id => 9, :name => 'nice try')
+ assert_nil keyboard.id
+ end
+
+ def test_mass_assignment_protection_on_defaults
+ firm = Firm.new
+ firm.attributes = { "id" => 5, "type" => "Client" }
+ assert_nil firm.id
+ assert_equal "Firm", firm[:type]
+ end
+
+ def test_mass_assignment_accessible
+ reply = Reply.new("title" => "hello", "content" => "world", "approved" => true)
+ reply.save
+
+ assert reply.approved?
+
+ reply.approved = false
+ reply.save
+
+ assert !reply.approved?
+ end
+
+ def test_mass_assignment_protection_inheritance
+ assert_nil LoosePerson.accessible_attributes
+ assert_equal Set.new([ 'credit_rating', 'administrator' ]), LoosePerson.protected_attributes
+
+ assert_nil LooseDescendant.accessible_attributes
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number' ]), LooseDescendant.protected_attributes
+
+ assert_nil LooseDescendantSecond.accessible_attributes
+ assert_equal Set.new([ 'credit_rating', 'administrator', 'phone_number', 'name' ]), LooseDescendantSecond.protected_attributes, 'Running attr_protected twice in one class should merge the protections'
+
+ assert_nil TightPerson.protected_attributes
+ assert_equal Set.new([ 'name', 'address' ]), TightPerson.accessible_attributes
+
+ assert_nil TightDescendant.protected_attributes
+ assert_equal Set.new([ 'name', 'address', 'phone_number' ]), TightDescendant.accessible_attributes
+ end
+
+ def test_readonly_attributes
+ assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes
+
+ post = ReadonlyTitlePost.create(:title => "cannot change this", :body => "changeable")
+ post.reload
+ assert_equal "cannot change this", post.title
+
+ post.update_attributes(:title => "try to change", :body => "changed")
+ post.reload
+ assert_equal "cannot change this", post.title
+ assert_equal "changed", post.body
+ end
+
+ def test_multiparameter_attributes_on_date
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "24" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 6, 24), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_empty_date
+ attributes = { "last_read(1i)" => "2004", "last_read(2i)" => "6", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # note that extra #to_date call allows test to pass for Oracle, which
+ # treats dates/times the same
+ assert_date_from_db Date.new(2004, 6, 1), topic.last_read.to_date
+ end
+
+ def test_multiparameter_attributes_on_date_with_all_empty
+ attributes = { "last_read(1i)" => "", "last_read(2i)" => "", "last_read(3i)" => "" }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_nil topic.last_read
+ end
+
+ def test_multiparameter_attributes_on_time
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
+
+ def test_multiparameter_attributes_on_time_with_old_date
+ attributes = {
+ "written_on(1i)" => "1850", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ # testing against to_s(:db) representation because either a Time or a DateTime might be returned, depending on platform
+ assert_equal "1850-06-24 16:24:00", topic.written_on.to_s(:db)
+ end
+
+ def test_multiparameter_attributes_on_time_with_utc
+ ActiveRecord::Base.default_timezone = :utc
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ ensure
+ ActiveRecord::Base.default_timezone = :local
+ end
+
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = TimeZone[-28800]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time
+ assert_equal Time.zone, topic.written_on.time_zone
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
+ end
+
+ def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ Time.zone = TimeZone[-28800]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ ensure
+ Time.zone = nil
+ end
+
+ def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes
+ ActiveRecord::Base.time_zone_aware_attributes = true
+ ActiveRecord::Base.default_timezone = :utc
+ Time.zone = TimeZone[-28800]
+ Topic.skip_time_zone_conversion_for_attributes = [:written_on]
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "00"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on
+ assert_equal false, topic.written_on.respond_to?(:time_zone)
+ ensure
+ ActiveRecord::Base.time_zone_aware_attributes = false
+ ActiveRecord::Base.default_timezone = :local
+ Time.zone = nil
+ Topic.skip_time_zone_conversion_for_attributes = []
+ end
+
+ def test_multiparameter_attributes_on_time_with_empty_seconds
+ attributes = {
+ "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24",
+ "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => ""
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on
+ end
+
+ def test_multiparameter_mass_assignment_protector
+ task = Task.new
+ time = Time.mktime(2000, 1, 1, 1)
+ task.starting = time
+ attributes = { "starting(1i)" => "2004", "starting(2i)" => "6", "starting(3i)" => "24" }
+ task.attributes = attributes
+ assert_equal time, task.starting
+ end
+
+ def test_multiparameter_assignment_of_aggregation
+ customer = Customer.new
+ address = Address.new("The Street", "The City", "The Country")
+ attributes = { "address(1)" => address.street, "address(2)" => address.city, "address(3)" => address.country }
+ customer.attributes = attributes
+ assert_equal address, customer.address
+ end
+
+ def test_attributes_on_dummy_time
+ # Oracle, SQL Server, and Sybase do not have a TIME datatype.
+ return true if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
+
+ attributes = {
+ "bonus_time" => "5:42:00AM"
+ }
+ topic = Topic.find(1)
+ topic.attributes = attributes
+ assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time
+ end
+
+ def test_boolean
+ b_false = Booleantest.create({ "value" => false })
+ false_id = b_false.id
+ b_true = Booleantest.create({ "value" => true })
+ true_id = b_true.id
+
+ b_false = Booleantest.find(false_id)
+ assert !b_false.value?
+ b_true = Booleantest.find(true_id)
+ assert b_true.value?
+ end
+
+ def test_boolean_cast_from_string
+ b_false = Booleantest.create({ "value" => "0" })
+ false_id = b_false.id
+ b_true = Booleantest.create({ "value" => "1" })
+ true_id = b_true.id
+
+ b_false = Booleantest.find(false_id)
+ assert !b_false.value?
+ b_true = Booleantest.find(true_id)
+ assert b_true.value?
+ end
+
+ def test_clone
+ topic = Topic.find(1)
+ cloned_topic = nil
+ assert_nothing_raised { cloned_topic = topic.clone }
+ assert_equal topic.title, cloned_topic.title
+ assert cloned_topic.new_record?
+
+ # test if the attributes have been cloned
+ topic.title = "a"
+ cloned_topic.title = "b"
+ assert_equal "a", topic.title
+ assert_equal "b", cloned_topic.title
+
+ # test if the attribute values have been cloned
+ topic.title = {"a" => "b"}
+ cloned_topic = topic.clone
+ cloned_topic.title["a"] = "c"
+ assert_equal "b", topic.title["a"]
+
+ #test if attributes set as part of after_initialize are cloned correctly
+ assert_equal topic.author_email_address, cloned_topic.author_email_address
+
+ # test if saved clone object differs from original
+ cloned_topic.save
+ assert !cloned_topic.new_record?
+ assert cloned_topic.id != topic.id
+ end
+
+ def test_clone_with_aggregate_of_same_name_as_attribute
+ dev = DeveloperWithAggregate.find(1)
+ assert_kind_of DeveloperSalary, dev.salary
+
+ clone = nil
+ assert_nothing_raised { clone = dev.clone }
+ assert_kind_of DeveloperSalary, clone.salary
+ assert_equal dev.salary.amount, clone.salary.amount
+ assert clone.new_record?
+
+ # test if the attributes have been cloned
+ original_amount = clone.salary.amount
+ dev.salary.amount = 1
+ assert_equal original_amount, clone.salary.amount
+
+ assert clone.save
+ assert !clone.new_record?
+ assert clone.id != dev.id
+ end
+
+ def test_clone_preserves_subtype
+ clone = nil
+ assert_nothing_raised { clone = Company.find(3).clone }
+ assert_kind_of Client, clone
+ end
+
+ def test_bignum
+ company = Company.find(1)
+ company.rating = 2147483647
+ company.save
+ company = Company.find(1)
+ assert_equal 2147483647, company.rating
+ end
+
+ # TODO: extend defaults tests to other databases!
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_default
+ default = Default.new
+
+ # fixed dates / times
+ assert_equal Date.new(2004, 1, 1), default.fixed_date
+ assert_equal Time.local(2004, 1,1,0,0,0,0), default.fixed_time
+
+ # char types
+ assert_equal 'Y', default.char1
+ assert_equal 'a varchar field', default.char2
+ assert_equal 'a text field', default.char3
+ end
+
+ class Geometric < ActiveRecord::Base; end
+ def test_geometric_content
+
+ # accepted format notes:
+ # ()'s aren't required
+ # values can be a mix of float or integer
+
+ g = Geometric.new(
+ :a_point => '(5.0, 6.1)',
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
+ :a_line_segment => '(2.0, 3), (5.5, 7.0)',
+ :a_box => '2.0, 3, 5.5, 7.0',
+ :a_path => '[(2.0, 3), (5.5, 7.0), (8.5, 11.0)]', # [ ] is an open path
+ :a_polygon => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))',
+ :a_circle => '<(5.3, 10.4), 2>'
+ )
+
+ assert g.save
+
+ # Reload and check that we have all the geometric attributes.
+ h = Geometric.find(g.id)
+
+ assert_equal '(5,6.1)', h.a_point
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '[(2,3),(5.5,7),(8.5,11)]', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+
+ # use a geometric function to test for an open path
+ objs = Geometric.find_by_sql ["select isopen(a_path) from geometrics where id = ?", g.id]
+ assert_equal objs[0].isopen, 't'
+
+ # test alternate formats when defining the geometric types
+
+ g = Geometric.new(
+ :a_point => '5.0, 6.1',
+ #:a_line => '((2.0, 3), (5.5, 7.0))' # line type is currently unsupported in postgresql
+ :a_line_segment => '((2.0, 3), (5.5, 7.0))',
+ :a_box => '(2.0, 3), (5.5, 7.0)',
+ :a_path => '((2.0, 3), (5.5, 7.0), (8.5, 11.0))', # ( ) is a closed path
+ :a_polygon => '2.0, 3, 5.5, 7.0, 8.5, 11.0',
+ :a_circle => '((5.3, 10.4), 2)'
+ )
+
+ assert g.save
+
+ # Reload and check that we have all the geometric attributes.
+ h = Geometric.find(g.id)
+
+ assert_equal '(5,6.1)', h.a_point
+ assert_equal '[(2,3),(5.5,7)]', h.a_line_segment
+ assert_equal '(5.5,7),(2,3)', h.a_box # reordered to store upper right corner then bottom left corner
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_path
+ assert_equal '((2,3),(5.5,7),(8.5,11))', h.a_polygon
+ assert_equal '<(5.3,10.4),2>', h.a_circle
+
+ # use a geometric function to test for an closed path
+ objs = Geometric.find_by_sql ["select isclosed(a_path) from geometrics where id = ?", g.id]
+ assert_equal objs[0].isclosed, 't'
+ end
+ end
+
+ class NumericData < ActiveRecord::Base
+ self.table_name = 'numeric_data'
+ end
+
+ def test_numeric_fields
+ m = NumericData.new(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3
+ )
+ assert m.save
+
+ m1 = NumericData.find(m.id)
+ assert_not_nil m1
+
+ # As with migration_test.rb, we should make world_population >= 2**62
+ # to cover 64-bit platforms and test it is a Bignum, but the main thing
+ # is that it's an Integer.
+ assert_kind_of Integer, m1.world_population
+ assert_equal 6000000000, m1.world_population
+
+ assert_kind_of Fixnum, m1.my_house_population
+ assert_equal 3, m1.my_house_population
+
+ assert_kind_of BigDecimal, m1.bank_balance
+ assert_equal BigDecimal("1586.43"), m1.bank_balance
+
+ assert_kind_of BigDecimal, m1.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), m1.big_bank_balance
+ end
+
+ def test_auto_id
+ auto = AutoId.new
+ auto.save
+ assert (auto.id > 0)
+ end
+
+ def quote_column_name(name)
+ "<#{name}>"
+ end
+
+ def test_quote_keys
+ ar = AutoId.new
+ source = {"foo" => "bar", "baz" => "quux"}
+ actual = ar.send(:quote_columns, self, source)
+ inverted = actual.invert
+ assert_equal("", inverted["bar"])
+ assert_equal("", inverted["quux"])
+ end
+
+ def test_sql_injection_via_find
+ assert_raises(ActiveRecord::RecordNotFound, ActiveRecord::StatementInvalid) do
+ Topic.find("123456 OR id > 0")
+ end
+ end
+
+ def test_column_name_properly_quoted
+ col_record = ColumnName.new
+ col_record.references = 40
+ assert col_record.save
+ col_record.references = 41
+ assert col_record.save
+ assert_not_nil c2 = ColumnName.find(col_record.id)
+ assert_equal(41, c2.references)
+ end
+
+ def test_quoting_arrays
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", topics(:first).replies.collect(&:id) ])
+ assert_equal topics(:first).replies.size, replies.size
+
+ replies = Reply.find(:all, :conditions => [ "id IN (?)", [] ])
+ assert_equal 0, replies.size
+ end
+
+ MyObject = Struct.new :attribute1, :attribute2
+
+ def test_serialized_attribute
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.create("content" => myobj)
+ Topic.serialize("content", MyObject)
+ assert_equal(myobj, topic.content)
+ end
+
+ def test_nil_serialized_attribute_with_class_constraint
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.new
+ assert_nil topic.content
+ end
+
+ def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
+ myobj = MyObject.new('value1', 'value2')
+ topic = Topic.new(:content => myobj)
+ assert topic.save
+ Topic.serialize(:content, Hash)
+ assert_raise(ActiveRecord::SerializationTypeMismatch) { Topic.find(topic.id).content }
+ ensure
+ Topic.serialize(:content)
+ end
+
+ def test_serialized_attribute_with_class_constraint
+ settings = { "color" => "blue" }
+ Topic.serialize(:content, Hash)
+ topic = Topic.new(:content => settings)
+ assert topic.save
+ assert_equal(settings, Topic.find(topic.id).content)
+ ensure
+ Topic.serialize(:content)
+ end
+
+ def test_quote
+ author_name = "\\ \001 ' \n \\n \""
+ topic = Topic.create('author_name' => author_name)
+ assert_equal author_name, Topic.find(topic.id).author_name
+ end
+
+ if RUBY_VERSION < '1.9'
+ def test_quote_chars
+ str = 'The Narrator'
+ topic = Topic.create(:author_name => str)
+ assert_equal str, topic.author_name
+
+ assert_kind_of ActiveSupport::Multibyte::Chars, str.chars
+ topic = Topic.find_by_author_name(str.chars)
+
+ assert_kind_of Topic, topic
+ assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars"
+ end
+ end
+
+ def test_class_level_destroy
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.destroy(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_raise(ActiveRecord::RecordNotFound) { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_class_level_delete
+ should_be_destroyed_reply = Reply.create("title" => "hello", "content" => "world")
+ Topic.find(1).replies << should_be_destroyed_reply
+
+ Topic.delete(1)
+ assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1) }
+ assert_nothing_raised { Reply.find(should_be_destroyed_reply.id) }
+ end
+
+ def test_increment_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit
+ assert_equal 51, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit).increment!(:credit_limit)
+ assert_equal 53, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_increment_nil_attribute
+ assert_nil topics(:first).parent_id
+ topics(:first).increment! :parent_id
+ assert_equal 1, topics(:first).parent_id
+ end
+
+ def test_increment_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).increment! :credit_limit, 5
+ assert_equal 55, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).increment(:credit_limit, 1).increment!(:credit_limit, 3)
+ assert_equal 59, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_decrement_attribute
+ assert_equal 50, accounts(:signals37).credit_limit
+
+ accounts(:signals37).decrement!(:credit_limit)
+ assert_equal 49, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit).decrement!(:credit_limit)
+ assert_equal 47, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_decrement_attribute_by
+ assert_equal 50, accounts(:signals37).credit_limit
+ accounts(:signals37).decrement! :credit_limit, 5
+ assert_equal 45, accounts(:signals37, :reload).credit_limit
+
+ accounts(:signals37).decrement(:credit_limit, 1).decrement!(:credit_limit, 3)
+ assert_equal 41, accounts(:signals37, :reload).credit_limit
+ end
+
+ def test_toggle_attribute
+ assert !topics(:first).approved?
+ topics(:first).toggle!(:approved)
+ assert topics(:first).approved?
+ topic = topics(:first)
+ topic.toggle(:approved)
+ assert !topic.approved?
+ topic.reload
+ assert topic.approved?
+ end
+
+ def test_reload
+ t1 = Topic.find(1)
+ t2 = Topic.find(1)
+ t1.title = "something else"
+ t1.save
+ t2.reload
+ assert_equal t1.title, t2.title
+ end
+
+ def test_define_attr_method_with_value
+ k = Class.new( ActiveRecord::Base )
+ k.send(:define_attr_method, :table_name, "foo")
+ assert_equal "foo", k.table_name
+ end
+
+ def test_define_attr_method_with_block
+ k = Class.new( ActiveRecord::Base )
+ k.send(:define_attr_method, :primary_key) { "sys_" + original_primary_key }
+ assert_equal "sys_id", k.primary_key
+ end
+
+ def test_set_table_name_with_value
+ k = Class.new( ActiveRecord::Base )
+ k.table_name = "foo"
+ assert_equal "foo", k.table_name
+ k.set_table_name "bar"
+ assert_equal "bar", k.table_name
+ end
+
+ def test_set_table_name_with_block
+ k = Class.new( ActiveRecord::Base )
+ k.set_table_name { "ks" }
+ assert_equal "ks", k.table_name
+ end
+
+ def test_set_primary_key_with_value
+ k = Class.new( ActiveRecord::Base )
+ k.primary_key = "foo"
+ assert_equal "foo", k.primary_key
+ k.set_primary_key "bar"
+ assert_equal "bar", k.primary_key
+ end
+
+ def test_set_primary_key_with_block
+ k = Class.new( ActiveRecord::Base )
+ k.set_primary_key { "sys_" + original_primary_key }
+ assert_equal "sys_id", k.primary_key
+ end
+
+ def test_set_inheritance_column_with_value
+ k = Class.new( ActiveRecord::Base )
+ k.inheritance_column = "foo"
+ assert_equal "foo", k.inheritance_column
+ k.set_inheritance_column "bar"
+ assert_equal "bar", k.inheritance_column
+ end
+
+ def test_set_inheritance_column_with_block
+ k = Class.new( ActiveRecord::Base )
+ k.set_inheritance_column { original_inheritance_column + "_id" }
+ assert_equal "type_id", k.inheritance_column
+ end
+
+ def test_count_with_join
+ res = Post.count_by_sql "SELECT COUNT(*) FROM posts LEFT JOIN comments ON posts.id=comments.post_id WHERE posts.#{QUOTED_TYPE} = 'Post'"
+
+ res2 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'", :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ assert_equal res, res2
+
+ res3 = nil
+ assert_nothing_raised do
+ res3 = Post.count(:conditions => "posts.#{QUOTED_TYPE} = 'Post'",
+ :joins => "LEFT JOIN comments ON posts.id=comments.post_id")
+ end
+ assert_equal res, res3
+
+ res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
+ res5 = nil
+ assert_nothing_raised do
+ res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
+ :joins => "p, comments co",
+ :select => "p.id")
+ end
+
+ assert_equal res4, res5
+
+ unless current_adapter?(:SQLite2Adapter, :DeprecatedSQLiteAdapter)
+ res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id"
+ res7 = nil
+ assert_nothing_raised do
+ res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id",
+ :joins => "p, comments co",
+ :select => "p.id",
+ :distinct => true)
+ end
+ assert_equal res6, res7
+ end
+ end
+
+ def test_clear_association_cache_stored
+ firm = Firm.find(1)
+ assert_kind_of Firm, firm
+
+ firm.clear_association_cache
+ assert_equal Firm.find(1).clients.collect{ |x| x.name }.sort, firm.clients.collect{ |x| x.name }.sort
+ end
+
+ def test_clear_association_cache_new_record
+ firm = Firm.new
+ client_stored = Client.find(3)
+ client_new = Client.new
+ client_new.name = "The Joneses"
+ clients = [ client_stored, client_new ]
+
+ firm.clients << clients
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+
+ firm.clear_association_cache
+ assert_equal clients.map(&:name).to_set, firm.clients.map(&:name).to_set
+ end
+
+ def test_interpolate_sql
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo@bar') }
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar) baz') }
+ assert_nothing_raised { Category.new.send(:interpolate_sql, 'foo bar} baz') }
+ end
+
+ def test_scoped_find_conditions
+ scoped_developers = Developer.with_scope(:find => { :conditions => 'salary > 90000' }) do
+ Developer.find(:all, :conditions => 'id < 5')
+ end
+ assert !scoped_developers.include?(developers(:david)) # David's salary is less than 90,000
+ assert_equal 3, scoped_developers.size
+ end
+
+ def test_scoped_find_limit_offset
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :offset => 2 }) do
+ Developer.find(:all, :order => 'id')
+ end
+ assert !scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 3, scoped_developers.size
+
+ # Test without scoped find conditions to ensure we get the whole thing
+ developers = Developer.find(:all, :order => 'id')
+ assert_equal Developer.count, developers.size
+ end
+
+ def test_scoped_find_order
+ # Test order in scope
+ scoped_developers = Developer.with_scope(:find => { :limit => 1, :order => 'salary DESC' }) do
+ Developer.find(:all)
+ end
+ assert_equal 'Jamis', scoped_developers.first.name
+ assert scoped_developers.include?(developers(:jamis))
+ # Test scope without order and order in find
+ scoped_developers = Developer.with_scope(:find => { :limit => 1 }) do
+ Developer.find(:all, :order => 'salary DESC')
+ end
+ # Test scope order + find order, find has priority
+ scoped_developers = Developer.with_scope(:find => { :limit => 3, :order => 'id DESC' }) do
+ Developer.find(:all, :order => 'salary ASC')
+ end
+ assert scoped_developers.include?(developers(:poor_jamis))
+ assert scoped_developers.include?(developers(:david))
+ assert scoped_developers.include?(developers(:dev_10))
+ # Test without scoped find conditions to ensure we get the right thing
+ developers = Developer.find(:all, :order => 'id', :limit => 1)
+ assert scoped_developers.include?(developers(:david))
+ end
+
+ def test_scoped_find_limit_offset_including_has_many_association
+ topics = Topic.with_scope(:find => {:limit => 1, :offset => 1, :include => :replies}) do
+ Topic.find(:all, :order => "topics.id")
+ end
+ assert_equal 1, topics.size
+ assert_equal 2, topics.first.id
+ end
+
+ def test_scoped_find_order_including_has_many_association
+ developers = Developer.with_scope(:find => { :order => 'developers.salary DESC', :include => :projects }) do
+ Developer.find(:all)
+ end
+ assert developers.size >= 2
+ for i in 1...developers.size
+ assert developers[i-1].salary >= developers[i].salary
+ end
+ end
+
+ def test_find_last
+ last = Developer.find :last
+ assert_equal last, Developer.find(:first, :order => 'id desc')
+ end
+
+ def test_last
+ assert_equal Developer.find(:first, :order => 'id desc'), Developer.last
+ end
+
+ def test_all_with_conditions
+ assert_equal Developer.find(:all, :order => 'id desc'), Developer.all(:order => 'id desc')
+ end
+
+ def test_find_ordered_last
+ last = Developer.find :last, :order => 'developers.salary ASC'
+ assert_equal last, Developer.find(:all, :order => 'developers.salary ASC').last
+ end
+
+ def test_find_reverse_ordered_last
+ last = Developer.find :last, :order => 'developers.salary DESC'
+ assert_equal last, Developer.find(:all, :order => 'developers.salary DESC').last
+ end
+
+ def test_find_multiple_ordered_last
+ last = Developer.find :last, :order => 'developers.name, developers.salary DESC'
+ assert_equal last, Developer.find(:all, :order => 'developers.name, developers.salary DESC').last
+ end
+
+ def test_find_scoped_ordered_last
+ last_developer = Developer.with_scope(:find => { :order => 'developers.salary ASC' }) do
+ Developer.find(:last)
+ end
+ assert_equal last_developer, Developer.find(:all, :order => 'developers.salary ASC').last
+ end
+
+ def test_abstract_class
+ assert !ActiveRecord::Base.abstract_class?
+ assert LoosePerson.abstract_class?
+ assert !LooseDescendant.abstract_class?
+ end
+
+ def test_base_class
+ assert_equal LoosePerson, LoosePerson.base_class
+ assert_equal LooseDescendant, LooseDescendant.base_class
+ assert_equal TightPerson, TightPerson.base_class
+ assert_equal TightPerson, TightDescendant.base_class
+
+ assert_equal Post, Post.base_class
+ assert_equal Post, SpecialPost.base_class
+ assert_equal Post, StiPost.base_class
+ assert_equal SubStiPost, SubStiPost.base_class
+ end
+
+ def test_descends_from_active_record
+ # Tries to call Object.abstract_class?
+ assert_raise(NoMethodError) do
+ ActiveRecord::Base.descends_from_active_record?
+ end
+
+ # Abstract subclass of AR::Base.
+ assert LoosePerson.descends_from_active_record?
+
+ # Concrete subclass of an abstract class.
+ assert LooseDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert TightPerson.descends_from_active_record?
+
+ # Concrete subclass of a concrete class but has no type column.
+ assert TightDescendant.descends_from_active_record?
+
+ # Concrete subclass of AR::Base.
+ assert Post.descends_from_active_record?
+
+ # Abstract subclass of a concrete class which has a type column.
+ # This is pathological, as you'll never have Sub < Abstract < Concrete.
+ assert !StiPost.descends_from_active_record?
+
+ # Concrete subclasses an abstract class which has a type column.
+ assert !SubStiPost.descends_from_active_record?
+ end
+
+ def test_find_on_abstract_base_class_doesnt_use_type_condition
+ old_class = LooseDescendant
+ Object.send :remove_const, :LooseDescendant
+
+ descendant = old_class.create! :first_name => 'bob'
+ assert_not_nil LoosePerson.find(descendant.id), "Should have found instance of LooseDescendant when finding abstract LoosePerson: #{descendant.inspect}"
+ ensure
+ unless Object.const_defined?(:LooseDescendant)
+ Object.const_set :LooseDescendant, old_class
+ end
+ end
+
+ def test_assert_queries
+ query = lambda { ActiveRecord::Base.connection.execute 'select count(*) from developers' }
+ assert_queries(2) { 2.times { query.call } }
+ assert_queries 1, &query
+ assert_no_queries { assert true }
+ end
+
+ def test_to_xml
+ xml = REXML::Document.new(topics(:first).to_xml(:indent => 0))
+ bonus_time_in_current_timezone = topics(:first).bonus_time.xmlschema
+ written_on_in_current_timezone = topics(:first).written_on.xmlschema
+ last_read_in_current_timezone = topics(:first).last_read.xmlschema
+
+ assert_equal "topic", xml.root.name
+ assert_equal "The First Topic" , xml.elements["//title"].text
+ assert_equal "David" , xml.elements["//author-name"].text
+
+ assert_equal "1", xml.elements["//id"].text
+ assert_equal "integer" , xml.elements["//id"].attributes['type']
+
+ assert_equal "1", xml.elements["//replies-count"].text
+ assert_equal "integer" , xml.elements["//replies-count"].attributes['type']
+
+ assert_equal written_on_in_current_timezone, xml.elements["//written-on"].text
+ assert_equal "datetime" , xml.elements["//written-on"].attributes['type']
+
+ assert_equal "--- Have a nice day\n" , xml.elements["//content"].text
+ assert_equal "yaml" , xml.elements["//content"].attributes['type']
+
+ assert_equal "david@loudthinking.com", xml.elements["//author-email-address"].text
+
+ assert_equal nil, xml.elements["//parent-id"].text
+ assert_equal "integer", xml.elements["//parent-id"].attributes['type']
+ assert_equal "true", xml.elements["//parent-id"].attributes['nil']
+
+ if current_adapter?(:SybaseAdapter, :SQLServerAdapter, :OracleAdapter)
+ assert_equal last_read_in_current_timezone, xml.elements["//last-read"].text
+ assert_equal "datetime" , xml.elements["//last-read"].attributes['type']
+ else
+ assert_equal "2004-04-15", xml.elements["//last-read"].text
+ assert_equal "date" , xml.elements["//last-read"].attributes['type']
+ end
+
+ # Oracle and DB2 don't have true boolean or time-only fields
+ unless current_adapter?(:OracleAdapter, :DB2Adapter)
+ assert_equal "false", xml.elements["//approved"].text
+ assert_equal "boolean" , xml.elements["//approved"].attributes['type']
+
+ assert_equal bonus_time_in_current_timezone, xml.elements["//bonus-time"].text
+ assert_equal "datetime" , xml.elements["//bonus-time"].attributes['type']
+ end
+ end
+
+ def test_to_xml_skipping_attributes
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :replies_count])
+ assert_equal "", xml.first(7)
+ assert !xml.include?(%(The First Topic))
+ assert xml.include?(%(David))
+
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :except => [:title, :author_name, :replies_count])
+ assert !xml.include?(%(The First Topic))
+ assert !xml.include?(%(David))
+ end
+
+ def test_to_xml_including_has_many_association
+ xml = topics(:first).to_xml(:indent => 0, :skip_instruct => true, :include => :replies, :except => :replies_count)
+ assert_equal "", xml.first(7)
+ assert xml.include?(%())
+ assert xml.include?(%(The Second Topic of the day))
+ end
+
+ def test_array_to_xml_including_has_many_association
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :include => :replies)
+ assert xml.include?(%())
+ end
+
+ def test_array_to_xml_including_methods
+ xml = [ topics(:first), topics(:second) ].to_xml(:indent => 0, :skip_instruct => true, :methods => [ :topic_id ])
+ assert xml.include?(%(#{topics(:first).topic_id})), xml
+ assert xml.include?(%(#{topics(:second).topic_id})), xml
+ end
+
+ def test_array_to_xml_including_has_one_association
+ xml = [ companies(:first_firm), companies(:rails_core) ].to_xml(:indent => 0, :skip_instruct => true, :include => :account)
+ assert xml.include?(companies(:first_firm).account.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:rails_core).account.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_array_to_xml_including_belongs_to_association
+ xml = [ companies(:first_client), companies(:second_client), companies(:another_client) ].to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?(companies(:first_client).to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:second_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ assert xml.include?(companies(:another_client).firm.to_xml(:indent => 0, :skip_instruct => true))
+ end
+
+ def test_to_xml_including_belongs_to_association
+ xml = companies(:first_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert !xml.include?("")
+
+ xml = companies(:second_client).to_xml(:indent => 0, :skip_instruct => true, :include => :firm)
+ assert xml.include?("")
+ end
+
+ def test_to_xml_including_multiple_associations
+ xml = companies(:first_firm).to_xml(:indent => 0, :skip_instruct => true, :include => [ :clients, :account ])
+ assert_equal "", xml.first(6)
+ assert xml.include?(%())
+ assert xml.include?(%())
+ end
+
+ def test_to_xml_including_multiple_associations_with_options
+ xml = companies(:first_firm).to_xml(
+ :indent => 0, :skip_instruct => true,
+ :include => { :clients => { :only => :name } }
+ )
+
+ assert_equal "", xml.first(6)
+ assert xml.include?(%(Summit))
+ assert xml.include?(%())
+ end
+
+ def test_to_xml_including_methods
+ xml = Company.new.to_xml(:methods => :arbitrary_method, :skip_instruct => true)
+ assert_equal "", xml.first(9)
+ assert xml.include?(%(I am Jack's profound disappointment))
+ end
+
+ def test_to_xml_with_block
+ value = "Rockin' the block"
+ xml = Company.new.to_xml(:skip_instruct => true) do |xml|
+ xml.tag! "arbitrary-element", value
+ end
+ assert_equal "", xml.first(9)
+ assert xml.include?(%(#{value}))
+ end
+
+ def test_type_name_with_module_should_handle_beginning
+ assert_equal 'ActiveRecord::Person', ActiveRecord::Base.send(:type_name_with_module, 'Person')
+ assert_equal '::Person', ActiveRecord::Base.send(:type_name_with_module, '::Person')
+ end
+
+ def test_to_param_should_return_string
+ assert_kind_of String, Client.find(:first).to_param
+ end
+
+ def test_inspect_class
+ assert_equal 'ActiveRecord::Base', ActiveRecord::Base.inspect
+ assert_equal 'LoosePerson(abstract)', LoosePerson.inspect
+ assert_match(/^Topic\(id: integer, title: string/, Topic.inspect)
+ end
+
+ def test_inspect_instance
+ topic = topics(:first)
+ assert_equal %(#), topic.inspect
+ end
+
+ def test_inspect_new_instance
+ assert_match /Topic id: nil/, Topic.new.inspect
+ end
+
+ def test_inspect_limited_select_instance
+ assert_equal %(#), Topic.find(:first, :select => 'id', :conditions => 'id = 1').inspect
+ assert_equal %(#), Topic.find(:first, :select => 'id, title', :conditions => 'id = 1').inspect
+ end
+
+ def test_inspect_class_without_table
+ assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect
+ end
+
+ def test_attribute_for_inspect
+ t = topics(:first)
+ t.title = "The First Topic Now Has A Title With\nNewlines And More Than 50 Characters"
+
+ assert_equal %("#{t.written_on.to_s(:db)}"), t.attribute_for_inspect(:written_on)
+ assert_equal '"The First Topic Now Has A Title With\nNewlines And M..."', t.attribute_for_inspect(:title)
+ end
+
+ def test_becomes
+ assert_kind_of Reply, topics(:first).becomes(Reply)
+ assert_equal "The First Topic", topics(:first).becomes(Reply).title
+ end
+
+ def test_silence_sets_log_level_to_error_in_block
+ original_logger = ActiveRecord::Base.logger
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::DEBUG
+ ActiveRecord::Base.silence do
+ ActiveRecord::Base.logger.warn "warn"
+ ActiveRecord::Base.logger.error "error"
+ end
+ assert_equal "error\n", log.string
+ ensure
+ ActiveRecord::Base.logger = original_logger
+ end
+
+ def test_silence_sets_log_level_back_to_level_before_yield
+ original_logger = ActiveRecord::Base.logger
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.silence do
+ end
+ assert_equal Logger::WARN, ActiveRecord::Base.logger.level
+ ensure
+ ActiveRecord::Base.logger = original_logger
+ end
+
+ def test_benchmark_with_log_level
+ original_logger = ActiveRecord::Base.logger
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.logger.level = Logger::WARN
+ ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count }
+ ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count }
+ ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count }
+ assert_no_match /Debug Topic Count/, log.string
+ assert_match /Warn Topic Count/, log.string
+ assert_match /Error Topic Count/, log.string
+ ensure
+ ActiveRecord::Base.logger = original_logger
+ end
+
+ def test_benchmark_with_use_silence
+ original_logger = ActiveRecord::Base.logger
+ log = StringIO.new
+ ActiveRecord::Base.logger = Logger.new(log)
+ ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" }
+ ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" }
+ assert_no_match /Loud/, log.string
+ assert_match /Quiet/, log.string
+ ensure
+ ActiveRecord::Base.logger = original_logger
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/binary_test.rb b/vendor/rails/activerecord/test/cases/binary_test.rb
new file mode 100644
index 0000000..7131532
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/binary_test.rb
@@ -0,0 +1,34 @@
+require "cases/helper"
+
+# Without using prepared statements, it makes no sense to test
+# BLOB data with SQL Server, because the length of a statement is
+# limited to 8KB.
+#
+# Without using prepared statements, it makes no sense to test
+# BLOB data with DB2 or Firebird, because the length of a statement
+# is limited to 32KB.
+unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :DB2Adapter, :FirebirdAdapter)
+ require 'models/binary'
+
+ class BinaryTest < ActiveRecord::TestCase
+ FIXTURES = %w(flowers.jpg example.log)
+
+ def test_load_save
+ Binary.delete_all
+
+ FIXTURES.each do |filename|
+ data = File.read(ASSETS_ROOT + "/#{filename}")
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
+ data.freeze
+
+ bin = Binary.new(:data => data)
+ assert_equal data, bin.data, 'Newly assigned data differs from original'
+
+ bin.save!
+ assert_equal data, bin.data, 'Data differs from original after save'
+
+ assert_equal data, bin.reload.data, 'Reloaded data differs from original'
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/calculations_test.rb b/vendor/rails/activerecord/test/cases/calculations_test.rb
new file mode 100644
index 0000000..a87fc26
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/calculations_test.rb
@@ -0,0 +1,271 @@
+require "cases/helper"
+require 'models/company'
+require 'models/topic'
+
+Company.has_many :accounts
+
+class NumericData < ActiveRecord::Base
+ self.table_name = 'numeric_data'
+end
+
+class CalculationsTest < ActiveRecord::TestCase
+ fixtures :companies, :accounts, :topics
+
+ def test_should_sum_field
+ assert_equal 318, Account.sum(:credit_limit)
+ end
+
+ def test_should_average_field
+ value = Account.average(:credit_limit)
+ assert_kind_of Float, value
+ assert_in_delta 53.0, value, 0.001
+ end
+
+ def test_should_return_nil_as_average
+ assert_nil NumericData.average(:bank_balance)
+ end
+
+ def test_should_get_maximum_of_field
+ assert_equal 60, Account.maximum(:credit_limit)
+ end
+
+ def test_should_get_maximum_of_field_with_include
+ assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'")
+ end
+
+ def test_should_get_maximum_of_field_with_scoped_include
+ Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do
+ assert_equal 50, Account.maximum(:credit_limit)
+ end
+ end
+
+ def test_should_get_minimum_of_field
+ assert_equal 50, Account.minimum(:credit_limit)
+ end
+
+ def test_should_group_by_field
+ c = Account.sum(:credit_limit, :group => :firm_id)
+ [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
+ end
+
+ def test_should_group_by_summed_field
+ c = Account.sum(:credit_limit, :group => :firm_id)
+ assert_equal 50, c[1]
+ assert_equal 105, c[6]
+ assert_equal 60, c[2]
+ end
+
+ def test_should_order_by_grouped_field
+ c = Account.sum(:credit_limit, :group => :firm_id, :order => "firm_id")
+ assert_equal [1, 2, 6, 9], c.keys.compact
+ end
+
+ def test_should_order_by_calculation
+ c = Account.sum(:credit_limit, :group => :firm_id, :order => "sum_credit_limit desc, firm_id")
+ assert_equal [105, 60, 53, 50, 50], c.keys.collect { |k| c[k] }
+ assert_equal [6, 2, 9, 1], c.keys.compact
+ end
+
+ def test_should_limit_calculation
+ c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
+ :group => :firm_id, :order => "firm_id", :limit => 2)
+ assert_equal [1, 2], c.keys.compact
+ end
+
+ def test_should_limit_calculation_with_offset
+ c = Account.sum(:credit_limit, :conditions => "firm_id IS NOT NULL",
+ :group => :firm_id, :order => "firm_id", :limit => 2, :offset => 1)
+ assert_equal [2, 6], c.keys.compact
+ end
+
+ def test_should_group_by_summed_field_having_condition
+ c = Account.sum(:credit_limit, :group => :firm_id,
+ :having => 'sum(credit_limit) > 50')
+ assert_nil c[1]
+ assert_equal 105, c[6]
+ assert_equal 60, c[2]
+ end
+
+ def test_should_group_by_summed_association
+ c = Account.sum(:credit_limit, :group => :firm)
+ assert_equal 50, c[companies(:first_firm)]
+ assert_equal 105, c[companies(:rails_core)]
+ assert_equal 60, c[companies(:first_client)]
+ end
+
+ def test_should_sum_field_with_conditions
+ assert_equal 105, Account.sum(:credit_limit, :conditions => 'firm_id = 6')
+ end
+
+ def test_should_return_zero_if_sum_conditions_return_nothing
+ assert_equal 0, Account.sum(:credit_limit, :conditions => '1 = 2')
+ end
+
+ def test_should_group_by_summed_field_with_conditions
+ c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
+ :group => :firm_id)
+ assert_nil c[1]
+ assert_equal 105, c[6]
+ assert_equal 60, c[2]
+ end
+
+ def test_should_group_by_summed_field_with_conditions_and_having
+ c = Account.sum(:credit_limit, :conditions => 'firm_id > 1',
+ :group => :firm_id,
+ :having => 'sum(credit_limit) > 60')
+ assert_nil c[1]
+ assert_equal 105, c[6]
+ assert_nil c[2]
+ end
+
+ def test_should_group_by_fields_with_table_alias
+ c = Account.sum(:credit_limit, :group => 'accounts.firm_id')
+ assert_equal 50, c[1]
+ assert_equal 105, c[6]
+ assert_equal 60, c[2]
+ end
+
+ def test_should_calculate_with_invalid_field
+ assert_equal 6, Account.calculate(:count, '*')
+ assert_equal 6, Account.calculate(:count, :all)
+ end
+
+ def test_should_calculate_grouped_with_invalid_field
+ c = Account.count(:all, :group => 'accounts.firm_id')
+ assert_equal 1, c[1]
+ assert_equal 2, c[6]
+ assert_equal 1, c[2]
+ end
+
+ def test_should_calculate_grouped_association_with_invalid_field
+ c = Account.count(:all, :group => :firm)
+ assert_equal 1, c[companies(:first_firm)]
+ assert_equal 2, c[companies(:rails_core)]
+ assert_equal 1, c[companies(:first_client)]
+ end
+
+ uses_mocha 'group_by_non_numeric_foreign_key_association' do
+ def test_should_group_by_association_with_non_numeric_foreign_key
+ ActiveRecord::Base.connection.expects(:select_all).returns([{"count_all" => 1, "firm_id" => "ABC"}])
+
+ firm = mock()
+ firm.expects(:id).returns("ABC")
+ firm.expects(:class).returns(Firm)
+ Company.expects(:find).with(["ABC"]).returns([firm])
+
+ column = mock()
+ column.expects(:name).at_least_once.returns(:firm_id)
+ column.expects(:type_cast).with("ABC").returns("ABC")
+ Account.expects(:columns).at_least_once.returns([column])
+
+ c = Account.count(:all, :group => :firm)
+ assert_equal Firm, c.first.first.class
+ assert_equal 1, c.first.last
+ end
+ end
+
+ def test_should_calculate_grouped_association_with_foreign_key_option
+ Account.belongs_to :another_firm, :class_name => 'Firm', :foreign_key => 'firm_id'
+ c = Account.count(:all, :group => :another_firm)
+ assert_equal 1, c[companies(:first_firm)]
+ assert_equal 2, c[companies(:rails_core)]
+ assert_equal 1, c[companies(:first_client)]
+ end
+
+ def test_should_not_modify_options_when_using_includes
+ options = {:conditions => 'companies.id > 1', :include => :firm}
+ options_copy = options.dup
+
+ Account.count(:all, options)
+ assert_equal options_copy, options
+ end
+
+ def test_should_calculate_grouped_by_function
+ c = Company.count(:all, :group => "UPPER(#{QUOTED_TYPE})")
+ assert_equal 2, c[nil]
+ assert_equal 1, c['DEPENDENTFIRM']
+ assert_equal 3, c['CLIENT']
+ assert_equal 2, c['FIRM']
+ end
+
+ def test_should_calculate_grouped_by_function_with_table_alias
+ c = Company.count(:all, :group => "UPPER(companies.#{QUOTED_TYPE})")
+ assert_equal 2, c[nil]
+ assert_equal 1, c['DEPENDENTFIRM']
+ assert_equal 3, c['CLIENT']
+ assert_equal 2, c['FIRM']
+ end
+
+ def test_should_not_overshadow_enumerable_sum
+ assert_equal 6, [1, 2, 3].sum(&:abs)
+ end
+
+ def test_should_sum_scoped_field
+ assert_equal 15, companies(:rails_core).companies.sum(:id)
+ end
+
+ def test_should_sum_scoped_field_with_conditions
+ assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7')
+ end
+
+ def test_should_group_by_scoped_field
+ c = companies(:rails_core).companies.sum(:id, :group => :name)
+ assert_equal 7, c['Leetsoft']
+ assert_equal 8, c['Jadedpixel']
+ end
+
+ def test_should_group_by_summed_field_with_conditions_and_having
+ c = companies(:rails_core).companies.sum(:id, :group => :name,
+ :having => 'sum(id) > 7')
+ assert_nil c['Leetsoft']
+ assert_equal 8, c['Jadedpixel']
+ end
+
+ def test_should_reject_invalid_options
+ assert_nothing_raised do
+ [:count, :sum].each do |func|
+ # empty options are valid
+ Company.send(:validate_calculation_options, func)
+ # these options are valid for all calculations
+ [:select, :conditions, :joins, :order, :group, :having, :distinct].each do |opt|
+ Company.send(:validate_calculation_options, func, opt => true)
+ end
+ end
+
+ # :include is only valid on :count
+ Company.send(:validate_calculation_options, :count, :include => true)
+ end
+
+ assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) }
+ assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }
+ end
+
+ def test_should_count_selected_field_with_include
+ assert_equal 6, Account.count(:distinct => true, :include => :firm)
+ assert_equal 4, Account.count(:distinct => true, :include => :firm, :select => :credit_limit)
+ end
+
+ def test_should_count_manual_select_with_include
+ assert_equal 6, Account.count(:select => "DISTINCT accounts.id", :include => :firm)
+ end
+
+ def test_count_with_column_parameter
+ assert_equal 5, Account.count(:firm_id)
+ end
+
+ def test_count_with_column_and_options_parameter
+ assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50")
+ end
+
+ def test_count_with_no_parameters_isnt_deprecated
+ assert_not_deprecated { Account.count }
+ end
+
+ def test_count_with_too_many_parameters_raises
+ assert_raise(ArgumentError) { Account.count(1, 2, 3) }
+ end
+
+ def test_should_sum_expression
+ assert_equal "636", Account.sum("2 * credit_limit")
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/callbacks_test.rb b/vendor/rails/activerecord/test/cases/callbacks_test.rb
new file mode 100644
index 0000000..11830a2
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/callbacks_test.rb
@@ -0,0 +1,400 @@
+require "cases/helper"
+
+class CallbackDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ class << self
+ def callback_string(callback_method)
+ "history << [#{callback_method.to_sym.inspect}, :string]"
+ end
+
+ def callback_proc(callback_method)
+ Proc.new { |model| model.history << [callback_method, :proc] }
+ end
+
+ def define_callback_method(callback_method)
+ define_method("#{callback_method}_method") do |model|
+ model.history << [callback_method, :method]
+ end
+ end
+
+ def callback_object(callback_method)
+ klass = Class.new
+ klass.send(:define_method, callback_method) do |model|
+ model.history << [callback_method, :object]
+ end
+ klass.new
+ end
+ end
+
+ ActiveRecord::Callbacks::CALLBACKS.each do |callback_method|
+ callback_method_sym = callback_method.to_sym
+ define_callback_method(callback_method_sym)
+ send(callback_method, callback_method_sym)
+ send(callback_method, callback_string(callback_method_sym))
+ send(callback_method, callback_proc(callback_method_sym))
+ send(callback_method, callback_object(callback_method_sym))
+ send(callback_method) { |model| model.history << [callback_method_sym, :block] }
+ end
+
+ def history
+ @history ||= []
+ end
+
+ # after_initialize and after_find are invoked only if instance methods have been defined.
+ def after_initialize
+ end
+
+ def after_find
+ end
+end
+
+class ParentDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+ attr_accessor :after_save_called
+ before_validation {|record| record.after_save_called = true}
+end
+
+class ChildDeveloper < ParentDeveloper
+
+end
+
+class RecursiveCallbackDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ before_save :on_before_save
+ after_save :on_after_save
+
+ attr_reader :on_before_save_called, :on_after_save_called
+
+ def on_before_save
+ @on_before_save_called ||= 0
+ @on_before_save_called += 1
+ save unless @on_before_save_called > 1
+ end
+
+ def on_after_save
+ @on_after_save_called ||= 0
+ @on_after_save_called += 1
+ save unless @on_after_save_called > 1
+ end
+end
+
+class ImmutableDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ validates_inclusion_of :salary, :in => 50000..200000
+
+ before_save :cancel
+ before_destroy :cancel
+
+ def cancelled?
+ @cancelled == true
+ end
+
+ private
+ def cancel
+ @cancelled = true
+ false
+ end
+end
+
+class ImmutableMethodDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+
+ validates_inclusion_of :salary, :in => 50000..200000
+
+ def cancelled?
+ @cancelled == true
+ end
+
+ def before_save
+ @cancelled = true
+ false
+ end
+
+ def before_destroy
+ @cancelled = true
+ false
+ end
+end
+
+class CallbackCancellationDeveloper < ActiveRecord::Base
+ set_table_name 'developers'
+ def before_create
+ false
+ end
+end
+
+class CallbacksTest < ActiveRecord::TestCase
+ fixtures :developers
+
+ def test_initialize
+ david = CallbackDeveloper.new
+ assert_equal [
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ ], david.history
+ end
+
+ def test_find
+ david = CallbackDeveloper.find(1)
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ ], david.history
+ end
+
+ def test_new_valid?
+ david = CallbackDeveloper.new
+ david.valid?
+ assert_equal [
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation_on_create, :string ],
+ [ :before_validation_on_create, :proc ],
+ [ :before_validation_on_create, :object ],
+ [ :before_validation_on_create, :block ],
+ [ :after_validation, :string ],
+ [ :after_validation, :proc ],
+ [ :after_validation, :object ],
+ [ :after_validation, :block ],
+ [ :after_validation_on_create, :string ],
+ [ :after_validation_on_create, :proc ],
+ [ :after_validation_on_create, :object ],
+ [ :after_validation_on_create, :block ]
+ ], david.history
+ end
+
+ def test_existing_valid?
+ david = CallbackDeveloper.find(1)
+ david.valid?
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation_on_update, :string ],
+ [ :before_validation_on_update, :proc ],
+ [ :before_validation_on_update, :object ],
+ [ :before_validation_on_update, :block ],
+ [ :after_validation, :string ],
+ [ :after_validation, :proc ],
+ [ :after_validation, :object ],
+ [ :after_validation, :block ],
+ [ :after_validation_on_update, :string ],
+ [ :after_validation_on_update, :proc ],
+ [ :after_validation_on_update, :object ],
+ [ :after_validation_on_update, :block ]
+ ], david.history
+ end
+
+ def test_create
+ david = CallbackDeveloper.create('name' => 'David', 'salary' => 1000000)
+ assert_equal [
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation_on_create, :string ],
+ [ :before_validation_on_create, :proc ],
+ [ :before_validation_on_create, :object ],
+ [ :before_validation_on_create, :block ],
+ [ :after_validation, :string ],
+ [ :after_validation, :proc ],
+ [ :after_validation, :object ],
+ [ :after_validation, :block ],
+ [ :after_validation_on_create, :string ],
+ [ :after_validation_on_create, :proc ],
+ [ :after_validation_on_create, :object ],
+ [ :after_validation_on_create, :block ],
+ [ :before_save, :string ],
+ [ :before_save, :proc ],
+ [ :before_save, :object ],
+ [ :before_save, :block ],
+ [ :before_create, :string ],
+ [ :before_create, :proc ],
+ [ :before_create, :object ],
+ [ :before_create, :block ],
+ [ :after_create, :string ],
+ [ :after_create, :proc ],
+ [ :after_create, :object ],
+ [ :after_create, :block ],
+ [ :after_save, :string ],
+ [ :after_save, :proc ],
+ [ :after_save, :object ],
+ [ :after_save, :block ]
+ ], david.history
+ end
+
+ def test_save
+ david = CallbackDeveloper.find(1)
+ david.save
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation_on_update, :string ],
+ [ :before_validation_on_update, :proc ],
+ [ :before_validation_on_update, :object ],
+ [ :before_validation_on_update, :block ],
+ [ :after_validation, :string ],
+ [ :after_validation, :proc ],
+ [ :after_validation, :object ],
+ [ :after_validation, :block ],
+ [ :after_validation_on_update, :string ],
+ [ :after_validation_on_update, :proc ],
+ [ :after_validation_on_update, :object ],
+ [ :after_validation_on_update, :block ],
+ [ :before_save, :string ],
+ [ :before_save, :proc ],
+ [ :before_save, :object ],
+ [ :before_save, :block ],
+ [ :before_update, :string ],
+ [ :before_update, :proc ],
+ [ :before_update, :object ],
+ [ :before_update, :block ],
+ [ :after_update, :string ],
+ [ :after_update, :proc ],
+ [ :after_update, :object ],
+ [ :after_update, :block ],
+ [ :after_save, :string ],
+ [ :after_save, :proc ],
+ [ :after_save, :object ],
+ [ :after_save, :block ]
+ ], david.history
+ end
+
+ def test_destroy
+ david = CallbackDeveloper.find(1)
+ david.destroy
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_destroy, :string ],
+ [ :before_destroy, :proc ],
+ [ :before_destroy, :object ],
+ [ :before_destroy, :block ],
+ [ :after_destroy, :string ],
+ [ :after_destroy, :proc ],
+ [ :after_destroy, :object ],
+ [ :after_destroy, :block ]
+ ], david.history
+ end
+
+ def test_delete
+ david = CallbackDeveloper.find(1)
+ CallbackDeveloper.delete(david.id)
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ ], david.history
+ end
+
+ def test_before_save_returning_false
+ david = ImmutableDeveloper.find(1)
+ assert david.valid?
+ assert !david.save
+ assert_raises(ActiveRecord::RecordNotSaved) { david.save! }
+
+ david = ImmutableDeveloper.find(1)
+ david.salary = 10_000_000
+ assert !david.valid?
+ assert !david.save
+ assert_raises(ActiveRecord::RecordInvalid) { david.save! }
+ end
+
+ def test_before_create_returning_false
+ someone = CallbackCancellationDeveloper.new
+ assert someone.valid?
+ assert !someone.save
+ end
+
+ def test_before_destroy_returning_false
+ david = ImmutableDeveloper.find(1)
+ assert !david.destroy
+ assert_not_nil ImmutableDeveloper.find_by_id(1)
+ end
+
+ def test_zzz_callback_returning_false # must be run last since we modify CallbackDeveloper
+ david = CallbackDeveloper.find(1)
+ CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :returning_false]; return false }
+ CallbackDeveloper.before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] }
+ david.save
+ assert_equal [
+ [ :after_find, :string ],
+ [ :after_find, :proc ],
+ [ :after_find, :object ],
+ [ :after_find, :block ],
+ [ :after_initialize, :string ],
+ [ :after_initialize, :proc ],
+ [ :after_initialize, :object ],
+ [ :after_initialize, :block ],
+ [ :before_validation, :string ],
+ [ :before_validation, :proc ],
+ [ :before_validation, :object ],
+ [ :before_validation, :block ],
+ [ :before_validation, :returning_false ]
+ ], david.history
+ end
+
+ def test_inheritence_of_callbacks
+ parent = ParentDeveloper.new
+ assert !parent.after_save_called
+ parent.save
+ assert parent.after_save_called
+
+ child = ChildDeveloper.new
+ assert !child.after_save_called
+ child.save
+ assert child.after_save_called
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/class_inheritable_attributes_test.rb b/vendor/rails/activerecord/test/cases/class_inheritable_attributes_test.rb
new file mode 100644
index 0000000..abeb63c
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/class_inheritable_attributes_test.rb
@@ -0,0 +1,32 @@
+require 'test/unit'
+require "cases/helper"
+require 'active_support/core_ext/class/inheritable_attributes'
+
+class A
+ include ClassInheritableAttributes
+end
+
+class B < A
+ write_inheritable_array "first", [ :one, :two ]
+end
+
+class C < A
+ write_inheritable_array "first", [ :three ]
+end
+
+class D < B
+ write_inheritable_array "first", [ :four ]
+end
+
+
+class ClassInheritableAttributesTest < ActiveRecord::TestCase
+ def test_first_level
+ assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
+ assert_equal [ :three ], C.read_inheritable_attribute("first")
+ end
+
+ def test_second_level
+ assert_equal [ :one, :two, :four ], D.read_inheritable_attribute("first")
+ assert_equal [ :one, :two ], B.read_inheritable_attribute("first")
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/column_alias_test.rb b/vendor/rails/activerecord/test/cases/column_alias_test.rb
new file mode 100644
index 0000000..40707d9
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/column_alias_test.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+require 'models/topic'
+
+class TestColumnAlias < ActiveRecord::TestCase
+ fixtures :topics
+
+ QUERY = if 'Oracle' == ActiveRecord::Base.connection.adapter_name
+ 'SELECT id AS pk FROM topics WHERE ROWNUM < 2'
+ else
+ 'SELECT id AS pk FROM topics'
+ end
+
+ def test_column_alias
+ records = Topic.connection.select_all(QUERY)
+ assert_equal 'pk', records[0].keys[0]
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/connection_test_firebird.rb b/vendor/rails/activerecord/test/cases/connection_test_firebird.rb
new file mode 100644
index 0000000..f57ea68
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/connection_test_firebird.rb
@@ -0,0 +1,8 @@
+require "cases/helper"
+
+class FirebirdConnectionTest < ActiveRecord::TestCase
+ def test_charset_properly_set
+ fb_conn = ActiveRecord::Base.connection.instance_variable_get(:@connection)
+ assert_equal 'UTF8', fb_conn.database.character_set
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/connection_test_mysql.rb b/vendor/rails/activerecord/test/cases/connection_test_mysql.rb
new file mode 100644
index 0000000..1adbf18
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/connection_test_mysql.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+
+class MysqlConnectionTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+ end
+
+ def test_no_automatic_reconnection_after_timeout
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ assert !@connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_manual_reconnect
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.reconnect!
+ assert @connection.active?
+ end
+
+ def test_successful_reconnection_after_timeout_with_verify
+ assert @connection.active?
+ @connection.update('set @@wait_timeout=1')
+ sleep 2
+ @connection.verify!(0)
+ assert @connection.active?
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/copy_table_test_sqlite.rb b/vendor/rails/activerecord/test/cases/copy_table_test_sqlite.rb
new file mode 100644
index 0000000..f0cfb67
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/copy_table_test_sqlite.rb
@@ -0,0 +1,69 @@
+require "cases/helper"
+
+class CopyTableTest < ActiveRecord::TestCase
+ fixtures :companies, :comments
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ class << @connection
+ public :copy_table, :table_structure, :indexes
+ end
+ end
+
+ def test_copy_table(from = 'companies', to = 'companies2', options = {})
+ assert_nothing_raised {copy_table(from, to, options)}
+ assert_equal row_count(from), row_count(to)
+
+ if block_given?
+ yield from, to, options
+ else
+ assert_equal column_names(from), column_names(to)
+ end
+
+ @connection.drop_table(to) rescue nil
+ end
+
+ def test_copy_table_renaming_column
+ test_copy_table('companies', 'companies2',
+ :rename => {'client_of' => 'fan_of'}) do |from, to, options|
+ expected = column_values(from, 'client_of')
+ assert expected.any?, 'only nils in resultset; real values are needed'
+ assert_equal expected, column_values(to, 'fan_of')
+ end
+ end
+
+ def test_copy_table_with_index
+ test_copy_table('comments', 'comments_with_index') do
+ @connection.add_index('comments_with_index', ['post_id', 'type'])
+ test_copy_table('comments_with_index', 'comments_with_index2') do
+ assert_equal table_indexes_without_name('comments_with_index'),
+ table_indexes_without_name('comments_with_index2')
+ end
+ end
+ end
+
+ def test_copy_table_without_primary_key
+ test_copy_table('developers_projects', 'programmers_projects')
+ end
+
+protected
+ def copy_table(from, to, options = {})
+ @connection.copy_table(from, to, {:temporary => true}.merge(options))
+ end
+
+ def column_names(table)
+ @connection.table_structure(table).map {|column| column['name']}
+ end
+
+ def column_values(table, column)
+ @connection.select_all("SELECT #{column} FROM #{table} ORDER BY id").map {|row| row[column]}
+ end
+
+ def table_indexes_without_name(table)
+ @connection.indexes('comments_with_index').delete(:name)
+ end
+
+ def row_count(table)
+ @connection.select_one("SELECT COUNT(*) AS count FROM #{table}")['count']
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/datatype_test_postgresql.rb b/vendor/rails/activerecord/test/cases/datatype_test_postgresql.rb
new file mode 100644
index 0000000..bff092b
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/datatype_test_postgresql.rb
@@ -0,0 +1,203 @@
+require "cases/helper"
+
+class PostgresqlArray < ActiveRecord::Base
+end
+
+class PostgresqlMoney < ActiveRecord::Base
+end
+
+class PostgresqlNumber < ActiveRecord::Base
+end
+
+class PostgresqlTime < ActiveRecord::Base
+end
+
+class PostgresqlNetworkAddress < ActiveRecord::Base
+end
+
+class PostgresqlBitString < ActiveRecord::Base
+end
+
+class PostgresqlOid < ActiveRecord::Base
+end
+
+class PostgresqlDataTypeTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )")
+ @first_array = PostgresqlArray.find(1)
+
+ @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('567.89'::money)")
+ @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-567.89'::money)")
+ @first_money = PostgresqlMoney.find(1)
+ @second_money = PostgresqlMoney.find(2)
+
+ @connection.execute("INSERT INTO postgresql_numbers (single, double) VALUES (123.456, 123456.789)")
+ @first_number = PostgresqlNumber.find(1)
+
+ @connection.execute("INSERT INTO postgresql_times (time_interval) VALUES ('1 year 2 days ago')")
+ @first_time = PostgresqlTime.find(1)
+
+ @connection.execute("INSERT INTO postgresql_network_addresses (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')")
+ @first_network_address = PostgresqlNetworkAddress.find(1)
+
+ @connection.execute("INSERT INTO postgresql_bit_strings (bit_string, bit_string_varying) VALUES (B'00010101', X'15')")
+ @first_bit_string = PostgresqlBitString.find(1)
+
+ @connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)")
+ @first_oid = PostgresqlOid.find(1)
+ end
+
+ def test_data_type_of_array_types
+ assert_equal :string, @first_array.column_for_attribute(:commission_by_quarter).type
+ assert_equal :string, @first_array.column_for_attribute(:nicknames).type
+ end
+
+ def test_data_type_of_money_types
+ assert_equal :decimal, @first_money.column_for_attribute(:wealth).type
+ end
+
+ def test_data_type_of_number_types
+ assert_equal :float, @first_number.column_for_attribute(:single).type
+ assert_equal :float, @first_number.column_for_attribute(:double).type
+ end
+
+ def test_data_type_of_time_types
+ assert_equal :string, @first_time.column_for_attribute(:time_interval).type
+ end
+
+ def test_data_type_of_network_address_types
+ assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type
+ assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type
+ assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type
+ end
+
+ def test_data_type_of_bit_string_types
+ assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type
+ assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type
+ end
+
+ def test_data_type_of_oid_types
+ assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type
+ end
+
+ def test_array_values
+ assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter
+ assert_equal '{foo,bar,baz}', @first_array.nicknames
+ end
+
+ def test_money_values
+ assert_equal 567.89, @first_money.wealth
+ assert_equal -567.89, @second_money.wealth
+ end
+
+ def test_number_values
+ assert_equal 123.456, @first_number.single
+ assert_equal 123456.789, @first_number.double
+ end
+
+ def test_time_values
+ assert_equal '-1 years -2 days', @first_time.time_interval
+ end
+
+ def test_network_address_values
+ assert_equal '192.168.0.0/24', @first_network_address.cidr_address
+ assert_equal '172.16.1.254', @first_network_address.inet_address
+ assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address
+ end
+
+ def test_bit_string_values
+ assert_equal '00010101', @first_bit_string.bit_string
+ assert_equal '00010101', @first_bit_string.bit_string_varying
+ end
+
+ def test_oid_values
+ assert_equal 1234, @first_oid.obj_id
+ end
+
+ def test_update_integer_array
+ new_value = '{32800,95000,29350,17000}'
+ assert @first_array.commission_by_quarter = new_value
+ assert @first_array.save
+ assert @first_array.reload
+ assert_equal @first_array.commission_by_quarter, new_value
+ assert @first_array.commission_by_quarter = new_value
+ assert @first_array.save
+ assert @first_array.reload
+ assert_equal @first_array.commission_by_quarter, new_value
+ end
+
+ def test_update_text_array
+ new_value = '{robby,robert,rob,robbie}'
+ assert @first_array.nicknames = new_value
+ assert @first_array.save
+ assert @first_array.reload
+ assert_equal @first_array.nicknames, new_value
+ assert @first_array.nicknames = new_value
+ assert @first_array.save
+ assert @first_array.reload
+ assert_equal @first_array.nicknames, new_value
+ end
+
+ def test_update_money
+ new_value = BigDecimal.new('123.45')
+ assert @first_money.wealth = new_value
+ assert @first_money.save
+ assert @first_money.reload
+ assert_equal new_value, @first_money.wealth
+ end
+
+ def test_update_number
+ new_single = 789.012
+ new_double = 789012.345
+ assert @first_number.single = new_single
+ assert @first_number.double = new_double
+ assert @first_number.save
+ assert @first_number.reload
+ assert_equal @first_number.single, new_single
+ assert_equal @first_number.double, new_double
+ end
+
+ def test_update_time
+ assert @first_time.time_interval = '2 years 3 minutes'
+ assert @first_time.save
+ assert @first_time.reload
+ assert_equal @first_time.time_interval, '2 years 00:03:00'
+ end
+
+ def test_update_network_address
+ new_cidr_address = '10.1.2.3/32'
+ new_inet_address = '10.0.0.0/8'
+ new_mac_address = 'bc:de:f0:12:34:56'
+ assert @first_network_address.cidr_address = new_cidr_address
+ assert @first_network_address.inet_address = new_inet_address
+ assert @first_network_address.mac_address = new_mac_address
+ assert @first_network_address.save
+ assert @first_network_address.reload
+ assert_equal @first_network_address.cidr_address, new_cidr_address
+ assert_equal @first_network_address.inet_address, new_inet_address
+ assert_equal @first_network_address.mac_address, new_mac_address
+ end
+
+ def test_update_bit_string
+ new_bit_string = '11111111'
+ new_bit_string_varying = 'FF'
+ assert @first_bit_string.bit_string = new_bit_string
+ assert @first_bit_string.bit_string_varying = new_bit_string_varying
+ assert @first_bit_string.save
+ assert @first_bit_string.reload
+ assert_equal @first_bit_string.bit_string, new_bit_string
+ assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying
+ end
+
+ def test_update_oid
+ new_value = 567890
+ assert @first_oid.obj_id = new_value
+ assert @first_oid.save
+ assert @first_oid.reload
+ assert_equal @first_oid.obj_id, new_value
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/date_time_test.rb b/vendor/rails/activerecord/test/cases/date_time_test.rb
new file mode 100644
index 0000000..36e1caa
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/date_time_test.rb
@@ -0,0 +1,37 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/task'
+
+class DateTimeTest < ActiveRecord::TestCase
+ def test_saves_both_date_and_time
+ time_values = [1807, 2, 10, 15, 30, 45]
+ now = DateTime.civil(*time_values)
+
+ task = Task.new
+ task.starting = now
+ task.save!
+
+ # check against Time.local_time, since some platforms will return a Time instead of a DateTime
+ assert_equal Time.local_time(*time_values), Task.find(task.id).starting
+ end
+
+ def test_assign_empty_date_time
+ task = Task.new
+ task.starting = ''
+ task.ending = nil
+ assert_nil task.starting
+ assert_nil task.ending
+ end
+
+ def test_assign_empty_date
+ topic = Topic.new
+ topic.last_read = ''
+ assert_nil topic.last_read
+ end
+
+ def test_assign_empty_time
+ topic = Topic.new
+ topic.bonus_time = ''
+ assert_nil topic.bonus_time
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/default_test_firebird.rb b/vendor/rails/activerecord/test/cases/default_test_firebird.rb
new file mode 100644
index 0000000..713c7e1
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/default_test_firebird.rb
@@ -0,0 +1,16 @@
+require "cases/helper"
+require 'models/default'
+
+class DefaultTest < ActiveRecord::TestCase
+ def test_default_timestamp
+ default = Default.new
+ assert_instance_of(Time, default.default_timestamp)
+ assert_equal(:datetime, default.column_for_attribute(:default_timestamp).type)
+
+ # Variance should be small; increase if required -- e.g., if test db is on
+ # remote host and clocks aren't synchronized.
+ t1 = Time.new
+ accepted_variance = 1.0
+ assert_in_delta(t1.to_f, default.default_timestamp.to_f, accepted_variance)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/defaults_test.rb b/vendor/rails/activerecord/test/cases/defaults_test.rb
new file mode 100644
index 0000000..2ea8541
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/defaults_test.rb
@@ -0,0 +1,69 @@
+require "cases/helper"
+require 'models/default'
+require 'models/entrant'
+
+class DefaultTest < ActiveRecord::TestCase
+ def test_nil_defaults_for_not_null_columns
+ column_defaults =
+ if current_adapter?(:MysqlAdapter) && Mysql.client_version < 50051
+ { 'id' => nil, 'name' => '', 'course_id' => nil }
+ else
+ { 'id' => nil, 'name' => nil, 'course_id' => nil }
+ end
+
+ column_defaults.each do |name, default|
+ column = Entrant.columns_hash[name]
+ assert !column.null, "#{name} column should be NOT NULL"
+ assert_equal default, column.default, "#{name} column should be DEFAULT #{default.inspect}"
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ # MySQL uses an implicit default 0 rather than NULL unless in strict mode.
+ # We use an implicit NULL so schema.rb is compatible with other databases.
+ def test_mysql_integer_not_null_defaults
+ klass = Class.new(ActiveRecord::Base)
+ klass.table_name = 'test_integer_not_null_default_zero'
+ klass.connection.create_table klass.table_name do |t|
+ t.column :zero, :integer, :null => false, :default => 0
+ t.column :omit, :integer, :null => false
+ end
+
+ assert_equal 0, klass.columns_hash['zero'].default
+ assert !klass.columns_hash['zero'].null
+ # 0 in MySQL 4, nil in 5.
+ assert [0, nil].include?(klass.columns_hash['omit'].default)
+ assert !klass.columns_hash['omit'].null
+
+ assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
+
+ assert_nothing_raised do
+ instance = klass.create!(:omit => 1)
+ assert_equal 0, instance.zero
+ assert_equal 1, instance.omit
+ end
+ ensure
+ klass.connection.drop_table(klass.table_name) rescue nil
+ end
+ end
+
+ if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter)
+ def test_default_integers
+ default = Default.new
+ assert_instance_of Fixnum, default.positive_integer
+ assert_equal 1, default.positive_integer
+ assert_instance_of Fixnum, default.negative_integer
+ assert_equal -1, default.negative_integer
+ assert_instance_of BigDecimal, default.decimal_number
+ assert_equal BigDecimal.new("2.78"), default.decimal_number
+ end
+ end
+
+ if current_adapter?(:PostgreSQLAdapter)
+ def test_multiline_default_text
+ # older postgres versions represent the default with escapes ("\\012" for a newline)
+ assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default ||
+ "--- []\\012\\012" == Default.columns_hash['multiline_default'].default)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/deprecated_finder_test.rb b/vendor/rails/activerecord/test/cases/deprecated_finder_test.rb
new file mode 100644
index 0000000..2afc91b
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/deprecated_finder_test.rb
@@ -0,0 +1,30 @@
+require "cases/helper"
+require 'models/entrant'
+
+class DeprecatedFinderTest < ActiveRecord::TestCase
+ fixtures :entrants
+
+ def test_deprecated_find_all_was_removed
+ assert_raise(NoMethodError) { Entrant.find_all }
+ end
+
+ def test_deprecated_find_first_was_removed
+ assert_raise(NoMethodError) { Entrant.find_first }
+ end
+
+ def test_deprecated_find_on_conditions_was_removed
+ assert_raise(NoMethodError) { Entrant.find_on_conditions }
+ end
+
+ def test_count
+ assert_equal(0, Entrant.count(:conditions => "id > 3"))
+ assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
+ assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
+ end
+
+ def test_count_by_sql
+ assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
+ assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
+ assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/dirty_test.rb b/vendor/rails/activerecord/test/cases/dirty_test.rb
new file mode 100644
index 0000000..c011ffa
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/dirty_test.rb
@@ -0,0 +1,163 @@
+require 'cases/helper'
+require 'models/topic' # For booleans
+require 'models/pirate' # For timestamps
+require 'models/parrot'
+
+class Pirate # Just reopening it, not defining it
+ attr_accessor :detected_changes_in_after_update # Boolean for if changes are detected
+ attr_accessor :changes_detected_in_after_update # Actual changes
+
+ after_update :check_changes
+
+private
+ # after_save/update in sweepers, observers, and the model itself
+ # can end up checking dirty status and acting on the results
+ def check_changes
+ if self.changed?
+ self.detected_changes_in_after_update = true
+ self.changes_detected_in_after_update = self.changes
+ end
+ end
+end
+
+class DirtyTest < ActiveRecord::TestCase
+ def test_attribute_changes
+ # New record - no changes.
+ pirate = Pirate.new
+ assert !pirate.catchphrase_changed?
+ assert_nil pirate.catchphrase_change
+
+ # Change catchphrase.
+ pirate.catchphrase = 'arrr'
+ assert pirate.catchphrase_changed?
+ assert_nil pirate.catchphrase_was
+ assert_equal [nil, 'arrr'], pirate.catchphrase_change
+
+ # Saved - no changes.
+ pirate.save!
+ assert !pirate.catchphrase_changed?
+ assert_nil pirate.catchphrase_change
+
+ # Same value - no changes.
+ pirate.catchphrase = 'arrr'
+ assert !pirate.catchphrase_changed?
+ assert_nil pirate.catchphrase_change
+ end
+
+ def test_nullable_integer_not_marked_as_changed_if_new_value_is_blank
+ pirate = Pirate.new
+
+ ["", nil].each do |value|
+ pirate.parrot_id = value
+ assert !pirate.parrot_id_changed?
+ assert_nil pirate.parrot_id_change
+ end
+ end
+
+ def test_object_should_be_changed_if_any_attribute_is_changed
+ pirate = Pirate.new
+ assert !pirate.changed?
+ assert_equal [], pirate.changed
+ assert_equal Hash.new, pirate.changes
+
+ pirate.catchphrase = 'arrr'
+ assert pirate.changed?
+ assert_nil pirate.catchphrase_was
+ assert_equal %w(catchphrase), pirate.changed
+ assert_equal({'catchphrase' => [nil, 'arrr']}, pirate.changes)
+
+ pirate.save
+ assert !pirate.changed?
+ assert_equal [], pirate.changed
+ assert_equal Hash.new, pirate.changes
+ end
+
+ def test_attribute_will_change!
+ pirate = Pirate.create!(:catchphrase => 'arr')
+
+ pirate.catchphrase << ' matey'
+ assert !pirate.catchphrase_changed?
+
+ assert pirate.catchphrase_will_change!
+ assert pirate.catchphrase_changed?
+ assert_equal ['arr matey', 'arr matey'], pirate.catchphrase_change
+
+ pirate.catchphrase << '!'
+ assert pirate.catchphrase_changed?
+ assert_equal ['arr matey', 'arr matey!'], pirate.catchphrase_change
+ end
+
+ def test_association_assignment_changes_foreign_key
+ pirate = Pirate.create!(:catchphrase => 'jarl')
+ pirate.parrot = Parrot.create!
+ assert pirate.changed?
+ assert_equal %w(parrot_id), pirate.changed
+ end
+
+ def test_attribute_should_be_compared_with_type_cast
+ topic = Topic.new
+ assert topic.approved?
+ assert !topic.approved_changed?
+
+ # Coming from web form.
+ params = {:topic => {:approved => 1}}
+ # In the controller.
+ topic.attributes = params[:topic]
+ assert topic.approved?
+ assert !topic.approved_changed?
+ end
+
+ def test_partial_update
+ pirate = Pirate.new(:catchphrase => 'foo')
+ old_updated_on = 1.hour.ago.beginning_of_day
+
+ with_partial_updates Pirate, false do
+ assert_queries(2) { 2.times { pirate.save! } }
+ Pirate.update_all({ :updated_on => old_updated_on }, :id => pirate.id)
+ end
+
+ with_partial_updates Pirate, true do
+ assert_queries(0) { 2.times { pirate.save! } }
+ assert_equal old_updated_on, pirate.reload.updated_on
+
+ assert_queries(1) { pirate.catchphrase = 'bar'; pirate.save! }
+ assert_not_equal old_updated_on, pirate.reload.updated_on
+ end
+ end
+
+ def test_changed_attributes_should_be_preserved_if_save_failure
+ pirate = Pirate.new
+ pirate.parrot_id = 1
+ assert !pirate.save
+ check_pirate_after_save_failure(pirate)
+
+ pirate = Pirate.new
+ pirate.parrot_id = 1
+ assert_raises(ActiveRecord::RecordInvalid) { pirate.save! }
+ check_pirate_after_save_failure(pirate)
+ end
+
+ def test_reload_should_clear_changed_attributes
+ pirate = Pirate.create!(:catchphrase => "shiver me timbers")
+ pirate.catchphrase = "*hic*"
+ assert pirate.changed?
+ pirate.reload
+ assert !pirate.changed?
+ end
+
+ private
+ def with_partial_updates(klass, on = true)
+ old = klass.partial_updates?
+ klass.partial_updates = on
+ yield
+ ensure
+ klass.partial_updates = old
+ end
+
+ def check_pirate_after_save_failure(pirate)
+ assert pirate.changed?
+ assert pirate.parrot_id_changed?
+ assert_equal %w(parrot_id), pirate.changed
+ assert_nil pirate.parrot_id_was
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/finder_respond_to_test.rb b/vendor/rails/activerecord/test/cases/finder_respond_to_test.rb
new file mode 100644
index 0000000..4e6fecf
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/finder_respond_to_test.rb
@@ -0,0 +1,76 @@
+require "cases/helper"
+require 'models/topic'
+
+class FinderRespondToTest < ActiveRecord::TestCase
+
+ fixtures :topics
+
+ def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method
+ class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { }
+ assert Topic.respond_to?(:method_added_for_finder_respond_to_test)
+ ensure
+ class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test)
+ end
+
+ def test_should_preserve_normal_respond_to_behaviour_and_respond_to_standard_object_method
+ assert Topic.respond_to?(:to_s)
+ end
+
+ def test_should_respond_to_find_by_one_attribute_before_caching
+ ensure_topic_method_is_not_cached(:find_by_title)
+ assert Topic.respond_to?(:find_by_title)
+ end
+
+ def test_should_respond_to_find_all_by_one_attribute
+ ensure_topic_method_is_not_cached(:find_all_by_title)
+ assert Topic.respond_to?(:find_all_by_title)
+ end
+
+ def test_should_respond_to_find_all_by_two_attributes
+ ensure_topic_method_is_not_cached(:find_all_by_title_and_author_name)
+ assert Topic.respond_to?(:find_all_by_title_and_author_name)
+ end
+
+ def test_should_respond_to_find_by_two_attributes
+ ensure_topic_method_is_not_cached(:find_by_title_and_author_name)
+ assert Topic.respond_to?(:find_by_title_and_author_name)
+ end
+
+ def test_should_respond_to_find_or_initialize_from_one_attribute
+ ensure_topic_method_is_not_cached(:find_or_initialize_by_title)
+ assert Topic.respond_to?(:find_or_initialize_by_title)
+ end
+
+ def test_should_respond_to_find_or_initialize_from_two_attributes
+ ensure_topic_method_is_not_cached(:find_or_initialize_by_title_and_author_name)
+ assert Topic.respond_to?(:find_or_initialize_by_title_and_author_name)
+ end
+
+ def test_should_respond_to_find_or_create_from_one_attribute
+ ensure_topic_method_is_not_cached(:find_or_create_by_title)
+ assert Topic.respond_to?(:find_or_create_by_title)
+ end
+
+ def test_should_respond_to_find_or_create_from_two_attributes
+ ensure_topic_method_is_not_cached(:find_or_create_by_title_and_author_name)
+ assert Topic.respond_to?(:find_or_create_by_title_and_author_name)
+ end
+
+ def test_should_not_respond_to_find_by_one_missing_attribute
+ assert !Topic.respond_to?(:find_by_undertitle)
+ end
+
+ def test_should_not_respond_to_find_by_invalid_method_syntax
+ assert !Topic.respond_to?(:fail_to_find_by_title)
+ assert !Topic.respond_to?(:find_by_title?)
+ assert !Topic.respond_to?(:fail_to_find_or_create_by_title)
+ assert !Topic.respond_to?(:find_or_create_by_title?)
+ end
+
+ private
+
+ def ensure_topic_method_is_not_cached(method_id)
+ class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.any? { |m| m.to_s == method_id.to_s }
+ end
+
+end
\ No newline at end of file
diff --git a/vendor/rails/activerecord/test/cases/finder_test.rb b/vendor/rails/activerecord/test/cases/finder_test.rb
new file mode 100644
index 0000000..80936d5
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/finder_test.rb
@@ -0,0 +1,883 @@
+require "cases/helper"
+require 'models/author'
+require 'models/comment'
+require 'models/company'
+require 'models/topic'
+require 'models/reply'
+require 'models/entrant'
+require 'models/developer'
+require 'models/post'
+require 'models/customer'
+require 'models/job'
+require 'models/categorization'
+
+class FinderTest < ActiveRecord::TestCase
+ fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers
+
+ def test_find
+ assert_equal(topics(:first).title, Topic.find(1).title)
+ end
+
+ # find should handle strings that come from URLs
+ # (example: Category.find(params[:id]))
+ def test_find_with_string
+ assert_equal(Topic.find(1).title,Topic.find("1").title)
+ end
+
+ def test_exists
+ assert Topic.exists?(1)
+ assert Topic.exists?("1")
+ assert Topic.exists?(:author_name => "David")
+ assert Topic.exists?(:author_name => "Mary", :approved => true)
+ assert Topic.exists?(["parent_id = ?", 1])
+ assert !Topic.exists?(45)
+
+ begin
+ assert !Topic.exists?("foo")
+ rescue ActiveRecord::StatementInvalid
+ # PostgreSQL complains about string comparison with integer field
+ rescue Exception
+ flunk
+ end
+
+ assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+ end
+
+ def test_exists_with_aggregate_having_three_mappings
+ existing_address = customers(:david).address
+ assert Customer.exists?(:address => existing_address)
+ end
+
+ def test_exists_with_aggregate_having_three_mappings_with_one_difference
+ existing_address = customers(:david).address
+ assert !Customer.exists?(:address =>
+ Address.new(existing_address.street, existing_address.city, existing_address.country + "1"))
+ assert !Customer.exists?(:address =>
+ Address.new(existing_address.street, existing_address.city + "1", existing_address.country))
+ assert !Customer.exists?(:address =>
+ Address.new(existing_address.street + "1", existing_address.city, existing_address.country))
+ end
+
+ def test_find_by_array_of_one_id
+ assert_kind_of(Array, Topic.find([ 1 ]))
+ assert_equal(1, Topic.find([ 1 ]).length)
+ end
+
+ def test_find_by_ids
+ assert_equal 2, Topic.find(1, 2).size
+ assert_equal topics(:second).title, Topic.find([2]).first.title
+ end
+
+ def test_find_by_ids_with_limit_and_offset
+ assert_equal 2, Entrant.find([1,3,2], :limit => 2).size
+ assert_equal 1, Entrant.find([1,3,2], :limit => 3, :offset => 2).size
+
+ # Also test an edge case: If you have 11 results, and you set a
+ # limit of 3 and offset of 9, then you should find that there
+ # will be only 2 results, regardless of the limit.
+ devs = Developer.find :all
+ last_devs = Developer.find devs.map(&:id), :limit => 3, :offset => 9
+ assert_equal 2, last_devs.size
+ end
+
+ def test_find_an_empty_array
+ assert_equal [], Topic.find([])
+ end
+
+ def test_find_by_ids_missing_one
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, 2, 45) }
+ end
+
+ def test_find_all_with_limit
+ entrants = Entrant.find(:all, :order => "id ASC", :limit => 2)
+
+ assert_equal(2, entrants.size)
+ assert_equal(entrants(:first).name, entrants.first.name)
+ end
+
+ def test_find_all_with_prepared_limit_and_offset
+ entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 1)
+
+ assert_equal(2, entrants.size)
+ assert_equal(entrants(:second).name, entrants.first.name)
+
+ entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2)
+ assert_equal(1, entrants.size)
+ assert_equal(entrants(:third).name, entrants.first.name)
+ end
+
+ def test_find_all_with_limit_and_offset_and_multiple_orderings
+ developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1)
+ assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name}
+ end
+
+ def test_find_with_limit_and_condition
+ developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7)
+ assert_equal(1, developers.size)
+ assert_equal("fixture_3", developers.first.name)
+ end
+
+ def test_find_with_entire_select_statement
+ topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
+
+ assert_equal(1, topics.size)
+ assert_equal(topics(:second).title, topics.first.title)
+ end
+
+ def test_find_with_prepared_select_statement
+ topics = Topic.find_by_sql ["SELECT * FROM topics WHERE author_name = ?", "Mary"]
+
+ assert_equal(1, topics.size)
+ assert_equal(topics(:second).title, topics.first.title)
+ end
+
+ def test_find_by_sql_with_sti_on_joined_table
+ accounts = Account.find_by_sql("SELECT * FROM accounts INNER JOIN companies ON companies.id = accounts.firm_id")
+ assert_equal [Account], accounts.collect(&:class).uniq
+ end
+
+ def test_find_first
+ first = Topic.find(:first, :conditions => "title = 'The First Topic'")
+ assert_equal(topics(:first).title, first.title)
+ end
+
+ def test_find_first_failing
+ first = Topic.find(:first, :conditions => "title = 'The First Topic!'")
+ assert_nil(first)
+ end
+
+ def test_first
+ assert_equal topics(:second).title, Topic.first(:conditions => "title = 'The Second Topic of the day'").title
+ end
+
+ def test_first_failing
+ assert_nil Topic.first(:conditions => "title = 'The Second Topic of the day!'")
+ end
+
+ def test_unexisting_record_exception_handling
+ assert_raises(ActiveRecord::RecordNotFound) {
+ Topic.find(1).parent
+ }
+
+ Topic.find(2).topic
+ end
+
+ def test_find_only_some_columns
+ topic = Topic.find(1, :select => "author_name")
+ assert_raises(ActiveRecord::MissingAttributeError) {topic.title}
+ assert_equal "David", topic.author_name
+ assert !topic.attribute_present?("title")
+ #assert !topic.respond_to?("title")
+ assert topic.attribute_present?("author_name")
+ assert topic.respond_to?("author_name")
+ end
+
+ def test_find_on_blank_conditions
+ [nil, " ", [], {}].each do |blank|
+ assert_nothing_raised { Topic.find(:first, :conditions => blank) }
+ end
+ end
+
+ def test_find_on_blank_bind_conditions
+ [ [""], ["",{}] ].each do |blank|
+ assert_nothing_raised { Topic.find(:first, :conditions => blank) }
+ end
+ end
+
+ def test_find_on_array_conditions
+ assert Topic.find(1, :conditions => ["approved = ?", false])
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => ["approved = ?", true]) }
+ end
+
+ def test_find_on_hash_conditions
+ assert Topic.find(1, :conditions => { :approved => false })
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :approved => true }) }
+ end
+
+ def test_find_on_hash_conditions_with_explicit_table_name
+ assert Topic.find(1, :conditions => { 'topics.approved' => false })
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) }
+ end
+
+ def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate
+ david = customers(:david)
+ assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address })
+ assert_raises(ActiveRecord::RecordNotFound) {
+ Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address })
+ }
+ end
+
+ def test_find_on_association_proxy_conditions
+ assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort
+ end
+
+ def test_find_on_hash_conditions_with_range
+ assert_equal [1,2], Topic.find(:all, :conditions => { :id => 1..2 }).map(&:id).sort
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :id => 2..3 }) }
+ end
+
+ def test_find_on_hash_conditions_with_multiple_ranges
+ assert_equal [1,2,3], Comment.find(:all, :conditions => { :id => 1..3, :post_id => 1..2 }).map(&:id).sort
+ assert_equal [1], Comment.find(:all, :conditions => { :id => 1..1, :post_id => 1..10 }).map(&:id).sort
+ end
+
+ def test_find_on_multiple_hash_conditions
+ assert Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => false })
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "HHC", :replies_count => 1, :approved => false }) }
+ assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { :author_name => "David", :title => "The First Topic", :replies_count => 1, :approved => true }) }
+ end
+
+
+ def test_condition_interpolation
+ assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
+ assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ end
+
+ def test_condition_array_interpolation
+ assert_kind_of Firm, Company.find(:first, :conditions => ["name = '%s'", "37signals"])
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!"])
+ assert_nil Company.find(:first, :conditions => ["name = '%s'", "37signals!' OR 1=1"])
+ assert_kind_of Time, Topic.find(:first, :conditions => ["id = %d", 1]).written_on
+ end
+
+ def test_condition_hash_interpolation
+ assert_kind_of Firm, Company.find(:first, :conditions => { :name => "37signals"})
+ assert_nil Company.find(:first, :conditions => { :name => "37signals!"})
+ assert_kind_of Time, Topic.find(:first, :conditions => {:id => 1}).written_on
+ end
+
+ def test_hash_condition_find_malformed
+ assert_raises(ActiveRecord::StatementInvalid) {
+ Company.find(:first, :conditions => { :id => 2, :dhh => true })
+ }
+ end
+
+ def test_hash_condition_find_with_escaped_characters
+ Company.create("name" => "Ain't noth'n like' \#stuff")
+ assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })
+ end
+
+ def test_hash_condition_find_with_array
+ p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
+ assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2] }, :order => 'id asc')
+ assert_equal [p1, p2], Post.find(:all, :conditions => { :id => [p1, p2.id] }, :order => 'id asc')
+ end
+
+ def test_hash_condition_find_with_nil
+ topic = Topic.find(:first, :conditions => { :last_read => nil } )
+ assert_not_nil topic
+ assert_nil topic.last_read
+ end
+
+ def test_hash_condition_find_with_aggregate_having_one_mapping
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customer = Customer.find(:first, :conditions => {:balance => balance})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate
+ gps_location = customers(:david).gps_location
+ assert_kind_of GpsLocation, gps_location
+ found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customer = Customer.find(:first, :conditions => {:balance => balance.amount})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value
+ gps_location = customers(:david).gps_location
+ assert_kind_of GpsLocation, gps_location
+ found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_with_aggregate_having_three_mappings
+ address = customers(:david).address
+ assert_kind_of Address, address
+ found_customer = Customer.find(:first, :conditions => {:address => address})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not
+ address = customers(:david).address
+ assert_kind_of Address, address
+ found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name})
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_bind_variables
+ assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"])
+ assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"])
+ assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!' OR 1=1"])
+ assert_kind_of Time, Topic.find(:first, :conditions => ["id = ?", 1]).written_on
+ assert_raises(ActiveRecord::PreparedStatementInvalid) {
+ Company.find(:first, :conditions => ["id=? AND name = ?", 2])
+ }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) {
+ Company.find(:first, :conditions => ["id=?", 2, 3, 4])
+ }
+ end
+
+ def test_bind_variables_with_quotes
+ Company.create("name" => "37signals' go'es agains")
+ assert Company.find(:first, :conditions => ["name = ?", "37signals' go'es agains"])
+ end
+
+ def test_named_bind_variables_with_quotes
+ Company.create("name" => "37signals' go'es agains")
+ assert Company.find(:first, :conditions => ["name = :name", {:name => "37signals' go'es agains"}])
+ end
+
+ def test_bind_arity
+ assert_nothing_raised { bind '' }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '', 1 }
+
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?' }
+ assert_nothing_raised { bind '?', 1 }
+ assert_raises(ActiveRecord::PreparedStatementInvalid) { bind '?', 1, 1 }
+ end
+
+ def test_named_bind_variables
+ assert_equal '1', bind(':a', :a => 1) # ' ruby-mode
+ assert_equal '1 1', bind(':a :a', :a => 1) # ' ruby-mode
+
+ assert_nothing_raised { bind("'+00:00'", :foo => "bar") }
+
+ assert_kind_of Firm, Company.find(:first, :conditions => ["name = :name", { :name => "37signals" }])
+ assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!" }])
+ assert_nil Company.find(:first, :conditions => ["name = :name", { :name => "37signals!' OR 1=1" }])
+ assert_kind_of Time, Topic.find(:first, :conditions => ["id = :id", { :id => 1 }]).written_on
+ end
+
+ def test_bind_enumerable
+ quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')})
+
+ assert_equal '1,2,3', bind('?', [1, 2, 3])
+ assert_equal quoted_abc, bind('?', %w(a b c))
+
+ assert_equal '1,2,3', bind(':a', :a => [1, 2, 3])
+ assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # '
+
+ require 'set'
+ assert_equal '1,2,3', bind('?', Set.new([1, 2, 3]))
+ assert_equal quoted_abc, bind('?', Set.new(%w(a b c)))
+
+ assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3]))
+ assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # '
+ end
+
+ def test_bind_empty_enumerable
+ quoted_nil = ActiveRecord::Base.connection.quote(nil)
+ assert_equal quoted_nil, bind('?', [])
+ assert_equal " in (#{quoted_nil})", bind(' in (?)', [])
+ assert_equal "foo in (#{quoted_nil})", bind('foo in (?)', [])
+ end
+
+ def test_bind_string
+ assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '')
+ end
+
+ def test_bind_record
+ o = Struct.new(:quoted_id).new(1)
+ assert_equal '1', bind('?', o)
+
+ os = [o] * 3
+ assert_equal '1,1,1', bind('?', os)
+ end
+
+ def test_string_sanitation
+ assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
+ assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table")
+ end
+
+ def test_count
+ assert_equal(0, Entrant.count(:conditions => "id > 3"))
+ assert_equal(1, Entrant.count(:conditions => ["id > ?", 2]))
+ assert_equal(2, Entrant.count(:conditions => ["id > ?", 1]))
+ end
+
+ def test_count_by_sql
+ assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
+ assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
+ assert_equal(2, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 1]))
+ end
+
+ def test_find_by_one_attribute
+ assert_equal topics(:first), Topic.find_by_title("The First Topic")
+ assert_nil Topic.find_by_title("The First Topic!")
+ end
+
+ def test_find_by_one_attribute_caches_dynamic_finder
+ # ensure this test can run independently of order
+ class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.public_methods.any? { |m| m.to_s == 'find_by_title' }
+ assert !Topic.public_methods.any? { |m| m.to_s == 'find_by_title' }
+ t = Topic.find_by_title("The First Topic")
+ assert Topic.public_methods.any? { |m| m.to_s == 'find_by_title' }
+ end
+
+ def test_dynamic_finder_returns_same_results_after_caching
+ # ensure this test can run independently of order
+ class << Topic; self; end.send(:remove_method, :find_by_title) if Topic.public_method_defined?(:find_by_title)
+ t = Topic.find_by_title("The First Topic")
+ assert_equal t, Topic.find_by_title("The First Topic") # find_by_title has been cached
+ end
+
+ def test_find_by_one_attribute_with_order_option
+ assert_equal accounts(:signals37), Account.find_by_credit_limit(50, :order => 'id')
+ assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :order => 'id DESC')
+ end
+
+ def test_find_by_one_attribute_with_conditions
+ assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
+ end
+
+ def test_find_by_one_attribute_that_is_an_aggregate
+ address = customers(:david).address
+ assert_kind_of Address, address
+ found_customer = Customer.find_by_address(address)
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference
+ address = customers(:david).address
+ assert_kind_of Address, address
+ missing_address = Address.new(address.street, address.city, address.country + "1")
+ assert_nil Customer.find_by_address(missing_address)
+ missing_address = Address.new(address.street, address.city + "1", address.country)
+ assert_nil Customer.find_by_address(missing_address)
+ missing_address = Address.new(address.street + "1", address.city, address.country)
+ assert_nil Customer.find_by_address(missing_address)
+ end
+
+ def test_find_by_two_attributes_that_are_both_aggregates
+ balance = customers(:david).balance
+ address = customers(:david).address
+ assert_kind_of Money, balance
+ assert_kind_of Address, address
+ found_customer = Customer.find_by_balance_and_address(balance, address)
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_find_by_two_attributes_with_one_being_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name)
+ assert_equal customers(:david), found_customer
+ end
+
+ def test_dynamic_finder_on_one_attribute_with_conditions_caches_method
+ # ensure this test can run independently of order
+ class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
+ assert !Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
+ a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
+ assert Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
+ end
+
+ def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching
+ # ensure this test can run independently of order
+ class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.any? { |m| m.to_s == 'find_by_credit_limit' }
+ a = Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6])
+ assert_equal a, Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) # find_by_credit_limit has been cached
+ end
+
+ def test_find_by_one_attribute_with_several_options
+ assert_equal accounts(:unknown), Account.find_by_credit_limit(50, :order => 'id DESC', :conditions => ['id != ?', 3])
+ end
+
+ def test_find_by_one_missing_attribute
+ assert_raises(NoMethodError) { Topic.find_by_undertitle("The First Topic!") }
+ end
+
+ def test_find_by_invalid_method_syntax
+ assert_raises(NoMethodError) { Topic.fail_to_find_by_title("The First Topic") }
+ assert_raises(NoMethodError) { Topic.find_by_title?("The First Topic") }
+ assert_raises(NoMethodError) { Topic.fail_to_find_or_create_by_title("Nonexistent Title") }
+ assert_raises(NoMethodError) { Topic.find_or_create_by_title?("Nonexistent Title") }
+ end
+
+ def test_find_by_two_attributes
+ assert_equal topics(:first), Topic.find_by_title_and_author_name("The First Topic", "David")
+ assert_nil Topic.find_by_title_and_author_name("The First Topic", "Mary")
+ end
+
+ def test_find_all_by_one_attribute
+ topics = Topic.find_all_by_content("Have a nice day")
+ assert_equal 2, topics.size
+ assert topics.include?(topics(:first))
+
+ assert_equal [], Topic.find_all_by_title("The First Topic!!")
+ end
+
+ def test_find_all_by_one_attribute_that_is_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance(balance)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_that_are_both_aggregates
+ balance = customers(:david).balance
+ address = customers(:david).address
+ assert_kind_of Money, balance
+ assert_kind_of Address, address
+ found_customers = Customer.find_all_by_balance_and_address(balance, address)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_two_attributes_with_one_being_an_aggregate
+ balance = customers(:david).balance
+ assert_kind_of Money, balance
+ found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name)
+ assert_equal 1, found_customers.size
+ assert_equal customers(:david), found_customers.first
+ end
+
+ def test_find_all_by_one_attribute_with_options
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC")
+ assert topics(:first), topics.last
+
+ topics = Topic.find_all_by_content("Have a nice day", :order => "id")
+ assert topics(:first), topics.first
+ end
+
+ def test_find_all_by_array_attribute
+ assert_equal 2, Topic.find_all_by_title(["The First Topic", "The Second Topic of the day"]).size
+ end
+
+ def test_find_all_by_boolean_attribute
+ topics = Topic.find_all_by_approved(false)
+ assert_equal 1, topics.size
+ assert topics.include?(topics(:first))
+
+ topics = Topic.find_all_by_approved(true)
+ assert_equal 3, topics.size
+ assert topics.include?(topics(:second))
+ end
+
+ def test_find_by_nil_attribute
+ topic = Topic.find_by_last_read nil
+ assert_not_nil topic
+ assert_nil topic.last_read
+ end
+
+ def test_find_all_by_nil_attribute
+ topics = Topic.find_all_by_last_read nil
+ assert_equal 3, topics.size
+ assert topics.collect(&:last_read).all?(&:nil?)
+ end
+
+ def test_find_by_nil_and_not_nil_attributes
+ topic = Topic.find_by_last_read_and_author_name nil, "Mary"
+ assert_equal "Mary", topic.author_name
+ end
+
+ def test_find_all_by_nil_and_not_nil_attributes
+ topics = Topic.find_all_by_last_read_and_author_name nil, "Mary"
+ assert_equal 1, topics.size
+ assert_equal "Mary", topics[0].author_name
+ end
+
+ def test_find_or_create_from_one_attribute
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name("38signals")
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name("38signals")
+ assert !sig38.new_record?
+ end
+
+ def test_find_or_create_from_two_attributes
+ number_of_topics = Topic.count
+ another = Topic.find_or_create_by_title_and_author_name("Another topic","John")
+ assert_equal number_of_topics + 1, Topic.count
+ assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John")
+ assert !another.new_record?
+ end
+
+ def test_find_or_create_from_two_attributes_with_one_being_an_aggregate
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth")
+ assert !created_customer.new_record?
+ end
+
+ def test_find_or_create_from_one_attribute_and_hash
+ number_of_companies = Company.count
+ sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal number_of_companies + 1, Company.count
+ assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert !sig38.new_record?
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute
+ number_of_customers = Customer.count
+ created_customer = Customer.find_or_create_by_balance(Money.new(123))
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123))
+ assert !created_customer.new_record?
+ end
+
+ def test_find_or_create_from_one_aggregate_attribute_and_hash
+ number_of_customers = Customer.count
+ balance = Money.new(123)
+ name = "Elizabeth"
+ created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert_equal number_of_customers + 1, Customer.count
+ assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name})
+ assert !created_customer.new_record?
+ assert_equal balance, created_customer.balance
+ assert_equal name, created_customer.name
+ end
+
+ def test_find_or_initialize_from_one_attribute
+ sig38 = Company.find_or_initialize_by_name("38signals")
+ assert_equal "38signals", sig38.name
+ assert sig38.new_record?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute
+ new_customer = Customer.find_or_initialize_by_balance(Money.new(123))
+ assert_equal 123, new_customer.balance.amount
+ assert new_customer.new_record?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert c.new_record?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_not_attribute_even_when_protected
+ c = Company.find_or_create_by_name({:name => "Fortune 1000", :rating => 1000})
+ assert_equal "Fortune 1000", c.name
+ assert_not_equal 1000, c.rating
+ assert c.valid?
+ assert !c.new_record?
+ end
+
+ def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert c.new_record?
+ end
+
+ def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
+ c = Company.find_or_create_by_name_and_rating("Fortune 1000", 1000)
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000, c.rating
+ assert c.valid?
+ assert !c.new_record?
+ end
+
+ def test_find_or_initialize_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_initialize_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert c.new_record?
+ end
+
+ def test_find_or_create_should_set_protected_attributes_if_given_as_block
+ c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 }
+ assert_equal "Fortune 1000", c.name
+ assert_equal 1000.to_f, c.rating.to_f
+ assert c.valid?
+ assert !c.new_record?
+ end
+
+ def test_dynamic_find_or_initialize_from_one_attribute_caches_method
+ class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' }
+ assert !Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' }
+ sig38 = Company.find_or_initialize_by_name("38signals")
+ assert Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' }
+ end
+
+ def test_find_or_initialize_from_two_attributes
+ another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John")
+ assert_equal "Another topic", another.title
+ assert_equal "John", another.author_name
+ assert another.new_record?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_one_not
+ new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth")
+ assert_equal 123, new_customer.balance.amount
+ assert_equal "Elizabeth", new_customer.name
+ assert new_customer.new_record?
+ end
+
+ def test_find_or_initialize_from_one_attribute_and_hash
+ sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23})
+ assert_equal "38signals", sig38.name
+ assert_equal 17, sig38.firm_id
+ assert_equal 23, sig38.client_of
+ assert sig38.new_record?
+ end
+
+ def test_find_or_initialize_from_one_aggregate_attribute_and_hash
+ balance = Money.new(123)
+ name = "Elizabeth"
+ new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name})
+ assert_equal balance, new_customer.balance
+ assert_equal name, new_customer.name
+ assert new_customer.new_record?
+ end
+
+ def test_find_with_bad_sql
+ assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" }
+ end
+
+ def test_find_with_invalid_params
+ assert_raises(ArgumentError) { Topic.find :first, :join => "It should be `joins'" }
+ assert_raises(ArgumentError) { Topic.find :first, :conditions => '1 = 1', :join => "It should be `joins'" }
+ end
+
+ def test_dynamic_finder_with_invalid_params
+ assert_raises(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" }
+ end
+
+ def test_find_all_with_limit
+ first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5
+ assert_equal 5, first_five_developers.length
+ assert_equal 'David', first_five_developers.first.name
+ assert_equal 'fixture_5', first_five_developers.last.name
+
+ no_developers = Developer.find :all, :order => 'id ASC', :limit => 0
+ assert_equal 0, no_developers.length
+ end
+
+ def test_find_all_with_limit_and_offset
+ first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0
+ second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3
+ last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8
+
+ assert_equal 3, first_three_developers.length
+ assert_equal 3, second_three_developers.length
+ assert_equal 2, last_two_developers.length
+
+ assert_equal 'David', first_three_developers.first.name
+ assert_equal 'fixture_4', second_three_developers.first.name
+ assert_equal 'fixture_9', last_two_developers.first.name
+ end
+
+ def test_find_all_with_limit_and_offset_and_multiple_order_clauses
+ first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0
+ second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3
+ last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6
+
+ assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] }
+ assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] }
+ end
+
+ def test_find_all_with_join
+ developers_on_project_one = Developer.find(
+ :all,
+ :joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
+ :conditions => 'project_id=1'
+ )
+ assert_equal 3, developers_on_project_one.length
+ developer_names = developers_on_project_one.map { |d| d.name }
+ assert developer_names.include?('David')
+ assert developer_names.include?('Jamis')
+ end
+
+ def test_joins_dont_clobber_id
+ first = Firm.find(
+ :first,
+ :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id',
+ :conditions => 'companies.id = 1'
+ )
+ assert_equal 1, first.id
+ end
+
+ def test_find_by_id_with_conditions_with_or
+ assert_nothing_raised do
+ Post.find([1,2,3],
+ :conditions => "posts.id <= 3 OR posts.#{QUOTED_TYPE} = 'Post'")
+ end
+ end
+
+ # http://dev.rubyonrails.org/ticket/6778
+ def test_find_ignores_previously_inserted_record
+ post = Post.create!(:title => 'test', :body => 'it out')
+ assert_equal [], Post.find_all_by_id(nil)
+ end
+
+ def test_find_by_empty_ids
+ assert_equal [], Post.find([])
+ end
+
+ def test_find_by_empty_in_condition
+ assert_equal [], Post.find(:all, :conditions => ['id in (?)', []])
+ end
+
+ def test_find_by_records
+ p1, p2 = Post.find(:all, :limit => 2, :order => 'id asc')
+ assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2]], :order => 'id asc')
+ assert_equal [p1, p2], Post.find(:all, :conditions => ['id in (?)', [p1, p2.id]], :order => 'id asc')
+ end
+
+ def test_select_value
+ assert_equal "37signals", Company.connection.select_value("SELECT name FROM companies WHERE id = 1")
+ assert_nil Company.connection.select_value("SELECT name FROM companies WHERE id = -1")
+ # make sure we didn't break count...
+ assert_equal 0, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = 'Halliburton'")
+ assert_equal 1, Company.count_by_sql("SELECT COUNT(*) FROM companies WHERE name = '37signals'")
+ end
+
+ def test_select_values
+ assert_equal ["1","2","3","4","5","6","7","8","9"], Company.connection.select_values("SELECT id FROM companies ORDER BY id").map! { |i| i.to_s }
+ assert_equal ["37signals","Summit","Microsoft", "Flamboyant Software", "Ex Nihilo", "RailsCore", "Leetsoft", "Jadedpixel", "Odegy"], Company.connection.select_values("SELECT name FROM companies ORDER BY id")
+ end
+
+ def test_select_rows
+ assert_equal(
+ [["1", nil, nil, "37signals"],
+ ["2", "1", "2", "Summit"],
+ ["3", "1", "1", "Microsoft"]],
+ Company.connection.select_rows("SELECT id, firm_id, client_of, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}})
+ assert_equal [["1", "37signals"], ["2", "Summit"], ["3", "Microsoft"]],
+ Company.connection.select_rows("SELECT id, name FROM companies WHERE id IN (1,2,3) ORDER BY id").map! {|i| i.map! {|j| j.to_s unless j.nil?}}
+ end
+
+ def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct
+ assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size
+
+ assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address},
+ :order => ' author_addresses_authors.id DESC ', :limit => 3).size
+ end
+
+ def test_with_limiting_with_custom_select
+ posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3, :order => 'posts.id')
+ assert_equal 3, posts.size
+ assert_equal [0, 1, 1], posts.map(&:author_id).sort
+ end
+
+ protected
+ def bind(statement, *vars)
+ if vars.first.is_a?(Hash)
+ ActiveRecord::Base.send(:replace_named_bind_variables, statement, vars.first)
+ else
+ ActiveRecord::Base.send(:replace_bind_variables, statement, vars)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/fixtures_test.rb b/vendor/rails/activerecord/test/cases/fixtures_test.rb
new file mode 100644
index 0000000..aca7cfb
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/fixtures_test.rb
@@ -0,0 +1,626 @@
+require "cases/helper"
+require 'models/post'
+require 'models/binary'
+require 'models/topic'
+require 'models/computer'
+require 'models/developer'
+require 'models/company'
+require 'models/task'
+require 'models/reply'
+require 'models/joke'
+require 'models/course'
+require 'models/category'
+require 'models/parrot'
+require 'models/pirate'
+require 'models/treasure'
+require 'models/matey'
+require 'models/ship'
+
+class FixturesTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = false
+
+ fixtures :topics, :developers, :accounts, :tasks, :categories, :funny_jokes, :binaries
+
+ FIXTURES = %w( accounts binaries companies customers
+ developers developers_projects entrants
+ movies projects subscribers topics tasks )
+ MATCH_ATTRIBUTE_NAME = /[a-zA-Z][-_\w]*/
+
+ def test_clean_fixtures
+ FIXTURES.each do |name|
+ fixtures = nil
+ assert_nothing_raised { fixtures = create_fixtures(name) }
+ assert_kind_of(Fixtures, fixtures)
+ fixtures.each { |name, fixture|
+ fixture.each { |key, value|
+ assert_match(MATCH_ATTRIBUTE_NAME, key)
+ }
+ }
+ end
+ end
+
+ def test_multiple_clean_fixtures
+ fixtures_array = nil
+ assert_nothing_raised { fixtures_array = create_fixtures(*FIXTURES) }
+ assert_kind_of(Array, fixtures_array)
+ fixtures_array.each { |fixtures| assert_kind_of(Fixtures, fixtures) }
+ end
+
+ def test_attributes
+ topics = create_fixtures("topics")
+ assert_equal("The First Topic", topics["first"]["title"])
+ assert_nil(topics["second"]["author_email_address"])
+ end
+
+ def test_inserts
+ topics = create_fixtures("topics")
+ first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'David'")
+ assert_equal("The First Topic", first_row["title"])
+
+ second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM topics WHERE author_name = 'Mary'")
+ assert_nil(second_row["author_email_address"])
+ end
+
+ if ActiveRecord::Base.connection.supports_migrations?
+ def test_inserts_with_pre_and_suffix
+ # Reset cache to make finds on the new table work
+ Fixtures.reset_cache
+
+ ActiveRecord::Base.connection.create_table :prefix_topics_suffix do |t|
+ t.column :title, :string
+ t.column :author_name, :string
+ t.column :author_email_address, :string
+ t.column :written_on, :datetime
+ t.column :bonus_time, :time
+ t.column :last_read, :date
+ t.column :content, :string
+ t.column :approved, :boolean, :default => true
+ t.column :replies_count, :integer, :default => 0
+ t.column :parent_id, :integer
+ t.column :type, :string, :limit => 50
+ end
+
+ # Store existing prefix/suffix
+ old_prefix = ActiveRecord::Base.table_name_prefix
+ old_suffix = ActiveRecord::Base.table_name_suffix
+
+ # Set a prefix/suffix we can test against
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
+ ActiveRecord::Base.table_name_suffix = '_suffix'
+
+ topics = create_fixtures("topics")
+
+ first_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'David'")
+ assert_equal("The First Topic", first_row["title"])
+
+ second_row = ActiveRecord::Base.connection.select_one("SELECT * FROM prefix_topics_suffix WHERE author_name = 'Mary'")
+ assert_nil(second_row["author_email_address"])
+
+ # This checks for a caching problem which causes a bug in the fixtures
+ # class-level configuration helper.
+ assert_not_nil topics, "Fixture data inserted, but fixture objects not returned from create"
+ ensure
+ # Restore prefix/suffix to its previous values
+ ActiveRecord::Base.table_name_prefix = old_prefix
+ ActiveRecord::Base.table_name_suffix = old_suffix
+
+ ActiveRecord::Base.connection.drop_table :prefix_topics_suffix rescue nil
+ end
+ end
+
+ def test_insert_with_datetime
+ topics = create_fixtures("tasks")
+ first = Task.find(1)
+ assert first
+ end
+
+ def test_logger_level_invariant
+ level = ActiveRecord::Base.logger.level
+ create_fixtures('topics')
+ assert_equal level, ActiveRecord::Base.logger.level
+ end
+
+ def test_instantiation
+ topics = create_fixtures("topics")
+ assert_kind_of Topic, topics["first"].find
+ end
+
+ def test_complete_instantiation
+ assert_equal 4, @topics.size
+ assert_equal "The First Topic", @first.title
+ end
+
+ def test_fixtures_from_root_yml_with_instantiation
+ # assert_equal 2, @accounts.size
+ assert_equal 50, @unknown.credit_limit
+ end
+
+ def test_erb_in_fixtures
+ assert_equal 11, @developers.size
+ assert_equal "fixture_5", @dev_5.name
+ end
+
+ def test_empty_yaml_fixture
+ assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts")
+ end
+
+ def test_empty_yaml_fixture_with_a_comment_in_it
+ assert_not_nil Fixtures.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies")
+ end
+
+ def test_dirty_dirty_yaml_file
+ assert_raises(Fixture::FormatError) do
+ Fixtures.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses")
+ end
+ end
+
+ def test_empty_csv_fixtures
+ assert_not_nil Fixtures.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/csv/accounts")
+ end
+
+ def test_omap_fixtures
+ assert_nothing_raised do
+ fixtures = Fixtures.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered")
+
+ i = 0
+ fixtures.each do |name, fixture|
+ assert_equal "fixture_no_#{i}", name
+ assert_equal "Category #{i}", fixture['name']
+ i += 1
+ end
+ end
+ end
+
+ def test_yml_file_in_subdirectory
+ assert_equal(categories(:sub_special_1).name, "A special category in a subdir file")
+ assert_equal(categories(:sub_special_1).class, SpecialCategory)
+ end
+
+ def test_subsubdir_file_with_arbitrary_name
+ assert_equal(categories(:sub_special_3).name, "A special category in an arbitrarily named subsubdir file")
+ assert_equal(categories(:sub_special_3).class, SpecialCategory)
+ end
+
+ def test_binary_in_fixtures
+ assert_equal 1, @binaries.size
+ data = File.read(ASSETS_ROOT + "/flowers.jpg")
+ data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding)
+ data.freeze
+ assert_equal data, @flowers.data
+ end
+end
+
+if Account.connection.respond_to?(:reset_pk_sequence!)
+ class FixturesResetPkSequenceTest < ActiveRecord::TestCase
+ fixtures :accounts
+ fixtures :companies
+
+ def setup
+ @instances = [Account.new(:credit_limit => 50), Company.new(:name => 'RoR Consulting')]
+ Fixtures.reset_cache # make sure tables get reinitialized
+ end
+
+ def test_resets_to_min_pk_with_specified_pk_and_sequence
+ @instances.each do |instance|
+ model = instance.class
+ model.delete_all
+ model.connection.reset_pk_sequence!(model.table_name, model.primary_key, model.sequence_name)
+
+ instance.save!
+ assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
+ end
+ end
+
+ def test_resets_to_min_pk_with_default_pk_and_sequence
+ @instances.each do |instance|
+ model = instance.class
+ model.delete_all
+ model.connection.reset_pk_sequence!(model.table_name)
+
+ instance.save!
+ assert_equal 1, instance.id, "Sequence reset for #{model.table_name} failed."
+ end
+ end
+
+ def test_create_fixtures_resets_sequences_when_not_cached
+ @instances.each do |instance|
+ max_id = create_fixtures(instance.class.table_name).inject(0) do |max_id, (name, fixture)|
+ fixture_id = fixture['id'].to_i
+ fixture_id > max_id ? fixture_id : max_id
+ end
+
+ # Clone the last fixture to check that it gets the next greatest id.
+ instance.save!
+ assert_equal max_id + 1, instance.id, "Sequence reset for #{instance.class.table_name} failed."
+ end
+ end
+ end
+end
+
+class FixturesWithoutInstantiationTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = false
+ fixtures :topics, :developers, :accounts
+
+ def test_without_complete_instantiation
+ assert_nil @first
+ assert_nil @topics
+ assert_nil @developers
+ assert_nil @accounts
+ end
+
+ def test_fixtures_from_root_yml_without_instantiation
+ assert_nil @unknown
+ end
+
+ def test_accessor_methods
+ assert_equal "The First Topic", topics(:first).title
+ assert_equal "Jamis", developers(:jamis).name
+ assert_equal 50, accounts(:signals37).credit_limit
+ end
+
+ def test_accessor_methods_with_multiple_args
+ assert_equal 2, topics(:first, :second).size
+ assert_raise(StandardError) { topics([:first, :second]) }
+ end
+
+ uses_mocha 'reloading_fixtures_through_accessor_methods' do
+ def test_reloading_fixtures_through_accessor_methods
+ assert_equal "The First Topic", topics(:first).title
+ @loaded_fixtures['topics']['first'].expects(:find).returns(stub(:title => "Fresh Topic!"))
+ assert_equal "Fresh Topic!", topics(:first, true).title
+ end
+ end
+end
+
+class FixturesWithoutInstanceInstantiationTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_instantiated_fixtures = :no_instances
+
+ fixtures :topics, :developers, :accounts
+
+ def test_without_instance_instantiation
+ assert_nil @first
+ assert_not_nil @topics
+ assert_not_nil @developers
+ assert_not_nil @accounts
+ end
+end
+
+class TransactionalFixturesTest < ActiveRecord::TestCase
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = true
+
+ fixtures :topics
+
+ def test_destroy
+ assert_not_nil @first
+ @first.destroy
+ end
+
+ def test_destroy_just_kidding
+ assert_not_nil @first
+ end
+end
+
+class MultipleFixturesTest < ActiveRecord::TestCase
+ fixtures :topics
+ fixtures :developers, :accounts
+
+ def test_fixture_table_names
+ assert_equal %w(topics developers accounts), fixture_table_names
+ end
+end
+
+class SetupTest < ActiveRecord::TestCase
+ # fixtures :topics
+
+ def setup
+ @first = true
+ end
+
+ def test_nothing
+ end
+end
+
+class SetupSubclassTest < SetupTest
+ def setup
+ super
+ @second = true
+ end
+
+ def test_subclassing_should_preserve_setups
+ assert @first
+ assert @second
+ end
+end
+
+
+class OverlappingFixturesTest < ActiveRecord::TestCase
+ fixtures :topics, :developers
+ fixtures :developers, :accounts
+
+ def test_fixture_table_names
+ assert_equal %w(topics developers accounts), fixture_table_names
+ end
+end
+
+class ForeignKeyFixturesTest < ActiveRecord::TestCase
+ fixtures :fk_test_has_pk, :fk_test_has_fk
+
+ # if foreign keys are implemented and fixtures
+ # are not deleted in reverse order then this test
+ # case will raise StatementInvalid
+
+ def test_number1
+ assert true
+ end
+
+ def test_number2
+ assert true
+ end
+end
+
+class CheckSetTableNameFixturesTest < ActiveRecord::TestCase
+ set_fixture_class :funny_jokes => 'Joke'
+ fixtures :funny_jokes
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_table_method
+ assert_kind_of Joke, funny_jokes(:a_joke)
+ end
+end
+
+class CustomConnectionFixturesTest < ActiveRecord::TestCase
+ set_fixture_class :courses => Course
+ fixtures :courses
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_connection
+ assert_kind_of Course, courses(:ruby)
+ assert_equal Course.connection, courses(:ruby).connection
+ end
+end
+
+class InvalidTableNameFixturesTest < ActiveRecord::TestCase
+ fixtures :funny_jokes
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our lack of set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_raises_error
+ assert_raises FixtureClassNotFound do
+ funny_jokes(:a_joke)
+ end
+ end
+end
+
+class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase
+ set_fixture_class :funny_jokes => 'Joke'
+ fixtures :funny_jokes
+ # Set to false to blow away fixtures cache and ensure our fixtures are loaded
+ # and thus takes into account our set_fixture_class
+ self.use_transactional_fixtures = false
+
+ def test_proper_escaped_fixture
+ assert_equal "The \\n Aristocrats\nAte the candy\n", funny_jokes(:another_joke).name
+ end
+end
+
+class DevelopersProject; end
+class ManyToManyFixturesWithClassDefined < ActiveRecord::TestCase
+ fixtures :developers_projects
+
+ def test_this_should_run_cleanly
+ assert true
+ end
+end
+
+class FixturesBrokenRollbackTest < ActiveRecord::TestCase
+ def blank_setup; end
+ alias_method :ar_setup_fixtures, :setup_fixtures
+ alias_method :setup_fixtures, :blank_setup
+ alias_method :setup, :blank_setup
+
+ def blank_teardown; end
+ alias_method :ar_teardown_fixtures, :teardown_fixtures
+ alias_method :teardown_fixtures, :blank_teardown
+ alias_method :teardown, :blank_teardown
+
+ def test_no_rollback_in_teardown_unless_transaction_active
+ assert_equal 0, Thread.current['open_transactions']
+ assert_raise(RuntimeError) { ar_setup_fixtures }
+ assert_equal 0, Thread.current['open_transactions']
+ assert_nothing_raised { ar_teardown_fixtures }
+ assert_equal 0, Thread.current['open_transactions']
+ end
+
+ private
+ def load_fixtures
+ raise 'argh'
+ end
+end
+
+class LoadAllFixturesTest < ActiveRecord::TestCase
+ self.fixture_path = FIXTURES_ROOT + "/all"
+ fixtures :all
+
+ def test_all_there
+ assert_equal %w(developers people tasks), fixture_table_names.sort
+ end
+end
+
+class FasterFixturesTest < ActiveRecord::TestCase
+ fixtures :categories, :authors
+
+ def load_extra_fixture(name)
+ fixture = create_fixtures(name)
+ assert fixture.is_a?(Fixtures)
+ @loaded_fixtures[fixture.table_name] = fixture
+ end
+
+ def test_cache
+ assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'categories')
+ assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'authors')
+
+ assert_no_queries do
+ create_fixtures('categories')
+ create_fixtures('authors')
+ end
+
+ load_extra_fixture('posts')
+ assert Fixtures.fixture_is_cached?(ActiveRecord::Base.connection, 'posts')
+ self.class.setup_fixture_accessors('posts')
+ assert_equal 'Welcome to the weblog', posts(:welcome).title
+ end
+end
+
+class FoxyFixturesTest < ActiveRecord::TestCase
+ fixtures :parrots, :parrots_pirates, :pirates, :treasures, :mateys, :ships, :computers, :developers
+
+ def test_identifies_strings
+ assert_equal(Fixtures.identify("foo"), Fixtures.identify("foo"))
+ assert_not_equal(Fixtures.identify("foo"), Fixtures.identify("FOO"))
+ end
+
+ def test_identifies_symbols
+ assert_equal(Fixtures.identify(:foo), Fixtures.identify(:foo))
+ end
+
+ TIMESTAMP_COLUMNS = %w(created_at created_on updated_at updated_on)
+
+ def test_populates_timestamp_columns
+ TIMESTAMP_COLUMNS.each do |property|
+ assert_not_nil(parrots(:george).send(property), "should set #{property}")
+ end
+ end
+
+ def test_does_not_populate_timestamp_columns_if_model_has_set_record_timestamps_to_false
+ TIMESTAMP_COLUMNS.each do |property|
+ assert_nil(ships(:black_pearl).send(property), "should not set #{property}")
+ end
+ end
+
+ def test_populates_all_columns_with_the_same_time
+ last = nil
+
+ TIMESTAMP_COLUMNS.each do |property|
+ current = parrots(:george).send(property)
+ last ||= current
+
+ assert_equal(last, current)
+ last = current
+ end
+ end
+
+ def test_only_populates_columns_that_exist
+ assert_not_nil(pirates(:blackbeard).created_on)
+ assert_not_nil(pirates(:blackbeard).updated_on)
+ end
+
+ def test_preserves_existing_fixture_data
+ assert_equal(2.weeks.ago.to_date, pirates(:redbeard).created_on.to_date)
+ assert_equal(2.weeks.ago.to_date, pirates(:redbeard).updated_on.to_date)
+ end
+
+ def test_generates_unique_ids
+ assert_not_nil(parrots(:george).id)
+ assert_not_equal(parrots(:george).id, parrots(:louis).id)
+ end
+
+ def test_automatically_sets_primary_key
+ assert_not_nil(ships(:black_pearl))
+ end
+
+ def test_preserves_existing_primary_key
+ assert_equal(2, ships(:interceptor).id)
+ end
+
+ def test_resolves_belongs_to_symbols
+ assert_equal(parrots(:george), pirates(:blackbeard).parrot)
+ end
+
+ def test_ignores_belongs_to_symbols_if_association_and_foreign_key_are_named_the_same
+ assert_equal(developers(:david), computers(:workstation).developer)
+ end
+
+ def test_supports_join_tables
+ assert(pirates(:blackbeard).parrots.include?(parrots(:george)))
+ assert(pirates(:blackbeard).parrots.include?(parrots(:louis)))
+ assert(parrots(:george).pirates.include?(pirates(:blackbeard)))
+ end
+
+ def test_supports_inline_habtm
+ assert(parrots(:george).treasures.include?(treasures(:diamond)))
+ assert(parrots(:george).treasures.include?(treasures(:sapphire)))
+ assert(!parrots(:george).treasures.include?(treasures(:ruby)))
+ end
+
+ def test_supports_inline_habtm_with_specified_id
+ assert(parrots(:polly).treasures.include?(treasures(:ruby)))
+ assert(parrots(:polly).treasures.include?(treasures(:sapphire)))
+ assert(!parrots(:polly).treasures.include?(treasures(:diamond)))
+ end
+
+ def test_supports_yaml_arrays
+ assert(parrots(:louis).treasures.include?(treasures(:diamond)))
+ assert(parrots(:louis).treasures.include?(treasures(:sapphire)))
+ end
+
+ def test_strips_DEFAULTS_key
+ assert_raise(StandardError) { parrots(:DEFAULTS) }
+
+ # this lets us do YAML defaults and not have an extra fixture entry
+ %w(sapphire ruby).each { |t| assert(parrots(:davey).treasures.include?(treasures(t))) }
+ end
+
+ def test_supports_label_interpolation
+ assert_equal("frederick", parrots(:frederick).name)
+ end
+
+ def test_supports_polymorphic_belongs_to
+ assert_equal(pirates(:redbeard), treasures(:sapphire).looter)
+ assert_equal(parrots(:louis), treasures(:ruby).looter)
+ end
+
+ def test_only_generates_a_pk_if_necessary
+ m = Matey.find(:first)
+ m.pirate = pirates(:blackbeard)
+ m.target = pirates(:redbeard)
+ end
+
+ def test_supports_sti
+ assert_kind_of DeadParrot, parrots(:polly)
+ assert_equal pirates(:blackbeard), parrots(:polly).killer
+ end
+end
+
+class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase
+ fixtures :parrots
+
+ # This seemingly useless assertion catches a bug that caused the fixtures
+ # setup code call nil[]
+ def test_foo
+ assert_equal parrots(:louis), Parrot.find_by_name("King Louis")
+ end
+end
+
+class FixtureLoadingTest < ActiveRecord::TestCase
+ uses_mocha 'reloading_fixtures_through_accessor_methods' do
+ def test_logs_message_for_failed_dependency_load
+ Test::Unit::TestCase.expects(:require_dependency).with(:does_not_exist).raises(LoadError)
+ ActiveRecord::Base.logger.expects(:warn)
+ Test::Unit::TestCase.try_to_load_dependency(:does_not_exist)
+ end
+
+ def test_does_not_logs_message_for_successful_dependency_load
+ Test::Unit::TestCase.expects(:require_dependency).with(:works_out_fine)
+ ActiveRecord::Base.logger.expects(:warn).never
+ Test::Unit::TestCase.try_to_load_dependency(:works_out_fine)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/helper.rb b/vendor/rails/activerecord/test/cases/helper.rb
new file mode 100644
index 0000000..dc83300
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/helper.rb
@@ -0,0 +1,47 @@
+$:.unshift(File.dirname(__FILE__) + '/../../lib')
+
+require 'config'
+require 'test/unit'
+
+require 'active_record'
+require 'active_record/fixtures'
+require 'active_record/test_case'
+require 'connection'
+
+# Show backtraces for deprecated behavior for quicker cleanup.
+ActiveSupport::Deprecation.debug = true
+
+# Quote "type" if it's a reserved word for the current connection.
+QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name('type')
+
+def current_adapter?(*types)
+ types.any? do |type|
+ ActiveRecord::ConnectionAdapters.const_defined?(type) &&
+ ActiveRecord::Base.connection.is_a?(ActiveRecord::ConnectionAdapters.const_get(type))
+ end
+end
+
+def uses_mocha(description)
+ require 'rubygems'
+ require 'mocha'
+ yield
+rescue LoadError
+ $stderr.puts "Skipping #{description} tests. `gem install mocha` and try again."
+end
+
+ActiveRecord::Base.connection.class.class_eval do
+ IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/]
+
+ def execute_with_counting(sql, name = nil, &block)
+ $query_count ||= 0
+ $query_count += 1 unless IGNORED_SQL.any? { |r| sql =~ r }
+ execute_without_counting(sql, name, &block)
+ end
+
+ alias_method_chain :execute, :counting
+end
+
+# Make with_scope public for tests
+class << ActiveRecord::Base
+ public :with_scope, :with_exclusive_scope
+end
diff --git a/vendor/rails/activerecord/test/cases/inheritance_test.rb b/vendor/rails/activerecord/test/cases/inheritance_test.rb
new file mode 100644
index 0000000..f09b617
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/inheritance_test.rb
@@ -0,0 +1,255 @@
+require "cases/helper"
+require 'models/company'
+require 'models/project'
+require 'models/subscriber'
+
+class InheritanceTest < ActiveRecord::TestCase
+ fixtures :companies, :projects, :subscribers, :accounts
+
+ def test_class_with_store_full_sti_class_returns_full_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+ assert_equal 'Namespaced::Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_class_without_store_full_sti_class_returns_demodulized_name
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = false
+ assert_equal 'Company', Namespaced::Company.sti_name
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = false
+ item = Namespaced::Company.new
+ assert_equal 'Company', item[:type]
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_should_store_full_class_name_with_store_full_sti_class_option_enabled
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+ item = Namespaced::Company.new
+ assert_equal 'Namespaced::Company', item[:type]
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option
+ old = ActiveRecord::Base.store_full_sti_class
+ ActiveRecord::Base.store_full_sti_class = true
+ item = Namespaced::Company.create :name => "Wolverine 2"
+ assert_not_nil Company.find(item.id)
+ assert_not_nil Namespaced::Company.find(item.id)
+ ensure
+ ActiveRecord::Base.store_full_sti_class = old
+ end
+
+ def test_company_descends_from_active_record
+ assert_raise(NoMethodError) { ActiveRecord::Base.descends_from_active_record? }
+ assert AbstractCompany.descends_from_active_record?, 'AbstractCompany should descend from ActiveRecord::Base'
+ assert Company.descends_from_active_record?, 'Company should descend from ActiveRecord::Base'
+ assert !Class.new(Company).descends_from_active_record?, 'Company subclass should not descend from ActiveRecord::Base'
+ end
+
+ def test_a_bad_type_column
+ #SQLServer need to turn Identity Insert On before manually inserting into the Identity column
+ if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
+ Company.connection.execute "SET IDENTITY_INSERT companies ON"
+ end
+ Company.connection.insert "INSERT INTO companies (id, #{QUOTED_TYPE}, name) VALUES(100, 'bad_class!', 'Not happening')"
+
+ #We then need to turn it back Off before continuing.
+ if current_adapter?(:SQLServerAdapter, :SybaseAdapter)
+ Company.connection.execute "SET IDENTITY_INSERT companies OFF"
+ end
+ assert_raises(ActiveRecord::SubclassNotFound) { Company.find(100) }
+ end
+
+ def test_inheritance_find
+ assert Company.find(1).kind_of?(Firm), "37signals should be a firm"
+ assert Firm.find(1).kind_of?(Firm), "37signals should be a firm"
+ assert Company.find(2).kind_of?(Client), "Summit should be a client"
+ assert Client.find(2).kind_of?(Client), "Summit should be a client"
+ end
+
+ def test_alt_inheritance_find
+ switch_to_alt_inheritance_column
+ test_inheritance_find
+ switch_to_default_inheritance_column
+ end
+
+ def test_inheritance_find_all
+ companies = Company.find(:all, :order => 'id')
+ assert companies[0].kind_of?(Firm), "37signals should be a firm"
+ assert companies[1].kind_of?(Client), "Summit should be a client"
+ end
+
+ def test_alt_inheritance_find_all
+ switch_to_alt_inheritance_column
+ test_inheritance_find_all
+ switch_to_default_inheritance_column
+ end
+
+ def test_inheritance_save
+ firm = Firm.new
+ firm.name = "Next Angle"
+ firm.save
+
+ next_angle = Company.find(firm.id)
+ assert next_angle.kind_of?(Firm), "Next Angle should be a firm"
+ end
+
+ def test_alt_inheritance_save
+ switch_to_alt_inheritance_column
+ test_inheritance_save
+ switch_to_default_inheritance_column
+ end
+
+ def test_inheritance_condition
+ assert_equal 9, Company.count
+ assert_equal 2, Firm.count
+ assert_equal 3, Client.count
+ end
+
+ def test_alt_inheritance_condition
+ switch_to_alt_inheritance_column
+ test_inheritance_condition
+ switch_to_default_inheritance_column
+ end
+
+ def test_finding_incorrect_type_data
+ assert_raises(ActiveRecord::RecordNotFound) { Firm.find(2) }
+ assert_nothing_raised { Firm.find(1) }
+ end
+
+ def test_alt_finding_incorrect_type_data
+ switch_to_alt_inheritance_column
+ test_finding_incorrect_type_data
+ switch_to_default_inheritance_column
+ end
+
+ def test_update_all_within_inheritance
+ Client.update_all "name = 'I am a client'"
+ assert_equal "I am a client", Client.find(:all).first.name
+ assert_equal "37signals", Firm.find(:all).first.name
+ end
+
+ def test_alt_update_all_within_inheritance
+ switch_to_alt_inheritance_column
+ test_update_all_within_inheritance
+ switch_to_default_inheritance_column
+ end
+
+ def test_destroy_all_within_inheritance
+ Client.destroy_all
+ assert_equal 0, Client.count
+ assert_equal 2, Firm.count
+ end
+
+ def test_alt_destroy_all_within_inheritance
+ switch_to_alt_inheritance_column
+ test_destroy_all_within_inheritance
+ switch_to_default_inheritance_column
+ end
+
+ def test_find_first_within_inheritance
+ assert_kind_of Firm, Company.find(:first, :conditions => "name = '37signals'")
+ assert_kind_of Firm, Firm.find(:first, :conditions => "name = '37signals'")
+ assert_nil Client.find(:first, :conditions => "name = '37signals'")
+ end
+
+ def test_alt_find_first_within_inheritance
+ switch_to_alt_inheritance_column
+ test_find_first_within_inheritance
+ switch_to_default_inheritance_column
+ end
+
+ def test_complex_inheritance
+ very_special_client = VerySpecialClient.create("name" => "veryspecial")
+ assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
+ assert_equal 1, Client.find(:all, :conditions => "name = 'Summit'").size
+ assert_equal very_special_client, Client.find(very_special_client.id)
+ end
+
+ def test_alt_complex_inheritance
+ switch_to_alt_inheritance_column
+ test_complex_inheritance
+ switch_to_default_inheritance_column
+ end
+
+ 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"
+ end
+
+ def test_alt_eager_loading
+ switch_to_alt_inheritance_column
+ test_eager_load_belongs_to_something_inherited
+ switch_to_default_inheritance_column
+ end
+
+ def test_inheritance_without_mapping
+ assert_kind_of SpecialSubscriber, SpecialSubscriber.find("webster132")
+ assert_nothing_raised { s = SpecialSubscriber.new("name" => "And breaaaaathe!"); s.id = 'roger'; s.save }
+ end
+
+ private
+ def switch_to_alt_inheritance_column
+ # we don't want misleading test results, so get rid of the values in the type column
+ Company.find(:all, :order => 'id').each do |c|
+ c['type'] = nil
+ c.save
+ end
+ [ Company, Firm, Client].each { |klass| klass.reset_column_information }
+ Company.set_inheritance_column('ruby_type')
+ end
+ def switch_to_default_inheritance_column
+ [ Company, Firm, Client].each { |klass| klass.reset_column_information }
+ Company.set_inheritance_column('type')
+ end
+end
+
+
+class InheritanceComputeTypeTest < ActiveRecord::TestCase
+ fixtures :companies
+
+ def setup
+ Dependencies.log_activity = true
+ end
+
+ def teardown
+ Dependencies.log_activity = false
+ self.class.const_remove :FirmOnTheFly rescue nil
+ Firm.const_remove :FirmOnTheFly rescue nil
+ end
+
+ def test_instantiation_doesnt_try_to_require_corresponding_file
+ foo = Firm.find(:first).clone
+ foo.ruby_type = foo.type = 'FirmOnTheFly'
+ foo.save!
+
+ # Should fail without FirmOnTheFly in the type condition.
+ assert_raise(ActiveRecord::RecordNotFound) { Firm.find(foo.id) }
+
+ # Nest FirmOnTheFly in the test case where Dependencies won't see it.
+ self.class.const_set :FirmOnTheFly, Class.new(Firm)
+ assert_raise(ActiveRecord::SubclassNotFound) { Firm.find(foo.id) }
+
+ # Nest FirmOnTheFly in Firm where Dependencies will see it.
+ # This is analogous to nesting models in a migration.
+ Firm.const_set :FirmOnTheFly, Class.new(Firm)
+
+ # And instantiate will find the existing constant rather than trying
+ # to require firm_on_the_fly.
+ assert_nothing_raised { assert_kind_of Firm::FirmOnTheFly, Firm.find(foo.id) }
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/invalid_date_test.rb b/vendor/rails/activerecord/test/cases/invalid_date_test.rb
new file mode 100644
index 0000000..e2bb17c
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/invalid_date_test.rb
@@ -0,0 +1,24 @@
+require 'cases/helper'
+require 'models/topic'
+
+class InvalidDateTest < Test::Unit::TestCase
+ def test_assign_valid_dates
+ valid_dates = [[2007, 11, 30], [1993, 2, 28], [2008, 2, 29]]
+
+ invalid_dates = [[2007, 11, 31], [1993, 2, 29], [2007, 2, 29]]
+
+ topic = Topic.new
+
+ valid_dates.each do |date_src|
+ topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s)
+ assert_equal(topic.last_read, Date.new(*date_src))
+ end
+
+ invalid_dates.each do |date_src|
+ assert_nothing_raised do
+ topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s})
+ assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object")
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/json_serialization_test.rb b/vendor/rails/activerecord/test/cases/json_serialization_test.rb
new file mode 100644
index 0000000..3446e5e
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/json_serialization_test.rb
@@ -0,0 +1,205 @@
+require "cases/helper"
+require 'models/contact'
+require 'models/post'
+require 'models/author'
+require 'models/tagging'
+require 'models/tag'
+require 'models/comment'
+
+class JsonSerializationTest < ActiveRecord::TestCase
+ class NamespacedContact < Contact
+ column :name, :string
+ end
+
+ def setup
+ @contact = Contact.new(
+ :name => 'Konata Izumi',
+ :age => 16,
+ :avatar => 'binarydata',
+ :created_at => Time.utc(2006, 8, 1),
+ :awesome => true,
+ :preferences => { :shows => 'anime' }
+ )
+ end
+
+ def test_should_demodulize_root_in_json
+ NamespacedContact.include_root_in_json = true
+ @contact = NamespacedContact.new :name => 'whatever'
+ json = @contact.to_json
+ assert_match %r{^\{"namespaced_contact": \{}, json
+ end
+
+ def test_should_include_root_in_json
+ Contact.include_root_in_json = true
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact": \{}, json
+ assert_match %r{"name": "Konata Izumi"}, json
+ assert_match %r{"age": 16}, json
+ assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome": true}, json
+ assert_match %r{"preferences": \{"shows": "anime"\}}, json
+ ensure
+ Contact.include_root_in_json = false
+ end
+
+ def test_should_encode_all_encodable_attributes
+ json = @contact.to_json
+
+ assert_match %r{"name": "Konata Izumi"}, json
+ assert_match %r{"age": 16}, json
+ assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome": true}, json
+ assert_match %r{"preferences": \{"shows": "anime"\}}, json
+ end
+
+ def test_should_allow_attribute_filtering_with_only
+ json = @contact.to_json(:only => [:name, :age])
+
+ assert_match %r{"name": "Konata Izumi"}, json
+ assert_match %r{"age": 16}, json
+ assert_no_match %r{"awesome": true}, json
+ assert !json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_no_match %r{"preferences": \{"shows": "anime"\}}, json
+ end
+
+ def test_should_allow_attribute_filtering_with_except
+ json = @contact.to_json(:except => [:name, :age])
+
+ assert_no_match %r{"name": "Konata Izumi"}, json
+ assert_no_match %r{"age": 16}, json
+ assert_match %r{"awesome": true}, json
+ assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"preferences": \{"shows": "anime"\}}, json
+ end
+
+ def test_methods_are_called_on_object
+ # Define methods on fixture.
+ def @contact.label; "Has cheezburger"; end
+ def @contact.favorite_quote; "Constraints are liberating"; end
+
+ # Single method.
+ assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
+
+ # Both methods.
+ methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
+ assert_match %r{"label": "Has cheezburger"}, methods_json
+ assert_match %r{"favorite_quote": "Constraints are liberating"}, methods_json
+ end
+end
+
+class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase
+ fixtures :authors, :posts, :comments, :tags, :taggings
+
+ def setup
+ @david = authors(:david)
+ @mary = authors(:mary)
+ end
+
+ def test_includes_uses_association_name
+ json = @david.to_json(:include => :posts)
+
+ assert_match %r{"posts": \[}, json
+
+ assert_match %r{"id": 1}, json
+ assert_match %r{"name": "David"}, json
+
+ assert_match %r{"author_id": 1}, json
+ assert_match %r{"title": "Welcome to the weblog"}, json
+ assert_match %r{"body": "Such a lovely day"}, json
+
+ assert_match %r{"title": "So I was thinking"}, json
+ assert_match %r{"body": "Like I hopefully always am"}, json
+ end
+
+ def test_includes_uses_association_name_and_applies_attribute_filters
+ json = @david.to_json(:include => { :posts => { :only => :title } })
+
+ assert_match %r{"name": "David"}, json
+ assert_match %r{"posts": \[}, json
+
+ assert_match %r{"title": "Welcome to the weblog"}, json
+ assert_no_match %r{"body": "Such a lovely day"}, json
+
+ assert_match %r{"title": "So I was thinking"}, json
+ assert_no_match %r{"body": "Like I hopefully always am"}, json
+ end
+
+ def test_includes_fetches_second_level_associations
+ json = @david.to_json(:include => { :posts => { :include => { :comments => { :only => :body } } } })
+
+ assert_match %r{"name": "David"}, json
+ assert_match %r{"posts": \[}, json
+
+ assert_match %r{"comments": \[}, json
+ assert_match %r{\{"body": "Thank you again for the welcome"\}}, json
+ assert_match %r{\{"body": "Don't think too hard"\}}, json
+ assert_no_match %r{"post_id": }, json
+ end
+
+ def test_includes_fetches_nth_level_associations
+ json = @david.to_json(
+ :include => {
+ :posts => {
+ :include => {
+ :taggings => {
+ :include => {
+ :tag => { :only => :name }
+ }
+ }
+ }
+ }
+ })
+
+ assert_match %r{"name": "David"}, json
+ assert_match %r{"posts": \[}, json
+
+ assert_match %r{"taggings": \[}, json
+ assert_match %r{"tag": \{"name": "General"\}}, json
+ end
+
+ def test_should_not_call_methods_on_associations_that_dont_respond
+ def @david.favorite_quote; "Constraints are liberating"; end
+ json = @david.to_json(:include => :posts, :methods => :favorite_quote)
+
+ assert !@david.posts.first.respond_to?(:favorite_quote)
+ assert_match %r{"favorite_quote": "Constraints are liberating"}, json
+ assert_equal %r{"favorite_quote": }.match(json).size, 1
+ end
+
+ def test_should_allow_only_option_for_list_of_authors
+ authors = [@david, @mary]
+
+ assert_equal %([{"name": "David"}, {"name": "Mary"}]), authors.to_json(:only => :name)
+ end
+
+ def test_should_allow_except_option_for_list_of_authors
+ authors = [@david, @mary]
+
+ assert_equal %([{"id": 1}, {"id": 2}]), authors.to_json(:except => [:name, :author_address_id, :author_address_extra_id])
+ end
+
+ def test_should_allow_includes_for_list_of_authors
+ authors = [@david, @mary]
+ json = authors.to_json(
+ :only => :name,
+ :include => {
+ :posts => { :only => :id }
+ }
+ )
+
+ ['"name": "David"', '"posts": [', '{"id": 1}', '{"id": 2}', '{"id": 4}',
+ '{"id": 5}', '{"id": 6}', '"name": "Mary"', '"posts": [{"id": 7}]'].each do |fragment|
+ assert json.include?(fragment), json
+ end
+ end
+
+ def test_should_allow_options_for_hash_of_authors
+ authors_hash = {
+ 1 => @david,
+ 2 => @mary
+ }
+
+ assert_equal %({1: {"name": "David"}}), authors_hash.to_json(:only => [1, :name])
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/lifecycle_test.rb b/vendor/rails/activerecord/test/cases/lifecycle_test.rb
new file mode 100644
index 0000000..ddcacac
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/lifecycle_test.rb
@@ -0,0 +1,140 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/developer'
+require 'models/reply'
+
+class Topic; def after_find() end end
+class Developer; def after_find() end end
+class SpecialDeveloper < Developer; end
+
+class TopicManualObserver
+ include Singleton
+
+ attr_reader :action, :object, :callbacks
+
+ def initialize
+ Topic.add_observer(self)
+ @callbacks = []
+ end
+
+ def update(callback_method, object)
+ @callbacks << { "callback_method" => callback_method, "object" => object }
+ end
+
+ def has_been_notified?
+ !@callbacks.empty?
+ end
+end
+
+class TopicaAuditor < ActiveRecord::Observer
+ observe :topic
+
+ attr_reader :topic
+
+ def after_find(topic)
+ @topic = topic
+ end
+end
+
+class TopicObserver < ActiveRecord::Observer
+ attr_reader :topic
+
+ def after_find(topic)
+ @topic = topic
+ end
+end
+
+class MultiObserver < ActiveRecord::Observer
+ attr_reader :record
+
+ def self.observed_class() [ Topic, Developer ] end
+
+ cattr_reader :last_inherited
+ @@last_inherited = nil
+
+ def observed_class_inherited_with_testing(subclass)
+ observed_class_inherited_without_testing(subclass)
+ @@last_inherited = subclass
+ end
+
+ alias_method_chain :observed_class_inherited, :testing
+
+ def after_find(record)
+ @record = record
+ end
+end
+
+class LifecycleTest < ActiveRecord::TestCase
+ fixtures :topics, :developers
+
+ 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
+ end
+
+ def test_after_save
+ ActiveRecord::Base.observers = :topic_manual_observer
+ ActiveRecord::Base.instantiate_observers
+
+ topic = Topic.find(1)
+ topic.title = "hello"
+ topic.save
+
+ assert TopicManualObserver.instance.has_been_notified?
+ assert_equal :after_save, TopicManualObserver.instance.callbacks.last["callback_method"]
+ end
+
+ def test_observer_update_on_save
+ ActiveRecord::Base.observers = TopicManualObserver
+ ActiveRecord::Base.instantiate_observers
+
+ topic = Topic.find(1)
+ assert TopicManualObserver.instance.has_been_notified?
+ assert_equal :after_find, TopicManualObserver.instance.callbacks.first["callback_method"]
+ end
+
+ def test_auto_observer
+ topic_observer = TopicaAuditor.instance
+ assert_nil TopicaAuditor.observed_class
+ assert_equal [Topic], TopicaAuditor.instance.observed_classes.to_a
+
+ topic = Topic.find(1)
+ assert_equal topic.title, topic_observer.topic.title
+ end
+
+ def test_inferred_auto_observer
+ topic_observer = TopicObserver.instance
+ assert_equal Topic, TopicObserver.observed_class
+
+ topic = Topic.find(1)
+ assert_equal topic.title, topic_observer.topic.title
+ end
+
+ def test_observing_two_classes
+ multi_observer = MultiObserver.instance
+
+ topic = Topic.find(1)
+ assert_equal topic.title, multi_observer.record.title
+
+ developer = Developer.find(1)
+ assert_equal developer.name, multi_observer.record.name
+ end
+
+ def test_observing_subclasses
+ multi_observer = MultiObserver.instance
+
+ developer = SpecialDeveloper.find(1)
+ assert_equal developer.name, multi_observer.record.name
+
+ klass = Class.new(Developer)
+ assert_equal klass, multi_observer.last_inherited
+
+ developer = klass.find(1)
+ assert_equal developer.name, multi_observer.record.name
+ end
+
+ def test_invalid_observer
+ assert_raise(ArgumentError) { Topic.observers = Object.new; Topic.instantiate_observers }
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/locking_test.rb b/vendor/rails/activerecord/test/cases/locking_test.rb
new file mode 100644
index 0000000..7db6c57
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/locking_test.rb
@@ -0,0 +1,289 @@
+require "cases/helper"
+require 'models/person'
+require 'models/reader'
+require 'models/legacy_thing'
+require 'models/reference'
+
+class LockWithoutDefault < ActiveRecord::Base; end
+
+class LockWithCustomColumnWithoutDefault < ActiveRecord::Base
+ set_table_name :lock_without_defaults_cust
+ set_locking_column :custom_lock_version
+end
+
+class ReadonlyFirstNamePerson < Person
+ attr_readonly :first_name
+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)
+ assert_equal 0, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ p1.save!
+ assert_equal 1, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
+ end
+
+ def test_lock_repeating
+ p1 = Person.find(1)
+ p2 = Person.find(1)
+ assert_equal 0, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ p1.save!
+ assert_equal 1, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
+ end
+
+ def test_lock_new
+ p1 = Person.new(:first_name => 'anika')
+ assert_equal 0, p1.lock_version
+
+ p1.save!
+ p2 = Person.find(p1.id)
+ assert_equal 0, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ p1.save!
+ assert_equal 1, p1.lock_version
+ assert_equal 0, p2.lock_version
+
+ assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
+ end
+
+ def test_lock_new_with_nil
+ p1 = Person.new(:first_name => 'anika')
+ p1.save!
+ p1.lock_version = nil # simulate bad fixture or column with no default
+ p1.save!
+ assert_equal 1, p1.lock_version
+ end
+
+
+ def test_lock_column_name_existing
+ t1 = LegacyThing.find(1)
+ t2 = LegacyThing.find(1)
+ assert_equal 0, t1.version
+ assert_equal 0, t2.version
+
+ t1.save!
+ assert_equal 1, t1.version
+ assert_equal 0, t2.version
+
+ assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
+ end
+
+ def test_lock_column_is_mass_assignable
+ p1 = Person.create(:first_name => 'bianca')
+ assert_equal 0, p1.lock_version
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
+
+ p1.save!
+ assert_equal 1, p1.lock_version
+ assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
+ end
+
+ def test_lock_without_default_sets_version_to_zero
+ t1 = LockWithoutDefault.new
+ assert_equal 0, t1.lock_version
+ end
+
+ def test_lock_with_custom_column_without_default_sets_version_to_zero
+ t1 = LockWithCustomColumnWithoutDefault.new
+ assert_equal 0, t1.custom_lock_version
+ end
+
+ def test_readonly_attributes
+ assert_equal Set.new([ 'first_name' ]), ReadonlyFirstNamePerson.readonly_attributes
+
+ p = ReadonlyFirstNamePerson.create(:first_name => "unchangeable name")
+ p.reload
+ assert_equal "unchangeable name", p.first_name
+
+ p.update_attributes(:first_name => "changed name")
+ p.reload
+ assert_equal "unchangeable name", p.first_name
+ end
+
+ { :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|
+ model.increment_counter :test_count, id
+ end
+ end
+
+ define_method("test_decrement_counter_updates_#{name}") do
+ counter_test model, -1 do |id|
+ model.decrement_counter :test_count, id
+ end
+ end
+
+ define_method("test_update_counters_updates_#{name}") do
+ counter_test model, 1 do |id|
+ model.update_counters id, :test_count => 1
+ end
+ end
+ end
+
+ def test_quote_table_name
+ ref = references(:michael_magician)
+ ref.favourite = !ref.favourite
+ assert ref.save
+ end
+
+ private
+
+ def add_counter_column_to(model)
+ model.connection.add_column model.table_name, :test_count, :integer, :null => false, :default => 0
+ model.reset_column_information
+ # OpenBase does not set a value to existing rows when adding a not null default column
+ model.update_all(:test_count => 0) if current_adapter?(:OpenBaseAdapter)
+ end
+
+ def remove_counter_column_from(model)
+ model.connection.remove_column model.table_name, :test_count
+ model.reset_column_information
+ end
+
+ def counter_test(model, expected_count)
+ add_counter_column_to(model)
+ object = model.find(:first)
+ assert_equal 0, object.test_count
+ assert_equal 0, object.send(model.locking_column)
+ yield object.id
+ object.reload
+ assert_equal expected_count, object.test_count
+ assert_equal 1, object.send(model.locking_column)
+ ensure
+ remove_counter_column_from(model)
+ end
+end
+
+
+# TODO: test against the generated SQL since testing locking behavior itself
+# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
+# blocks, so separate script called by Kernel#system is needed.
+# (See exec vs. async_exec in the PostgreSQL adapter.)
+
+# TODO: The SQL Server, Sybase, and OpenBase adapters currently have no support for pessimistic locking
+
+unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :OpenBaseAdapter)
+ class PessimisticLockingTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :people, :readers
+
+ def setup
+ # Avoid introspection queries during tests.
+ Person.columns; Reader.columns
+
+ @allow_concurrency = ActiveRecord::Base.allow_concurrency
+ ActiveRecord::Base.allow_concurrency = true
+ end
+
+ def teardown
+ ActiveRecord::Base.allow_concurrency = @allow_concurrency
+ end
+
+ # Test typical find.
+ def test_sane_find_with_lock
+ assert_nothing_raised do
+ Person.transaction do
+ Person.find 1, :lock => true
+ end
+ end
+ end
+
+ # Test scoped lock.
+ def test_sane_find_with_scoped_lock
+ assert_nothing_raised do
+ Person.transaction do
+ Person.with_scope(:find => { :lock => true }) do
+ Person.find 1
+ end
+ end
+ end
+ end
+
+ # PostgreSQL protests SELECT ... FOR UPDATE on an outer join.
+ unless current_adapter?(:PostgreSQLAdapter)
+ # Test locked eager find.
+ def test_eager_find_with_lock
+ assert_nothing_raised do
+ Person.transaction do
+ Person.find 1, :include => :readers, :lock => true
+ end
+ end
+ end
+ end
+
+ # Locking a record reloads it.
+ def test_sane_lock_method
+ assert_nothing_raised do
+ Person.transaction do
+ person = Person.find 1
+ old, person.first_name = person.first_name, 'fooman'
+ person.lock!
+ assert_equal old, person.first_name
+ end
+ end
+ end
+
+ if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
+ def test_no_locks_no_wait
+ first, second = duel { Person.find 1 }
+ assert first.end > second.end
+ end
+
+ def test_second_lock_waits
+ assert [0.2, 1, 5].any? { |zzz|
+ first, second = duel(zzz) { Person.find 1, :lock => true }
+ second.end > first.end
+ }
+ end
+
+ protected
+ def duel(zzz = 5)
+ t0, t1, t2, t3 = nil, nil, nil, nil
+
+ a = Thread.new do
+ t0 = Time.now
+ Person.transaction do
+ yield
+ sleep zzz # block thread 2 for zzz seconds
+ end
+ t1 = Time.now
+ end
+
+ b = Thread.new do
+ sleep zzz / 2.0 # ensure thread 1 tx starts first
+ t2 = Time.now
+ Person.transaction { yield }
+ t3 = Time.now
+ end
+
+ a.join
+ b.join
+
+ assert t1 > t0 + zzz
+ assert t2 > t0
+ assert t3 > t2
+ [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
+ end
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/method_scoping_test.rb b/vendor/rails/activerecord/test/cases/method_scoping_test.rb
new file mode 100644
index 0000000..1a9a875
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/method_scoping_test.rb
@@ -0,0 +1,452 @@
+require "cases/helper"
+require 'models/developer'
+require 'models/project'
+require 'models/comment'
+require 'models/post'
+require 'models/category'
+
+class MethodScopingTest < ActiveRecord::TestCase
+ fixtures :developers, :projects, :comments, :posts
+
+ def test_set_conditions
+ Developer.with_scope(:find => { :conditions => 'just a test...' }) do
+ assert_equal 'just a test...', Developer.send(:current_scoped_methods)[:find][:conditions]
+ end
+ end
+
+ def test_scoped_find
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ assert_nothing_raised { Developer.find(1) }
+ end
+ end
+
+ def test_scoped_find_first
+ Developer.with_scope(:find => { :conditions => "salary = 100000" }) do
+ assert_equal Developer.find(10), Developer.find(:first, :order => 'name')
+ end
+ end
+
+ def test_scoped_find_combines_conditions
+ Developer.with_scope(:find => { :conditions => "salary = 9000" }) do
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => "name = 'Jamis'")
+ end
+ end
+
+ def test_scoped_find_sanitizes_conditions
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
+ assert_equal developers(:poor_jamis), Developer.find(:first)
+ end
+ end
+
+ def test_scoped_find_combines_and_sanitizes_conditions
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
+ end
+ end
+
+ def test_scoped_find_all
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ assert_equal [developers(:david)], Developer.find(:all)
+ end
+ end
+
+ def test_scoped_find_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :conditions => "name = 'David'")
+ assert_equal "David", developer.name
+ assert !developer.has_attribute?(:salary)
+ end
+ end
+
+ def test_options_select_replaces_scope_select
+ Developer.with_scope(:find => { :select => "id, name" }) do
+ developer = Developer.find(:first, :select => 'id, salary', :conditions => "name = 'David'")
+ assert_equal 80000, developer.salary
+ assert !developer.has_attribute?(:name)
+ end
+ end
+
+ def test_scoped_count
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ assert_equal 1, Developer.count
+ end
+
+ Developer.with_scope(:find => { :conditions => 'salary = 100000' }) do
+ assert_equal 8, Developer.count
+ assert_equal 1, Developer.count(:conditions => "name LIKE 'fixture_1%'")
+ end
+ end
+
+ def test_scoped_find_include
+ # with the include, will retrieve only developers for the given project
+ scoped_developers = Developer.with_scope(:find => { :include => :projects }) do
+ Developer.find(:all, :conditions => 'projects.id = 2')
+ end
+ assert scoped_developers.include?(developers(:david))
+ assert !scoped_developers.include?(developers(:jamis))
+ assert_equal 1, scoped_developers.size
+ end
+
+ def test_scoped_count_include
+ # with the include, will retrieve only developers for the given project
+ Developer.with_scope(:find => { :include => :projects }) do
+ assert_equal 1, Developer.count(:conditions => 'projects.id = 2')
+ end
+ end
+
+ def test_scoped_create
+ new_comment = nil
+
+ VerySpecialComment.with_scope(:create => { :post_id => 1 }) do
+ assert_equal({ :post_id => 1 }, VerySpecialComment.send(:current_scoped_methods)[:create])
+ new_comment = VerySpecialComment.create :body => "Wonderful world"
+ end
+
+ assert Post.find(1).comments.include?(new_comment)
+ end
+
+ def test_immutable_scope
+ options = { :conditions => "name = 'David'" }
+ Developer.with_scope(:find => options) do
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ options[:conditions] = "name != 'David'"
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ end
+
+ scope = { :find => { :conditions => "name = 'David'" }}
+ Developer.with_scope(scope) do
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ scope[:find][:conditions] = "name != 'David'"
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ end
+ end
+
+ def test_scoped_with_duck_typing
+ scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] })
+ Developer.with_scope(scoping) do
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ end
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ scoped_methods = Developer.instance_eval('current_scoped_methods')
+
+ begin
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ raise "an exception"
+ end
+ rescue
+ end
+ assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+ end
+end
+
+class NestedScopingTest < ActiveRecord::TestCase
+ fixtures :developers, :projects, :comments, :posts
+
+ def test_merge_options
+ Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
+ Developer.with_scope(:find => { :limit => 10 }) do
+ merged_option = Developer.instance_eval('current_scoped_methods')[:find]
+ assert_equal({ :conditions => 'salary = 80000', :limit => 10 }, merged_option)
+ end
+ end
+ end
+
+ def test_replace_options
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.instance_eval('current_scoped_methods'))
+ assert_equal({:find => { :conditions => "name = 'Jamis'" }}, Developer.send(:scoped_methods)[-1])
+ end
+ end
+ end
+
+ def test_append_conditions
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
+ appended_condition = Developer.instance_eval('current_scoped_methods')[:find][:conditions]
+ assert_equal("(name = 'David') AND (salary = 80000)", appended_condition)
+ assert_equal(1, Developer.count)
+ end
+ Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
+ assert_equal(0, Developer.count)
+ end
+ end
+ end
+
+ def test_merge_and_append_options
+ Developer.with_scope(:find => { :conditions => 'salary = 80000', :limit => 10 }) do
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ merged_option = Developer.instance_eval('current_scoped_methods')[:find]
+ assert_equal({ :conditions => "(salary = 80000) AND (name = 'David')", :limit => 10 }, merged_option)
+ end
+ end
+ end
+
+ def test_nested_scoped_find
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
+ assert_nothing_raised { Developer.find(1) }
+ assert_equal('David', Developer.find(:first).name)
+ end
+ assert_equal('Jamis', Developer.find(:first).name)
+ end
+ end
+
+ def test_nested_scoped_find_include
+ Developer.with_scope(:find => { :include => :projects }) do
+ Developer.with_scope(:find => { :conditions => "projects.id = 2" }) do
+ assert_nothing_raised { Developer.find(1) }
+ assert_equal('David', Developer.find(:first).name)
+ end
+ end
+ end
+
+ def test_nested_scoped_find_merged_include
+ # :include's remain unique and don't "double up" when merging
+ Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
+ Developer.with_scope(:find => { :include => :projects }) do
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal('David', Developer.find(:first).name)
+ end
+ end
+
+ # the nested scope doesn't remove the first :include
+ Developer.with_scope(:find => { :include => :projects, :conditions => "projects.id = 2" }) do
+ Developer.with_scope(:find => { :include => [] }) do
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal('David', Developer.find(:first).name)
+ end
+ end
+
+ # mixing array and symbol include's will merge correctly
+ Developer.with_scope(:find => { :include => [:projects], :conditions => "projects.id = 2" }) do
+ Developer.with_scope(:find => { :include => :projects }) do
+ assert_equal 1, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ assert_equal('David', Developer.find(:first).name)
+ end
+ end
+ end
+
+ def test_nested_scoped_find_replace_include
+ Developer.with_scope(:find => { :include => :projects }) do
+ Developer.with_exclusive_scope(:find => { :include => [] }) do
+ assert_equal 0, Developer.instance_eval('current_scoped_methods')[:find][:include].length
+ end
+ end
+ end
+
+ def test_three_level_nested_exclusive_scoped_find
+ Developer.with_scope(:find => { :conditions => "name = 'Jamis'" }) do
+ assert_equal('Jamis', Developer.find(:first).name)
+
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'David'" }) do
+ assert_equal('David', Developer.find(:first).name)
+
+ Developer.with_exclusive_scope(:find => { :conditions => "name = 'Maiha'" }) do
+ assert_equal(nil, Developer.find(:first))
+ end
+
+ # ensure that scoping is restored
+ assert_equal('David', Developer.find(:first).name)
+ end
+
+ # ensure that scoping is restored
+ assert_equal('Jamis', Developer.find(:first).name)
+ end
+ end
+
+ def test_merged_scoped_find
+ poor_jamis = developers(:poor_jamis)
+ Developer.with_scope(:find => { :conditions => "salary < 100000" }) do
+ Developer.with_scope(:find => { :offset => 1 }) do
+ assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc'))
+ end
+ end
+ end
+
+ def test_merged_scoped_find_sanitizes_conditions
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
+ Developer.with_scope(:find => { :conditions => ['salary = ?', 9000] }) do
+ assert_raise(ActiveRecord::RecordNotFound) { developers(:poor_jamis) }
+ end
+ end
+ end
+
+ def test_nested_scoped_find_combines_and_sanitizes_conditions
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
+ Developer.with_exclusive_scope(:find => { :conditions => ['salary = ?', 9000] }) do
+ assert_equal developers(:poor_jamis), Developer.find(:first)
+ assert_equal developers(:poor_jamis), Developer.find(:first, :conditions => ['name = ?', 'Jamis'])
+ end
+ end
+ end
+
+ def test_merged_scoped_find_combines_and_sanitizes_conditions
+ Developer.with_scope(:find => { :conditions => ["name = ?", 'David'] }) do
+ Developer.with_scope(:find => { :conditions => ['salary > ?', 9000] }) do
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ end
+ end
+ end
+
+ def test_merged_scoped_find_on_blank_conditions
+ [nil, " ", [], {}].each do |blank|
+ Developer.with_scope(:find => {:conditions => blank}) do
+ Developer.with_scope(:find => {:conditions => blank}) do
+ assert_nothing_raised { Developer.find(:first) }
+ end
+ end
+ end
+ end
+
+ def test_merged_scoped_find_on_blank_bind_conditions
+ [ [""], ["",{}] ].each do |blank|
+ Developer.with_scope(:find => {:conditions => blank}) do
+ Developer.with_scope(:find => {:conditions => blank}) do
+ assert_nothing_raised { Developer.find(:first) }
+ end
+ end
+ end
+ end
+
+ def test_immutable_nested_scope
+ options1 = { :conditions => "name = 'Jamis'" }
+ options2 = { :conditions => "name = 'David'" }
+ Developer.with_scope(:find => options1) do
+ Developer.with_exclusive_scope(:find => options2) do
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ options1[:conditions] = options2[:conditions] = nil
+ assert_equal %w(David), Developer.find(:all).map { |d| d.name }
+ end
+ end
+ end
+
+ def test_immutable_merged_scope
+ options1 = { :conditions => "name = 'Jamis'" }
+ options2 = { :conditions => "salary > 10000" }
+ Developer.with_scope(:find => options1) do
+ Developer.with_scope(:find => options2) do
+ assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ options1[:conditions] = options2[:conditions] = nil
+ assert_equal %w(Jamis), Developer.find(:all).map { |d| d.name }
+ end
+ end
+ end
+
+ def test_ensure_that_method_scoping_is_correctly_restored
+ Developer.with_scope(:find => { :conditions => "name = 'David'" }) do
+ scoped_methods = Developer.instance_eval('current_scoped_methods')
+ begin
+ Developer.with_scope(:find => { :conditions => "name = 'Maiha'" }) do
+ raise "an exception"
+ end
+ rescue
+ end
+ assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
+ end
+ end
+end
+
+class HasManyScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+
+ def test_forwarding_to_scoped
+ assert_equal 4, Comment.search_by_type('Comment').size
+ assert_equal 2, @welcome.comments.search_by_type('Comment').size
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Comment.find_all_by_type('Comment').size
+ assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
+ end
+
+ def test_nested_scope
+ Comment.with_scope(:find => { :conditions => '1=1' }) do
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+end
+
+
+class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
+ fixtures :posts, :categories, :categories_posts
+
+ def setup
+ @welcome = Post.find(1)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a category...', Category.what_are_you
+ assert_equal 'a category...', @welcome.categories.what_are_you
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Category.find_all_by_type('SpecialCategory').size
+ assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
+ assert_equal 2, @welcome.categories.find_all_by_type('Category').size
+ end
+
+ def test_nested_scope
+ Category.with_scope(:find => { :conditions => '1=1' }) do
+ assert_equal 'a comment...', @welcome.comments.what_are_you
+ end
+ end
+end
+
+
+=begin
+# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
+
+
+class BelongsToScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts
+
+ def setup
+ @greetings = Comment.find(1)
+ end
+
+ def test_forwarding_of_static_method
+ assert_equal 'a post...', Post.what_are_you
+ assert_equal 'a post...', @greetings.post.what_are_you
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 4, Post.find_all_by_type('Post').size
+ assert_equal 1, @greetings.post.find_all_by_type('Post').size
+ end
+
+end
+
+
+class HasOneScopingTest< ActiveRecord::TestCase
+ fixtures :comments, :posts
+
+ def setup
+ @sti_comments = Post.find(4)
+ end
+
+ def test_forwarding_of_static_methods
+ assert_equal 'a comment...', Comment.what_are_you
+ assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
+ end
+
+ def test_forwarding_to_dynamic_finders
+ assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
+ assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
+ assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
+ end
+
+end
+
+=end
diff --git a/vendor/rails/activerecord/test/cases/migration_test.rb b/vendor/rails/activerecord/test/cases/migration_test.rb
new file mode 100644
index 0000000..f36255e
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/migration_test.rb
@@ -0,0 +1,1319 @@
+require "cases/helper"
+require 'bigdecimal/util'
+
+require 'models/person'
+require 'models/topic'
+
+require MIGRATIONS_ROOT + "/valid/1_people_have_last_names"
+require MIGRATIONS_ROOT + "/valid/2_we_need_reminders"
+require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers"
+require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down"
+
+if ActiveRecord::Base.connection.supports_migrations?
+ class BigNumber < ActiveRecord::Base; end
+
+ class Reminder < ActiveRecord::Base; end
+
+ class ActiveRecord::Migration
+ class < 40)
+ Person.reset_column_information
+ end
+
+ def test_add_index
+ # Limit size of last_name and key columns to support Firebird index limitations
+ Person.connection.add_column "people", "last_name", :string, :limit => 100
+ Person.connection.add_column "people", "key", :string, :limit => 100
+ Person.connection.add_column "people", "administrator", :boolean
+
+ assert_nothing_raised { Person.connection.add_index("people", "last_name") }
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
+
+ # Orcl nds shrt indx nms. Sybs 2.
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:OracleAdapter, :SybaseAdapter, :OpenBaseAdapter)
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", :column => ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", :name => "index_people_on_last_name_and_first_name") }
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") }
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) }
+ end
+
+ # quoting
+ # Note: changed index name from "key" to "key_idx" since "key" is a Firebird reserved word
+ # OpenBase does not have named indexes. You must specify a single column name
+ unless current_adapter?(:OpenBaseAdapter)
+ Person.update_all "#{Person.connection.quote_column_name 'key'}=#{Person.connection.quote_column_name 'id'}" #some databases (including sqlite2 won't add a unique index if existing data non unique)
+ assert_nothing_raised { Person.connection.add_index("people", ["key"], :name => "key_idx", :unique => true) }
+ assert_nothing_raised { Person.connection.remove_index("people", :name => "key_idx", :unique => true) }
+ end
+
+ # Sybase adapter does not support indexes on :boolean columns
+ # OpenBase does not have named indexes. You must specify a single column
+ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter)
+ assert_nothing_raised { Person.connection.add_index("people", %w(last_name first_name administrator), :name => "named_admin") }
+ assert_nothing_raised { Person.connection.remove_index("people", :name => "named_admin") }
+ end
+ end
+
+ def test_create_table_adds_id
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo id),
+ Person.connection.columns(:testings).map { |c| c.name }.sort
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_create_table_with_not_null_column
+ assert_nothing_raised do
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :null => false
+ end
+ end
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Person.connection.execute "insert into testings (foo) values (NULL)"
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_create_table_with_defaults
+ # MySQL doesn't allow defaults on TEXT or BLOB columns.
+ mysql = current_adapter?(:MysqlAdapter)
+
+ Person.connection.create_table :testings do |t|
+ t.column :one, :string, :default => "hello"
+ t.column :two, :boolean, :default => true
+ t.column :three, :boolean, :default => false
+ t.column :four, :integer, :default => 1
+ t.column :five, :text, :default => "hello" unless mysql
+ end
+
+ columns = Person.connection.columns(:testings)
+ one = columns.detect { |c| c.name == "one" }
+ two = columns.detect { |c| c.name == "two" }
+ three = columns.detect { |c| c.name == "three" }
+ four = columns.detect { |c| c.name == "four" }
+ five = columns.detect { |c| c.name == "five" } unless mysql
+
+ assert_equal "hello", one.default
+ assert_equal true, two.default
+ assert_equal false, three.default
+ assert_equal 1, four.default
+ assert_equal "hello", five.default unless mysql
+
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_create_table_with_limits
+ assert_nothing_raised do
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string, :limit => 255
+
+ t.column :default_int, :integer
+
+ t.column :one_int, :integer, :limit => 1
+ t.column :four_int, :integer, :limit => 4
+ t.column :eight_int, :integer, :limit => 8
+ end
+ end
+
+ columns = Person.connection.columns(:testings)
+ foo = columns.detect { |c| c.name == "foo" }
+ assert_equal 255, foo.limit
+
+ default = columns.detect { |c| c.name == "default_int" }
+ one = columns.detect { |c| c.name == "one_int" }
+ four = columns.detect { |c| c.name == "four_int" }
+ eight = columns.detect { |c| c.name == "eight_int" }
+
+ if current_adapter?(:PostgreSQLAdapter)
+ assert_equal 'integer', default.sql_type
+ assert_equal 'smallint', one.sql_type
+ assert_equal 'integer', four.sql_type
+ assert_equal 'bigint', eight.sql_type
+ elsif current_adapter?(:OracleAdapter)
+ assert_equal 'NUMBER(38)', default.sql_type
+ assert_equal 'NUMBER(1)', one.sql_type
+ assert_equal 'NUMBER(4)', four.sql_type
+ assert_equal 'NUMBER(8)', eight.sql_type
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name_with_underscore
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
+
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testings_id), Person.connection.columns(:testings).map { |c| c.name }.sort
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ def test_create_table_with_primary_key_prefix_as_table_name
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ assert_equal %w(foo testingsid), Person.connection.columns(:testings).map { |c| c.name }.sort
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ end
+
+ uses_mocha('test_create_table_with_force_true_does_not_drop_nonexisting_table') do
+ def test_create_table_with_force_true_does_not_drop_nonexisting_table
+ if Person.connection.table_exists?(:testings2)
+ Person.connection.drop_table :testings2
+ end
+
+ # using a copy as we need the drop_table method to
+ # continue to work for the ensure block of the test
+ temp_conn = Person.connection.dup
+ temp_conn.expects(:drop_table).never
+ temp_conn.create_table :testings2, :force => true do |t|
+ t.column :foo, :string
+ end
+ ensure
+ Person.connection.drop_table :testings2 rescue nil
+ end
+ end
+
+
+ # SQL Server, Sybase, and SQLite3 will not allow you to add a NOT NULL
+ # column to a table without a default value.
+ unless current_adapter?(:SQLServerAdapter, :SybaseAdapter, :SQLiteAdapter)
+ def test_add_column_not_null_without_default
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+ Person.connection.add_column :testings, :bar, :string, :null => false
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Person.connection.execute "insert into testings (foo, bar) values ('hello', NULL)"
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+ end
+
+ def test_add_column_not_null_with_default
+ Person.connection.create_table :testings do |t|
+ t.column :foo, :string
+ end
+
+ con = Person.connection
+ Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter)
+ Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')"
+ Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter)
+ assert_nothing_raised {Person.connection.add_column :testings, :bar, :string, :null => false, :default => "default" }
+
+ assert_raises(ActiveRecord::StatementInvalid) do
+ unless current_adapter?(:OpenBaseAdapter)
+ Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)"
+ else
+ Person.connection.insert("INSERT INTO testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) VALUES (2, 'hello', NULL)",
+ "Testing Insert","id",2)
+ end
+ end
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ # We specifically do a manual INSERT here, and then test only the SELECT
+ # functionality. This allows us to more easily catch INSERT being broken,
+ # but SELECT actually working fine.
+ def test_native_decimal_insert_manual_vs_automatic
+ correct_value = '0012345678901234567890.0123456789'.to_d
+
+ Person.delete_all
+ Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
+ Person.reset_column_information
+
+ # Do a manual insertion
+ if current_adapter?(:OracleAdapter)
+ Person.connection.execute "insert into people (id, wealth) values (people_seq.nextval, 12345678901234567890.0123456789)"
+ elsif current_adapter?(:OpenBaseAdapter) || (current_adapter?(:MysqlAdapter) && Mysql.client_version < 50003) #before mysql 5.0.3 decimals stored as strings
+ Person.connection.execute "insert into people (wealth) values ('12345678901234567890.0123456789')"
+ else
+ Person.connection.execute "insert into people (wealth) values (12345678901234567890.0123456789)"
+ end
+
+ # SELECT
+ row = Person.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If this assert fails, that means the SELECT is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+
+ # Reset to old state
+ Person.delete_all
+
+ # Now use the Rails insertion
+ assert_nothing_raised { Person.create :wealth => BigDecimal.new("12345678901234567890.0123456789") }
+
+ # SELECT
+ row = Person.find(:first)
+ assert_kind_of BigDecimal, row.wealth
+
+ # If these asserts fail, that means the INSERT (create function, or cast to SQL) is broken!
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal correct_value, row.wealth
+ end
+
+ # Reset to old state
+ Person.connection.del_column "people", "wealth" rescue nil
+ Person.reset_column_information
+ end
+
+ def test_add_column_with_precision_and_scale
+ Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7
+ Person.reset_column_information
+
+ wealth_column = Person.columns_hash['wealth']
+ assert_equal 9, wealth_column.precision
+ assert_equal 7, wealth_column.scale
+ end
+
+ def test_native_types
+ Person.delete_all
+ Person.connection.add_column "people", "last_name", :string
+ Person.connection.add_column "people", "bio", :text
+ Person.connection.add_column "people", "age", :integer
+ Person.connection.add_column "people", "height", :float
+ Person.connection.add_column "people", "wealth", :decimal, :precision => '30', :scale => '10'
+ Person.connection.add_column "people", "birthday", :datetime
+ Person.connection.add_column "people", "favorite_day", :date
+ Person.connection.add_column "people", "moment_of_truth", :datetime
+ Person.connection.add_column "people", "male", :boolean
+ Person.reset_column_information
+
+ assert_nothing_raised do
+ Person.create :first_name => 'bob', :last_name => 'bobsen',
+ :bio => "I was born ....", :age => 18, :height => 1.78,
+ :wealth => BigDecimal.new("12345678901234567890.0123456789"),
+ :birthday => 18.years.ago, :favorite_day => 10.days.ago,
+ :moment_of_truth => "1782-10-10 21:40:18", :male => true
+ end
+
+ bob = Person.find(:first)
+ assert_equal 'bob', bob.first_name
+ assert_equal 'bobsen', bob.last_name
+ assert_equal "I was born ....", bob.bio
+ assert_equal 18, bob.age
+
+ # Test for 30 significent digits (beyond the 16 of float), 10 of them
+ # after the decimal place.
+
+ unless current_adapter?(:SQLite3Adapter)
+ assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth
+ end
+
+ assert_equal true, bob.male?
+
+ assert_equal String, bob.first_name.class
+ assert_equal String, bob.last_name.class
+ assert_equal String, bob.bio.class
+ assert_equal Fixnum, bob.age.class
+ assert_equal Time, bob.birthday.class
+
+ if current_adapter?(:SQLServerAdapter, :OracleAdapter, :SybaseAdapter)
+ # Sybase, and Oracle don't differentiate between date/time
+ assert_equal Time, bob.favorite_day.class
+ else
+ assert_equal Date, bob.favorite_day.class
+ end
+
+ # Test DateTime column and defaults, including timezone.
+ # FIXME: moment of truth may be Time on 64-bit platforms.
+ if bob.moment_of_truth.is_a?(DateTime)
+
+ with_env_tz 'US/Eastern' do
+ assert_equal DateTime.local_offset, bob.moment_of_truth.offset
+ assert_not_equal 0, bob.moment_of_truth.offset
+ assert_not_equal "Z", bob.moment_of_truth.zone
+ # US/Eastern is -5 hours from GMT
+ assert_equal Rational(-5, 24), bob.moment_of_truth.offset
+ assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM
+ assert_equal DateTime::ITALY, bob.moment_of_truth.start
+ end
+ end
+
+ assert_equal TrueClass, bob.male?.class
+ assert_kind_of BigDecimal, bob.wealth
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_unabstracted_database_dependent_types
+ Person.delete_all
+
+ ActiveRecord::Migration.add_column :people, :intelligence_quotient, :tinyint
+ Person.reset_column_information
+ Person.create :intelligence_quotient => 300
+ jonnyg = Person.find(:first)
+ assert_equal 127, jonnyg.intelligence_quotient
+ jonnyg.destroy
+ ensure
+ ActiveRecord::Migration.remove_column :people, :intelligence_quotient rescue nil
+ end
+ end
+
+ def test_add_remove_single_field_using_string_arguments
+ assert !Person.column_methods_hash.include?(:last_name)
+
+ ActiveRecord::Migration.add_column 'people', 'last_name', :string
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+
+ ActiveRecord::Migration.remove_column 'people', 'last_name'
+
+ Person.reset_column_information
+ assert !Person.column_methods_hash.include?(:last_name)
+ end
+
+ def test_add_remove_single_field_using_symbol_arguments
+ assert !Person.column_methods_hash.include?(:last_name)
+
+ ActiveRecord::Migration.add_column :people, :last_name, :string
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+
+ ActiveRecord::Migration.remove_column :people, :last_name
+
+ Person.reset_column_information
+ assert !Person.column_methods_hash.include?(:last_name)
+ end
+
+ def test_add_rename
+ Person.delete_all
+
+ begin
+ Person.connection.add_column "people", "girlfriend", :string
+ Person.reset_column_information
+ Person.create :girlfriend => 'bobette'
+
+ Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
+
+ Person.reset_column_information
+ bob = Person.find(:first)
+
+ assert_equal "bobette", bob.exgirlfriend
+ ensure
+ Person.connection.remove_column("people", "girlfriend") rescue nil
+ Person.connection.remove_column("people", "exgirlfriend") rescue nil
+ end
+
+ end
+
+ def test_rename_column_using_symbol_arguments
+ begin
+ names_before = Person.find(:all).map(&:first_name)
+ Person.connection.rename_column :people, :first_name, :nick_name
+ Person.reset_column_information
+ assert Person.column_names.include?("nick_name")
+ assert_equal names_before, Person.find(:all).map(&:nick_name)
+ ensure
+ Person.connection.remove_column("people","nick_name")
+ Person.connection.add_column("people","first_name", :string)
+ end
+ end
+
+ def test_rename_column
+ begin
+ names_before = Person.find(:all).map(&:first_name)
+ Person.connection.rename_column "people", "first_name", "nick_name"
+ Person.reset_column_information
+ assert Person.column_names.include?("nick_name")
+ assert_equal names_before, Person.find(:all).map(&:nick_name)
+ ensure
+ Person.connection.remove_column("people","nick_name")
+ Person.connection.add_column("people","first_name", :string)
+ end
+ end
+
+ def test_rename_column_with_sql_reserved_word
+ begin
+ assert_nothing_raised { Person.connection.rename_column "people", "first_name", "group" }
+ Person.reset_column_information
+ assert Person.column_names.include?("group")
+ ensure
+ Person.connection.remove_column("people", "group") rescue nil
+ Person.connection.add_column("people", "first_name", :string) rescue nil
+ end
+ end
+
+ def test_rename_column_with_an_index
+ ActiveRecord::Base.connection.create_table(:hats) do |table|
+ table.column :hat_name, :string, :limit => 100
+ table.column :hat_size, :integer
+ end
+ Person.connection.add_index :hats, :hat_name
+ assert_nothing_raised do
+ Person.connection.rename_column "hats", "hat_name", "name"
+ end
+ ensure
+ ActiveRecord::Base.connection.drop_table(:hats)
+ end
+
+ def test_remove_column_with_index
+ ActiveRecord::Base.connection.create_table(:hats) do |table|
+ table.column :hat_name, :string, :limit => 100
+ table.column :hat_size, :integer
+ end
+ ActiveRecord::Base.connection.add_index "hats", "hat_size"
+
+ assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
+ ensure
+ ActiveRecord::Base.connection.drop_table(:hats)
+ end
+
+ def test_remove_column_with_multi_column_index
+ ActiveRecord::Base.connection.create_table(:hats) do |table|
+ table.column :hat_name, :string, :limit => 100
+ table.column :hat_size, :integer
+ table.column :hat_style, :string, :limit => 100
+ end
+ ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true
+
+ assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") }
+ ensure
+ ActiveRecord::Base.connection.drop_table(:hats)
+ end
+
+ def test_change_type_of_not_null_column
+ assert_nothing_raised do
+ Topic.connection.change_column "topics", "written_on", :datetime, :null => false
+ Topic.reset_column_information
+
+ Topic.connection.change_column "topics", "written_on", :datetime, :null => false
+ Topic.reset_column_information
+ end
+ end
+
+ def test_rename_table
+ begin
+ ActiveRecord::Base.connection.create_table :octopuses do |t|
+ t.column :url, :string
+ end
+ ActiveRecord::Base.connection.rename_table :octopuses, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = ActiveRecord::Base.connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+ assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
+
+ ensure
+ ActiveRecord::Base.connection.drop_table :octopuses rescue nil
+ ActiveRecord::Base.connection.drop_table :octopi rescue nil
+ end
+ end
+
+ def test_change_column_nullability
+ Person.delete_all
+ Person.connection.add_column "people", "funny", :boolean
+ Person.reset_column_information
+ assert Person.columns_hash["funny"].null, "Column 'funny' must initially allow nulls"
+ Person.connection.change_column "people", "funny", :boolean, :null => false, :default => true
+ Person.reset_column_information
+ assert !Person.columns_hash["funny"].null, "Column 'funny' must *not* allow nulls at this point"
+ Person.connection.change_column "people", "funny", :boolean, :null => true
+ Person.reset_column_information
+ assert Person.columns_hash["funny"].null, "Column 'funny' must allow nulls again at this point"
+ end
+
+ def test_rename_table_with_an_index
+ begin
+ ActiveRecord::Base.connection.create_table :octopuses do |t|
+ t.column :url, :string
+ end
+ ActiveRecord::Base.connection.add_index :octopuses, :url
+
+ ActiveRecord::Base.connection.rename_table :octopuses, :octopi
+
+ # Using explicit id in insert for compatibility across all databases
+ con = ActiveRecord::Base.connection
+ con.enable_identity_insert("octopi", true) if current_adapter?(:SybaseAdapter)
+ assert_nothing_raised { con.execute "INSERT INTO octopi (#{con.quote_column_name('id')}, #{con.quote_column_name('url')}) VALUES (1, 'http://www.foreverflying.com/octopus-black7.jpg')" }
+ con.enable_identity_insert("octopi", false) if current_adapter?(:SybaseAdapter)
+
+ assert_equal 'http://www.foreverflying.com/octopus-black7.jpg', ActiveRecord::Base.connection.select_value("SELECT url FROM octopi WHERE id=1")
+ assert ActiveRecord::Base.connection.indexes(:octopi).first.columns.include?("url")
+ ensure
+ ActiveRecord::Base.connection.drop_table :octopuses rescue nil
+ ActiveRecord::Base.connection.drop_table :octopi rescue nil
+ end
+ end
+
+ def test_change_column
+ Person.connection.add_column 'people', 'age', :integer
+ old_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
+ assert old_columns.find { |c| c.name == 'age' and c.type == :integer }
+
+ assert_nothing_raised { Person.connection.change_column "people", "age", :string }
+
+ new_columns = Person.connection.columns(Person.table_name, "#{name} Columns")
+ assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer }
+ assert new_columns.find { |c| c.name == 'age' and c.type == :string }
+
+ old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
+ assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
+ assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false }
+ new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")
+ assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true }
+ assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false }
+ assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true }
+ end
+
+ def test_change_column_with_nil_default
+ Person.connection.add_column "people", "contributor", :boolean, :default => true
+ Person.reset_column_information
+ assert Person.new.contributor?
+
+ assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil }
+ Person.reset_column_information
+ assert !Person.new.contributor?
+ assert_nil Person.new.contributor
+ ensure
+ Person.connection.remove_column("people", "contributor") rescue nil
+ end
+
+ def test_change_column_with_new_default
+ Person.connection.add_column "people", "administrator", :boolean, :default => true
+ Person.reset_column_information
+ assert Person.new.administrator?
+
+ assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => false }
+ Person.reset_column_information
+ assert !Person.new.administrator?
+ ensure
+ Person.connection.remove_column("people", "administrator") rescue nil
+ end
+
+ def test_change_column_default
+ Person.connection.change_column_default "people", "first_name", "Tester"
+ Person.reset_column_information
+ assert_equal "Tester", Person.new.first_name
+ end
+
+ def test_change_column_quotes_column_names
+ Person.connection.create_table :testings do |t|
+ t.column :select, :string
+ end
+
+ assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 }
+
+ assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" }
+ ensure
+ Person.connection.drop_table :testings rescue nil
+ end
+
+ def test_change_column_default_to_null
+ Person.connection.change_column_default "people", "first_name", nil
+ Person.reset_column_information
+ assert_nil Person.new.first_name
+ end
+
+ def test_add_table
+ assert !Reminder.table_exists?
+
+ WeNeedReminders.up
+
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+
+ WeNeedReminders.down
+ assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ end
+
+ def test_add_table_with_decimals
+ Person.connection.drop_table :big_numbers rescue nil
+
+ assert !BigNumber.table_exists?
+ GiveMeBigNumbers.up
+
+ assert BigNumber.create(
+ :bank_balance => 1586.43,
+ :big_bank_balance => BigDecimal("1000234000567.95"),
+ :world_population => 6000000000,
+ :my_house_population => 3,
+ :value_of_e => BigDecimal("2.7182818284590452353602875")
+ )
+
+ b = BigNumber.find(:first)
+ assert_not_nil b
+
+ assert_not_nil b.bank_balance
+ assert_not_nil b.big_bank_balance
+ assert_not_nil b.world_population
+ assert_not_nil b.my_house_population
+ assert_not_nil b.value_of_e
+
+ # TODO: set world_population >= 2**62 to cover 64-bit platforms and test
+ # is_a?(Bignum)
+ assert_kind_of Integer, b.world_population
+ assert_equal 6000000000, b.world_population
+ assert_kind_of Fixnum, b.my_house_population
+ assert_equal 3, b.my_house_population
+ assert_kind_of BigDecimal, b.bank_balance
+ assert_equal BigDecimal("1586.43"), b.bank_balance
+ assert_kind_of BigDecimal, b.big_bank_balance
+ assert_equal BigDecimal("1000234000567.95"), b.big_bank_balance
+
+ # This one is fun. The 'value_of_e' field is defined as 'DECIMAL' with
+ # precision/scale explicitly left out. By the SQL standard, numbers
+ # assigned to this field should be truncated but that's seldom respected.
+ if current_adapter?(:PostgreSQLAdapter, :SQLite2Adapter)
+ # - PostgreSQL changes the SQL spec on columns declared simply as
+ # "decimal" to something more useful: instead of being given a scale
+ # of 0, they take on the compile-time limit for precision and scale,
+ # so the following should succeed unless you have used really wacky
+ # compilation options
+ # - SQLite2 has the default behavior of preserving all data sent in,
+ # so this happens there too
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e
+ elsif current_adapter?(:SQLiteAdapter)
+ # - SQLite3 stores a float, in violation of SQL
+ assert_kind_of BigDecimal, b.value_of_e
+ assert_equal BigDecimal("2.71828182845905"), b.value_of_e
+ elsif current_adapter?(:SQLServer)
+ # - SQL Server rounds instead of truncating
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 3, b.value_of_e
+ else
+ # - SQL standard is an integer
+ assert_kind_of Fixnum, b.value_of_e
+ assert_equal 2, b.value_of_e
+ end
+
+ GiveMeBigNumbers.down
+ assert_raises(ActiveRecord::StatementInvalid) { BigNumber.find(:first) }
+ end
+
+ def test_migrator
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
+
+ assert_equal 3, ActiveRecord::Migrator.current_version
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid")
+
+ assert_equal 0, ActiveRecord::Migrator.current_version
+ Person.reset_column_information
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ end
+
+ def test_migrator_one_up
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 2)
+
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+ end
+
+ def test_migrator_one_down
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
+
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+ end
+
+ def test_migrator_one_up_one_down
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
+
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+ end
+
+ def test_finds_migrations
+ migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/valid").migrations
+ [['1', 'people_have_last_names'],
+ ['2', 'we_need_reminders'],
+ ['3', 'innocent_jointable']].each_with_index do |pair, i|
+ migrations[i].version == pair.first
+ migrations[1].name == pair.last
+ end
+ end
+
+ def test_finds_pending_migrations
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2", 1)
+ migrations = ActiveRecord::Migrator.new(:up, MIGRATIONS_ROOT + "/interleaved/pass_2").pending_migrations
+ assert_equal 1, migrations.size
+ migrations[0].version == '3'
+ migrations[0].name == 'innocent_jointable'
+ end
+
+ def test_migrator_interleaved_migrations
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1")
+
+ assert_nothing_raised do
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2")
+ end
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+
+ assert_nothing_raised do
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3")
+ end
+ end
+
+ def test_migrator_db_has_no_schema_migrations_table
+ ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;")
+ assert_nothing_raised do
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1)
+ end
+ end
+
+ def test_migrator_verbosity
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
+ assert PeopleHaveLastNames.message_count > 0
+ PeopleHaveLastNames.message_count = 0
+
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
+ assert PeopleHaveLastNames.message_count > 0
+ PeopleHaveLastNames.message_count = 0
+ end
+
+ def test_migrator_verbosity_off
+ PeopleHaveLastNames.verbose = false
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
+ assert PeopleHaveLastNames.message_count.zero?
+ ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 0)
+ assert PeopleHaveLastNames.message_count.zero?
+ end
+
+ def test_migrator_going_down_due_to_version_target
+ ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1)
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 0)
+
+ assert !Person.column_methods_hash.include?(:last_name)
+ assert !Reminder.table_exists?
+
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
+
+ Person.reset_column_information
+ assert Person.column_methods_hash.include?(:last_name)
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+ end
+
+ def test_migrator_rollback
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
+ assert_equal(3, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal(2, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal(1, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_migrator_run
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
+ assert_equal(0, ActiveRecord::Migrator.current_version)
+ end
+
+ def test_schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name
+ ensure
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ end
+
+ def test_proper_table_name
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table)
+ assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.reset_table_name
+ assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder)
+
+ # Use the model's own prefix/suffix if a model is given
+ ActiveRecord::Base.table_name_prefix = "ARprefix_"
+ ActiveRecord::Base.table_name_suffix = "_ARsuffix"
+ Reminder.table_name_prefix = 'prefix_'
+ Reminder.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder)
+ Reminder.table_name_prefix = ''
+ Reminder.table_name_suffix = ''
+ Reminder.reset_table_name
+
+ # Use AR::Base's prefix/suffix if string or symbol is given
+ ActiveRecord::Base.table_name_prefix = "prefix_"
+ ActiveRecord::Base.table_name_suffix = "_suffix"
+ Reminder.reset_table_name
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table')
+ assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table)
+ ActiveRecord::Base.table_name_prefix = ""
+ ActiveRecord::Base.table_name_suffix = ""
+ Reminder.reset_table_name
+ end
+
+ def test_add_drop_table_with_prefix_and_suffix
+ assert !Reminder.table_exists?
+ ActiveRecord::Base.table_name_prefix = 'prefix_'
+ ActiveRecord::Base.table_name_suffix = '_suffix'
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ WeNeedReminders.up
+ assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
+ assert_equal "hello world", Reminder.find(:first).content
+
+ WeNeedReminders.down
+ assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
+ ensure
+ ActiveRecord::Base.table_name_prefix = ''
+ ActiveRecord::Base.table_name_suffix = ''
+ Reminder.reset_table_name
+ Reminder.reset_sequence_name
+ end
+
+ def test_create_table_with_binary_column
+ Person.connection.drop_table :binary_testings rescue nil
+
+ assert_nothing_raised {
+ Person.connection.create_table :binary_testings do |t|
+ t.column "data", :binary, :null => false
+ end
+ }
+
+ columns = Person.connection.columns(:binary_testings)
+ data_column = columns.detect { |c| c.name == "data" }
+
+ assert_nil data_column.default
+
+ Person.connection.drop_table :binary_testings rescue nil
+ end
+
+ def test_migrator_with_duplicates
+ assert_raises(ActiveRecord::DuplicateMigrationVersionError) do
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate", nil)
+ end
+ end
+
+ def test_migrator_with_duplicate_names
+ assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/duplicate_names", nil)
+ end
+ end
+
+ def test_migrator_with_missing_version_numbers
+ assert_raise(ActiveRecord::UnknownMigrationVersionError) do
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
+ end
+ end
+
+ def test_create_table_with_custom_sequence_name
+ return unless current_adapter? :OracleAdapter
+
+ # table name is 29 chars, the standard sequence name will
+ # be 33 chars and fail
+ assert_raises(ActiveRecord::StatementInvalid) do
+ begin
+ Person.connection.create_table :table_with_name_thats_just_ok do |t|
+ t.column :foo, :string, :null => false
+ end
+ ensure
+ Person.connection.drop_table :table_with_name_thats_just_ok rescue nil
+ end
+ end
+
+ # should be all good w/ a custom sequence name
+ assert_nothing_raised do
+ begin
+ Person.connection.create_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' do |t|
+ t.column :foo, :string, :null => false
+ end
+
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
+
+ ensure
+ Person.connection.drop_table :table_with_name_thats_just_ok,
+ :sequence_name => 'suitably_short_seq' rescue nil
+ end
+ end
+
+ # confirm the custom sequence got dropped
+ assert_raises(ActiveRecord::StatementInvalid) do
+ Person.connection.execute("select suitably_short_seq.nextval from dual")
+ end
+ end
+
+ protected
+ def with_env_tz(new_tz = 'US/Eastern')
+ old_tz, ENV['TZ'] = ENV['TZ'], new_tz
+ yield
+ ensure
+ old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
+ end
+
+ end
+
+ uses_mocha 'Sexy migration tests' do
+ class SexyMigrationsTest < ActiveRecord::TestCase
+ def test_references_column_type_adds_id
+ with_new_table do |t|
+ t.expects(:column).with('customer_id', :integer, {})
+ t.references :customer
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_adds_type
+ with_new_table do |t|
+ t.expects(:column).with('taggable_type', :string, {})
+ t.expects(:column).with('taggable_id', :integer, {})
+ t.references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_new_table do |t|
+ t.expects(:column).with('taggable_type', :string, {:null => false})
+ t.expects(:column).with('taggable_id', :integer, {:null => false})
+ t.references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_belongs_to_works_like_references
+ with_new_table do |t|
+ t.expects(:column).with('customer_id', :integer, {})
+ t.belongs_to :customer
+ end
+ end
+
+ def test_timestamps_creates_updated_at_and_created_at
+ with_new_table do |t|
+ t.expects(:column).with(:created_at, :datetime)
+ t.expects(:column).with(:updated_at, :datetime)
+ t.timestamps
+ end
+ end
+
+ def test_integer_creates_integer_column
+ with_new_table do |t|
+ t.expects(:column).with(:foo, 'integer', {})
+ t.expects(:column).with(:bar, 'integer', {})
+ t.integer :foo, :bar
+ end
+ end
+
+ def test_string_creates_string_column
+ with_new_table do |t|
+ t.expects(:column).with(:foo, 'string', {})
+ t.expects(:column).with(:bar, 'string', {})
+ t.string :foo, :bar
+ end
+ end
+
+ protected
+ def with_new_table
+ Person.connection.create_table :delete_me, :force => true do |t|
+ yield t
+ end
+ ensure
+ Person.connection.drop_table :delete_me rescue nil
+ end
+
+ end # SexyMigrationsTest
+ end # uses_mocha
+
+ uses_mocha 'ChangeTable migration tests' do
+ class ChangeTableMigrationsTest < ActiveRecord::TestCase
+ def setup
+ @connection = Person.connection
+ @connection.create_table :delete_me, :force => true do |t|
+ end
+ end
+
+ def teardown
+ Person.connection.drop_table :delete_me rescue nil
+ end
+
+ def test_references_column_type_adds_id
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+ t.references :customer
+ end
+ end
+
+ def test_remove_references_column_type_removes_id
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+ t.remove_references :customer
+ end
+ end
+
+ def test_add_belongs_to_works_like_add_references
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+ t.belongs_to :customer
+ end
+ end
+
+ def test_remove_belongs_to_works_like_remove_references
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+ t.remove_belongs_to :customer
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_adds_type
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
+ @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
+ t.references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_removes_type
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+ t.remove_references :taggable, :polymorphic => true
+ end
+ end
+
+ def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null => false})
+ @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
+ t.references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+ @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+ t.remove_references :taggable, :polymorphic => true, :null => false
+ end
+ end
+
+ def test_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expects(:add_timestamps).with(:delete_me)
+ t.timestamps
+ end
+ end
+
+ def test_remove_timestamps_creates_updated_at_and_created_at
+ with_change_table do |t|
+ @connection.expects(:remove_timestamps).with(:delete_me)
+ t.remove_timestamps
+ end
+ end
+
+ def string_column
+ if current_adapter?(:PostgreSQLAdapter)
+ "character varying(255)"
+ else
+ 'varchar(255)'
+ end
+ end
+
+ def integer_column
+ if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
+ "integer"
+ else
+ 'int(11)'
+ end
+ end
+
+ def test_integer_creates_integer_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
+ @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
+ t.integer :foo, :bar
+ end
+ end
+
+ def test_string_creates_string_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
+ @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
+ t.string :foo, :bar
+ end
+ end
+
+ def test_column_creates_column
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
+ t.column :bar, :integer
+ end
+ end
+
+ def test_column_creates_column_with_options
+ with_change_table do |t|
+ @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null => false})
+ t.column :bar, :integer, :null => false
+ end
+ end
+
+ def test_index_creates_index
+ with_change_table do |t|
+ @connection.expects(:add_index).with(:delete_me, :bar, {})
+ t.index :bar
+ end
+ end
+
+ def test_index_creates_index_with_options
+ with_change_table do |t|
+ @connection.expects(:add_index).with(:delete_me, :bar, {:unique => true})
+ t.index :bar, :unique => true
+ end
+ end
+
+ def test_change_changes_column
+ with_change_table do |t|
+ @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
+ t.change :bar, :string
+ end
+ end
+
+ def test_change_changes_column_with_options
+ with_change_table do |t|
+ @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null => true})
+ t.change :bar, :string, :null => true
+ end
+ end
+
+ def test_change_default_changes_column
+ with_change_table do |t|
+ @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
+ t.change_default :bar, :string
+ end
+ end
+
+ def test_remove_drops_single_column
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, [:bar])
+ t.remove :bar
+ end
+ end
+
+ def test_remove_drops_multiple_columns
+ with_change_table do |t|
+ @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
+ t.remove :bar, :baz
+ end
+ end
+
+ def test_remove_index_removes_index_with_options
+ with_change_table do |t|
+ @connection.expects(:remove_index).with(:delete_me, {:unique => true})
+ t.remove_index :unique => true
+ end
+ end
+
+ def test_rename_renames_column
+ with_change_table do |t|
+ @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
+ t.rename :bar, :baz
+ end
+ end
+
+ protected
+ def with_change_table
+ Person.connection.change_table :delete_me do |t|
+ yield t
+ end
+ end
+
+ end # ChangeTable test
+ end # uses_mocha
+
+end
diff --git a/vendor/rails/activerecord/test/cases/migration_test_firebird.rb b/vendor/rails/activerecord/test/cases/migration_test_firebird.rb
new file mode 100644
index 0000000..710661b
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/migration_test_firebird.rb
@@ -0,0 +1,124 @@
+require "cases/helper"
+require 'models/course'
+
+class FirebirdMigrationTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ # using Course connection for tests -- need a db that doesn't already have a BOOLEAN domain
+ @connection = Course.connection
+ @fireruby_connection = @connection.instance_variable_get(:@connection)
+ end
+
+ def teardown
+ @connection.drop_table :foo rescue nil
+ @connection.execute("DROP DOMAIN D_BOOLEAN") rescue nil
+ end
+
+ def test_create_table_with_custom_sequence_name
+ assert_nothing_raised do
+ @connection.create_table(:foo, :sequence => 'foo_custom_seq') do |f|
+ f.column :bar, :string
+ end
+ end
+ assert !sequence_exists?('foo_seq')
+ assert sequence_exists?('foo_custom_seq')
+
+ assert_nothing_raised { @connection.drop_table(:foo, :sequence => 'foo_custom_seq') }
+ assert !sequence_exists?('foo_custom_seq')
+ ensure
+ FireRuby::Generator.new('foo_custom_seq', @fireruby_connection).drop rescue nil
+ end
+
+ def test_create_table_without_sequence
+ assert_nothing_raised do
+ @connection.create_table(:foo, :sequence => false) do |f|
+ f.column :bar, :string
+ end
+ end
+ assert !sequence_exists?('foo_seq')
+ assert_nothing_raised { @connection.drop_table :foo }
+
+ assert_nothing_raised do
+ @connection.create_table(:foo, :id => false) do |f|
+ f.column :bar, :string
+ end
+ end
+ assert !sequence_exists?('foo_seq')
+ assert_nothing_raised { @connection.drop_table :foo }
+ end
+
+ def test_create_table_with_boolean_column
+ assert !boolean_domain_exists?
+ assert_nothing_raised do
+ @connection.create_table :foo do |f|
+ f.column :bar, :string
+ f.column :baz, :boolean
+ end
+ end
+ assert boolean_domain_exists?
+ end
+
+ def test_add_boolean_column
+ assert !boolean_domain_exists?
+ @connection.create_table :foo do |f|
+ f.column :bar, :string
+ end
+
+ assert_nothing_raised { @connection.add_column :foo, :baz, :boolean }
+ assert boolean_domain_exists?
+ assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "baz" }.type
+ end
+
+ def test_change_column_to_boolean
+ assert !boolean_domain_exists?
+ # Manually create table with a SMALLINT column, which can be changed to a BOOLEAN
+ @connection.execute "CREATE TABLE foo (bar SMALLINT)"
+ assert_equal :integer, @connection.columns(:foo).find { |c| c.name == "bar" }.type
+
+ assert_nothing_raised { @connection.change_column :foo, :bar, :boolean }
+ assert boolean_domain_exists?
+ assert_equal :boolean, @connection.columns(:foo).find { |c| c.name == "bar" }.type
+ end
+
+ def test_rename_table_with_data_and_index
+ @connection.create_table :foo do |f|
+ f.column :baz, :string, :limit => 50
+ end
+ 100.times { |i| @connection.execute "INSERT INTO foo VALUES (GEN_ID(foo_seq, 1), 'record #{i+1}')" }
+ @connection.add_index :foo, :baz
+
+ assert_nothing_raised { @connection.rename_table :foo, :bar }
+ assert !@connection.tables.include?("foo")
+ assert @connection.tables.include?("bar")
+ assert_equal "index_bar_on_baz", @connection.indexes("bar").first.name
+ assert_equal 100, FireRuby::Generator.new("bar_seq", @fireruby_connection).last
+ assert_equal 100, @connection.select_one("SELECT COUNT(*) FROM bar")["count"]
+ ensure
+ @connection.drop_table :bar rescue nil
+ end
+
+ def test_renaming_table_with_fk_constraint_raises_error
+ @connection.create_table :parent do |p|
+ p.column :name, :string
+ end
+ @connection.create_table :child do |c|
+ c.column :parent_id, :integer
+ end
+ @connection.execute "ALTER TABLE child ADD CONSTRAINT fk_child_parent FOREIGN KEY(parent_id) REFERENCES parent(id)"
+ assert_raise(ActiveRecord::ActiveRecordError) { @connection.rename_table :child, :descendant }
+ ensure
+ @connection.drop_table :child rescue nil
+ @connection.drop_table :descendant rescue nil
+ @connection.drop_table :parent rescue nil
+ end
+
+ private
+ def boolean_domain_exists?
+ !@connection.select_one("SELECT 1 FROM rdb$fields WHERE rdb$field_name = 'D_BOOLEAN'").nil?
+ end
+
+ def sequence_exists?(sequence_name)
+ FireRuby::Generator.exists?(sequence_name, @fireruby_connection)
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/mixin_test.rb b/vendor/rails/activerecord/test/cases/mixin_test.rb
new file mode 100644
index 0000000..f927c13
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/mixin_test.rb
@@ -0,0 +1,96 @@
+require "cases/helper"
+
+class Mixin < ActiveRecord::Base
+end
+
+# Let us control what Time.now returns for the TouchTest suite
+class Time
+ @@forced_now_time = nil
+ cattr_accessor :forced_now_time
+
+ class << self
+ def now_with_forcing
+ if @@forced_now_time
+ @@forced_now_time
+ else
+ now_without_forcing
+ end
+ end
+ alias_method_chain :now, :forcing
+ end
+end
+
+
+class TouchTest < ActiveRecord::TestCase
+ fixtures :mixins
+
+ def setup
+ Time.forced_now_time = Time.now
+ end
+
+ def teardown
+ Time.forced_now_time = nil
+ end
+
+ def test_time_mocking
+ five_minutes_ago = 5.minutes.ago
+ Time.forced_now_time = five_minutes_ago
+ assert_equal five_minutes_ago, Time.now
+
+ Time.forced_now_time = nil
+ assert_not_equal five_minutes_ago, Time.now
+ end
+
+ def test_update
+ stamped = Mixin.new
+
+ assert_nil stamped.updated_at
+ assert_nil stamped.created_at
+ stamped.save
+ assert_equal Time.now, stamped.updated_at
+ assert_equal Time.now, stamped.created_at
+ end
+
+ def test_create
+ obj = Mixin.create
+ assert_equal Time.now, obj.updated_at
+ assert_equal Time.now, obj.created_at
+ end
+
+ def test_many_updates
+ stamped = Mixin.new
+
+ assert_nil stamped.updated_at
+ assert_nil stamped.created_at
+ stamped.save
+ assert_equal Time.now, stamped.created_at
+ assert_equal Time.now, stamped.updated_at
+
+ old_updated_at = stamped.updated_at
+
+ Time.forced_now_time = 5.minutes.from_now
+ stamped.lft_will_change!
+ stamped.save
+
+ assert_equal Time.now, stamped.updated_at
+ assert_equal old_updated_at, stamped.created_at
+ end
+
+ def test_create_turned_off
+ Mixin.record_timestamps = false
+
+ mixin = Mixin.new
+
+ assert_nil mixin.updated_at
+ mixin.save
+ assert_nil mixin.updated_at
+
+ # Make sure Mixin.record_timestamps gets reset, even if this test fails,
+ # so that other tests do not fail because Mixin.record_timestamps == false
+ rescue Exception => e
+ raise e
+ ensure
+ Mixin.record_timestamps = true
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/modules_test.rb b/vendor/rails/activerecord/test/cases/modules_test.rb
new file mode 100644
index 0000000..283333f
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/modules_test.rb
@@ -0,0 +1,39 @@
+require "cases/helper"
+require 'models/company_in_module'
+
+class ModulesTest < ActiveRecord::TestCase
+ fixtures :accounts, :companies, :projects, :developers
+
+ def test_module_spanning_associations
+ firm = MyApplication::Business::Firm.find(:first)
+ assert !firm.clients.empty?, "Firm should have clients"
+ assert_nil firm.class.table_name.match('::'), "Firm shouldn't have the module appear in its table name"
+ end
+
+ def test_module_spanning_has_and_belongs_to_many_associations
+ project = MyApplication::Business::Project.find(:first)
+ project.developers << MyApplication::Business::Developer.create("name" => "John")
+ assert "John", project.developers.last.name
+ end
+
+ def test_associations_spanning_cross_modules
+ account = MyApplication::Billing::Account.find(:first, :order => 'id')
+ assert_kind_of MyApplication::Business::Firm, account.firm
+ assert_kind_of MyApplication::Billing::Firm, account.qualified_billing_firm
+ assert_kind_of MyApplication::Billing::Firm, account.unqualified_billing_firm
+ assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_qualified_billing_firm
+ assert_kind_of MyApplication::Billing::Nested::Firm, account.nested_unqualified_billing_firm
+ end
+
+ 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
+
+ def test_table_name
+ assert_equal 'accounts', MyApplication::Billing::Account.table_name, 'table_name for ActiveRecord model in module'
+ assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass'
+ assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model'
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/multiple_db_test.rb b/vendor/rails/activerecord/test/cases/multiple_db_test.rb
new file mode 100644
index 0000000..236e471
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/multiple_db_test.rb
@@ -0,0 +1,60 @@
+require "cases/helper"
+require 'models/entrant'
+
+# So we can test whether Course.connection survives a reload.
+require_dependency 'models/course'
+
+class MultipleDbTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @courses = create_fixtures("courses") { Course.retrieve_connection }
+ @entrants = create_fixtures("entrants")
+ end
+
+ def test_connected
+ assert_not_nil Entrant.connection
+ assert_not_nil Course.connection
+ end
+
+ def test_proper_connection
+ assert_not_equal(Entrant.connection, Course.connection)
+ assert_equal(Entrant.connection, Entrant.retrieve_connection)
+ assert_equal(Course.connection, Course.retrieve_connection)
+ assert_equal(ActiveRecord::Base.connection, Entrant.connection)
+ end
+
+ def test_find
+ c1 = Course.find(1)
+ assert_equal "Ruby Development", c1.name
+ c2 = Course.find(2)
+ assert_equal "Java Development", c2.name
+ e1 = Entrant.find(1)
+ assert_equal "Ruby Developer", e1.name
+ e2 = Entrant.find(2)
+ assert_equal "Ruby Guru", e2.name
+ e3 = Entrant.find(3)
+ assert_equal "Java Lover", e3.name
+ end
+
+ def test_associations
+ c1 = Course.find(1)
+ assert_equal 2, c1.entrants.count
+ e1 = Entrant.find(1)
+ assert_equal e1.course.id, c1.id
+ c2 = Course.find(2)
+ assert_equal 1, c2.entrants.count
+ e3 = Entrant.find(3)
+ assert_equal e3.course.id, c2.id
+ end
+
+ def test_course_connection_should_survive_dependency_reload
+ assert Course.connection
+
+ Dependencies.clear
+ Object.send(:remove_const, :Course)
+ require_dependency 'models/course'
+
+ assert Course.connection
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/named_scope_test.rb b/vendor/rails/activerecord/test/cases/named_scope_test.rb
new file mode 100644
index 0000000..d890cf7
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/named_scope_test.rb
@@ -0,0 +1,157 @@
+require "cases/helper"
+require 'models/post'
+require 'models/topic'
+require 'models/comment'
+require 'models/reply'
+require 'models/author'
+
+class NamedScopeTest < ActiveRecord::TestCase
+ fixtures :posts, :authors, :topics, :comments
+
+ def test_implements_enumerable
+ assert !Topic.find(:all).empty?
+
+ assert_equal Topic.find(:all), Topic.base
+ assert_equal Topic.find(:all), Topic.base.to_a
+ assert_equal Topic.find(:first), Topic.base.first
+ assert_equal Topic.find(:all), Topic.base.each { |i| i }
+ end
+
+ def test_found_items_are_cached
+ Topic.columns
+ all_posts = Topic.base
+
+ assert_queries(1) do
+ all_posts.collect
+ all_posts.collect
+ end
+ end
+
+ def test_reload_expires_cache_of_found_items
+ all_posts = Topic.base
+ all_posts.inspect
+
+ new_post = Topic.create!
+ assert !all_posts.include?(new_post)
+ assert all_posts.reload.include?(new_post)
+ end
+
+ def test_delegates_finds_and_calculations_to_the_base_class
+ assert !Topic.find(:all).empty?
+
+ assert_equal Topic.find(:all), Topic.base.find(:all)
+ assert_equal Topic.find(:first), Topic.base.find(:first)
+ assert_equal Topic.count, Topic.base.count
+ assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count)
+ end
+
+ def test_subclasses_inherit_scopes
+ assert Topic.scopes.include?(:base)
+
+ assert Reply.scopes.include?(:base)
+ assert_equal Reply.find(:all), Reply.base
+ end
+
+ def test_scopes_with_options_limit_finds_to_those_matching_the_criteria_specified
+ assert !Topic.find(:all, :conditions => {:approved => true}).empty?
+
+ assert_equal Topic.find(:all, :conditions => {:approved => true}), Topic.approved
+ assert_equal Topic.count(:conditions => {:approved => true}), Topic.approved.count
+ end
+
+ def test_scopes_are_composable
+ assert_equal (approved = Topic.find(:all, :conditions => {:approved => true})), Topic.approved
+ assert_equal (replied = Topic.find(:all, :conditions => 'replies_count > 0')), Topic.replied
+ assert !(approved == replied)
+ assert !(approved & replied).empty?
+
+ assert_equal approved & replied, Topic.approved.replied
+ end
+
+ def test_procedural_scopes
+ topics_written_before_the_third = Topic.find(:all, :conditions => ['written_on < ?', topics(:third).written_on])
+ topics_written_before_the_second = Topic.find(:all, :conditions => ['written_on < ?', topics(:second).written_on])
+ assert_not_equal topics_written_before_the_second, topics_written_before_the_third
+
+ assert_equal topics_written_before_the_third, Topic.written_before(topics(:third).written_on)
+ assert_equal topics_written_before_the_second, Topic.written_before(topics(:second).written_on)
+ end
+
+ def test_extensions
+ assert_equal 1, Topic.anonymous_extension.one
+ assert_equal 2, Topic.named_extension.two
+ end
+
+ def test_multiple_extensions
+ assert_equal 2, Topic.multiple_extensions.extension_two
+ assert_equal 1, Topic.multiple_extensions.extension_one
+ end
+
+ def test_has_many_associations_have_access_to_named_scopes
+ assert_not_equal Post.containing_the_letter_a, authors(:david).posts
+ assert !Post.containing_the_letter_a.empty?
+
+ assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a
+ end
+
+ def test_has_many_through_associations_have_access_to_named_scopes
+ assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
+ assert !Comment.containing_the_letter_e.empty?
+
+ assert_equal authors(:david).comments & Comment.containing_the_letter_e, authors(:david).comments.containing_the_letter_e
+ end
+
+ def test_active_records_have_scope_named__all__
+ assert !Topic.find(:all).empty?
+
+ assert_equal Topic.find(:all), Topic.base
+ end
+
+ def test_active_records_have_scope_named__scoped__
+ assert !Topic.find(:all, scope = {:conditions => "content LIKE '%Have%'"}).empty?
+
+ assert_equal Topic.find(:all, scope), Topic.scoped(scope)
+ end
+
+ def test_proxy_options
+ expected_proxy_options = { :conditions => { :approved => true } }
+ assert_equal expected_proxy_options, Topic.approved.proxy_options
+ end
+
+ def test_first_and_last_should_support_find_options
+ assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
+ assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
+ end
+
+ def test_first_and_last_should_allow_integers_for_limit
+ assert_equal Topic.base.first(2), Topic.base.to_a.first(2)
+ assert_equal Topic.base.last(2), Topic.base.to_a.last(2)
+ end
+
+ def test_first_and_last_should_not_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_no_queries do
+ topics.first
+ topics.last
+ end
+ end
+
+ def test_first_and_last_find_options_should_use_query_when_results_are_loaded
+ topics = Topic.base
+ topics.reload # force load
+ assert_queries(2) do
+ topics.first(:order => 'title')
+ topics.last(:order => 'title')
+ end
+ end
+
+ def test_empty_should_not_load_results
+ topics = Topic.base
+ assert_queries(2) do
+ topics.empty? # use count query
+ topics.collect # force load
+ topics.empty? # use loaded (no query)
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/pk_test.rb b/vendor/rails/activerecord/test/cases/pk_test.rb
new file mode 100644
index 0000000..948a570
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/pk_test.rb
@@ -0,0 +1,101 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/subscriber'
+require 'models/movie'
+require 'models/keyboard'
+require 'models/mixed_case_monkey'
+
+class PrimaryKeysTest < ActiveRecord::TestCase
+ fixtures :topics, :subscribers, :movies, :mixed_case_monkeys
+
+ def test_integer_key
+ topic = Topic.find(1)
+ assert_equal(topics(:first).author_name, topic.author_name)
+ topic = Topic.find(2)
+ assert_equal(topics(:second).author_name, topic.author_name)
+
+ topic = Topic.new
+ topic.title = "New Topic"
+ assert_equal(nil, topic.id)
+ assert_nothing_raised { topic.save! }
+ id = topic.id
+
+ topicReloaded = Topic.find(id)
+ assert_equal("New Topic", topicReloaded.title)
+ end
+
+ def test_customized_primary_key_auto_assigns_on_save
+ Keyboard.delete_all
+ keyboard = Keyboard.new(:name => 'HHKB')
+ assert_nothing_raised { keyboard.save! }
+ assert_equal keyboard.id, Keyboard.find_by_name('HHKB').id
+ end
+
+ def test_customized_primary_key_can_be_get_before_saving
+ keyboard = Keyboard.new
+ assert_nil keyboard.id
+ assert_nothing_raised { assert_nil keyboard.key_number }
+ end
+
+ def test_customized_string_primary_key_settable_before_save
+ subscriber = Subscriber.new
+ assert_nothing_raised { subscriber.id = 'webster123' }
+ assert_equal 'webster123', subscriber.id
+ assert_equal 'webster123', subscriber.nick
+ end
+
+ def test_string_key
+ subscriber = Subscriber.find(subscribers(:first).nick)
+ assert_equal(subscribers(:first).name, subscriber.name)
+ subscriber = Subscriber.find(subscribers(:second).nick)
+ assert_equal(subscribers(:second).name, subscriber.name)
+
+ subscriber = Subscriber.new
+ subscriber.id = "jdoe"
+ assert_equal("jdoe", subscriber.id)
+ subscriber.name = "John Doe"
+ assert_nothing_raised { subscriber.save! }
+ assert_equal("jdoe", subscriber.id)
+
+ subscriberReloaded = Subscriber.find("jdoe")
+ assert_equal("John Doe", subscriberReloaded.name)
+ end
+
+ def test_find_with_more_than_one_string_key
+ assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length
+ end
+
+ def test_primary_key_prefix
+ ActiveRecord::Base.primary_key_prefix_type = :table_name
+ Topic.reset_primary_key
+ assert_equal "topicid", Topic.primary_key
+
+ ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore
+ Topic.reset_primary_key
+ assert_equal "topic_id", Topic.primary_key
+
+ ActiveRecord::Base.primary_key_prefix_type = nil
+ Topic.reset_primary_key
+ assert_equal "id", Topic.primary_key
+ end
+
+ def test_delete_should_quote_pkey
+ assert_nothing_raised { MixedCaseMonkey.delete(1) }
+ end
+ def test_update_counters_should_quote_pkey_and_quote_counter_columns
+ assert_nothing_raised { MixedCaseMonkey.update_counters(1, :fleaCount => 99) }
+ end
+ def test_find_with_one_id_should_quote_pkey
+ assert_nothing_raised { MixedCaseMonkey.find(1) }
+ end
+ def test_find_with_multiple_ids_should_quote_pkey
+ assert_nothing_raised { MixedCaseMonkey.find([1,2]) }
+ end
+ def test_instance_update_should_quote_pkey
+ assert_nothing_raised { MixedCaseMonkey.find(1).save }
+ end
+ def test_instance_destroy_should_quote_pkey
+ assert_nothing_raised { MixedCaseMonkey.find(1).destroy }
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/query_cache_test.rb b/vendor/rails/activerecord/test/cases/query_cache_test.rb
new file mode 100644
index 0000000..dc9eeec
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/query_cache_test.rb
@@ -0,0 +1,126 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/task'
+require 'models/course'
+require 'models/category'
+require 'models/post'
+
+
+class QueryCacheTest < ActiveRecord::TestCase
+ fixtures :tasks, :topics, :categories, :posts, :categories_posts
+
+ def test_find_queries
+ assert_queries(2) { Task.find(1); Task.find(1) }
+ end
+
+ def test_find_queries_with_cache
+ Task.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+
+ def test_count_queries_with_cache
+ Task.cache do
+ assert_queries(1) { Task.count; Task.count }
+ end
+ end
+
+ def test_query_cache_dups_results_correctly
+ Task.cache do
+ now = Time.now.utc
+ task = Task.find 1
+ assert_not_equal now, task.starting
+ task.starting = now
+ task.reload
+ assert_not_equal now, task.starting
+ end
+ end
+
+ def test_cache_is_flat
+ Task.cache do
+ Topic.columns # don't count this query
+ assert_queries(1) { Topic.find(1); Topic.find(1); }
+ end
+
+ ActiveRecord::Base.cache do
+ assert_queries(1) { Task.find(1); Task.find(1) }
+ end
+ end
+
+ def test_cache_does_not_wrap_string_results_in_arrays
+ Task.cache do
+ assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks")
+ end
+ end
+end
+
+uses_mocha 'QueryCacheExpiryTest' do
+
+class QueryCacheExpiryTest < ActiveRecord::TestCase
+ fixtures :tasks
+
+ def test_find
+ Task.connection.expects(:clear_query_cache).times(1)
+
+ assert !Task.connection.query_cache_enabled
+ Task.cache do
+ assert Task.connection.query_cache_enabled
+ Task.find(1)
+
+ Task.uncached do
+ assert !Task.connection.query_cache_enabled
+ Task.find(1)
+ end
+
+ assert Task.connection.query_cache_enabled
+ end
+ assert !Task.connection.query_cache_enabled
+ end
+
+ def test_update
+ Task.connection.expects(:clear_query_cache).times(2)
+
+ Task.cache do
+ task = Task.find(1)
+ task.starting = Time.now.utc
+ task.save!
+ end
+ end
+
+ def test_destroy
+ Task.connection.expects(:clear_query_cache).times(2)
+
+ Task.cache do
+ Task.find(1).destroy
+ end
+ end
+
+ def test_insert
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
+
+ Task.cache do
+ Task.create!
+ end
+ end
+
+ def test_cache_is_expired_by_habtm_update
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
+ ActiveRecord::Base.cache do
+ c = Category.find(:first)
+ p = Post.find(:first)
+ p.categories << c
+ end
+ end
+
+ def test_cache_is_expired_by_habtm_delete
+ ActiveRecord::Base.connection.expects(:clear_query_cache).times(2)
+ ActiveRecord::Base.cache do
+ c = Category.find(:first)
+ p = Post.find(:first)
+ p.categories.delete_all
+ end
+ end
+end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/readonly_test.rb b/vendor/rails/activerecord/test/cases/readonly_test.rb
new file mode 100644
index 0000000..b921cbd
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/readonly_test.rb
@@ -0,0 +1,107 @@
+require "cases/helper"
+require 'models/post'
+require 'models/comment'
+require 'models/developer'
+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
+
+ def test_cant_save_readonly_record
+ dev = Developer.find(1)
+ assert !dev.readonly?
+
+ dev.readonly!
+ assert dev.readonly?
+
+ assert_nothing_raised do
+ dev.name = 'Luscious forbidden fruit.'
+ assert !dev.save
+ dev.name = 'Forbidden.'
+ end
+ assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save }
+ assert_raise(ActiveRecord::ReadOnlyRecord) { dev.save! }
+ end
+
+
+ def test_find_with_readonly_option
+ Developer.find(:all).each { |d| assert !d.readonly? }
+ Developer.find(:all, :readonly => false).each { |d| assert !d.readonly? }
+ Developer.find(:all, :readonly => true).each { |d| assert d.readonly? }
+ end
+
+
+ def test_find_with_joins_option_implies_readonly
+ # Blank joins don't count.
+ Developer.find(:all, :joins => ' ').each { |d| assert !d.readonly? }
+ Developer.find(:all, :joins => ' ', :readonly => false).each { |d| assert !d.readonly? }
+
+ # Others do.
+ Developer.find(:all, :joins => ', projects').each { |d| assert d.readonly? }
+ Developer.find(:all, :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.find(:all, :readonly => true).all?(&:readonly?)
+ end
+
+ def test_has_many_find_readonly
+ post = Post.find(1)
+ assert !post.comments.empty?
+ assert !post.comments.any?(&:readonly?)
+ assert !post.comments.find(:all).any?(&:readonly?)
+ assert post.comments.find(:all, :readonly => true).all?(&:readonly?)
+ end
+
+ def test_has_many_with_through_is_not_implicitly_marked_readonly
+ assert people = Post.find(1).people
+ assert !people.any?(&:readonly?)
+ end
+
+ def test_readonly_scoping
+ Post.with_scope(:find => { :conditions => '1=1' }) do
+ assert !Post.find(1).readonly?
+ assert Post.find(1, :readonly => true).readonly?
+ assert !Post.find(1, :readonly => false).readonly?
+ end
+
+ Post.with_scope(:find => { :joins => ' ' }) do
+ assert !Post.find(1).readonly?
+ assert Post.find(1, :readonly => true).readonly?
+ assert !Post.find(1, :readonly => false).readonly?
+ end
+
+ # Oracle barfs on this because the join includes unqualified and
+ # conflicting column names
+ unless current_adapter?(:OracleAdapter)
+ Post.with_scope(:find => { :joins => ', developers' }) do
+ assert Post.find(1).readonly?
+ assert Post.find(1, :readonly => true).readonly?
+ assert !Post.find(1, :readonly => false).readonly?
+ end
+ end
+
+ Post.with_scope(:find => { :readonly => true }) do
+ assert Post.find(1).readonly?
+ assert Post.find(1, :readonly => true).readonly?
+ assert !Post.find(1, :readonly => false).readonly?
+ end
+ end
+
+ def test_association_collection_method_missing_scoping_not_readonly
+ assert !Developer.find(1).projects.foo.readonly?
+ assert !Post.find(1).comments.foo.readonly?
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/reflection_test.rb b/vendor/rails/activerecord/test/cases/reflection_test.rb
new file mode 100644
index 0000000..8b4d232
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/reflection_test.rb
@@ -0,0 +1,176 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/customer'
+require 'models/company'
+require 'models/company_in_module'
+require 'models/subscriber'
+
+class ReflectionTest < ActiveRecord::TestCase
+ fixtures :topics, :customers, :companies, :subscribers
+
+ def setup
+ @first = Topic.find(1)
+ end
+
+ def test_column_null_not_null
+ subscriber = Subscriber.find(:first)
+ assert subscriber.column_for_attribute("name").null
+ assert !subscriber.column_for_attribute("nick").null
+ end
+
+ def test_read_attribute_names
+ assert_equal(
+ %w( id title author_name author_email_address bonus_time written_on last_read content approved replies_count parent_id type ).sort,
+ @first.attribute_names
+ )
+ end
+
+ def test_columns
+ assert_equal 12, Topic.columns.length
+ end
+
+ def test_columns_are_returned_in_the_order_they_were_declared
+ column_names = Topic.columns.map { |column| column.name }
+ assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id type), column_names
+ end
+
+ def test_content_columns
+ content_columns = Topic.content_columns
+ content_column_names = content_columns.map {|column| column.name}
+ assert_equal 8, content_columns.length
+ assert_equal %w(title author_name author_email_address written_on bonus_time last_read content approved).sort, content_column_names.sort
+ end
+
+ def test_column_string_type_and_limit
+ assert_equal :string, @first.column_for_attribute("title").type
+ assert_equal 255, @first.column_for_attribute("title").limit
+ end
+
+ def test_column_null_not_null
+ subscriber = Subscriber.find(:first)
+ assert subscriber.column_for_attribute("name").null
+ assert !subscriber.column_for_attribute("nick").null
+ end
+
+ def test_human_name_for_column
+ assert_equal "Author name", @first.column_for_attribute("author_name").human_name
+ end
+
+ def test_integer_columns
+ assert_equal :integer, @first.column_for_attribute("id").type
+ end
+
+ def test_reflection_klass_for_nested_class_name
+ reflection = ActiveRecord::Reflection::MacroReflection.new(nil, nil, { :class_name => 'MyApplication::Business::Company' }, nil)
+ assert_nothing_raised do
+ assert_equal MyApplication::Business::Company, reflection.klass
+ end
+ end
+
+ def test_aggregation_reflection
+ reflection_for_address = ActiveRecord::Reflection::AggregateReflection.new(
+ :composed_of, :address, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
+ )
+
+ reflection_for_balance = ActiveRecord::Reflection::AggregateReflection.new(
+ :composed_of, :balance, { :class_name => "Money", :mapping => %w(balance amount) }, Customer
+ )
+
+ reflection_for_gps_location = ActiveRecord::Reflection::AggregateReflection.new(
+ :composed_of, :gps_location, { }, Customer
+ )
+
+ assert Customer.reflect_on_all_aggregations.include?(reflection_for_gps_location)
+ assert Customer.reflect_on_all_aggregations.include?(reflection_for_balance)
+ assert Customer.reflect_on_all_aggregations.include?(reflection_for_address)
+
+ assert_equal reflection_for_address, Customer.reflect_on_aggregation(:address)
+
+ assert_equal Address, Customer.reflect_on_aggregation(:address).klass
+
+ assert_equal Money, Customer.reflect_on_aggregation(:balance).klass
+ end
+
+ def test_has_many_reflection
+ reflection_for_clients = ActiveRecord::Reflection::AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm)
+
+ assert_equal reflection_for_clients, Firm.reflect_on_association(:clients)
+
+ assert_equal Client, Firm.reflect_on_association(:clients).klass
+ assert_equal 'companies', Firm.reflect_on_association(:clients).table_name
+
+ assert_equal Client, Firm.reflect_on_association(:clients_of_firm).klass
+ assert_equal 'companies', Firm.reflect_on_association(:clients_of_firm).table_name
+ end
+
+ def test_has_one_reflection
+ reflection_for_account = ActiveRecord::Reflection::AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm)
+ assert_equal reflection_for_account, Firm.reflect_on_association(:account)
+
+ assert_equal Account, Firm.reflect_on_association(:account).klass
+ assert_equal 'accounts', Firm.reflect_on_association(:account).table_name
+ end
+
+ 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
+ Company.belongs_to :bar, :class_name => "Xyzzy"
+ assert_equal "bar_id", Company.reflect_on_association(:bar).primary_key_name
+ Company.belongs_to :baz, :class_name => "Xyzzy", :foreign_key => "xyzzy_id"
+ assert_equal "xyzzy_id", Company.reflect_on_association(:baz).primary_key_name
+ end
+
+ def test_association_reflection_in_modules
+ assert_reflection MyApplication::Business::Firm,
+ :clients_of_firm,
+ :klass => MyApplication::Business::Client,
+ :class_name => 'Client',
+ :table_name => 'companies'
+
+ assert_reflection MyApplication::Billing::Account,
+ :firm,
+ :klass => MyApplication::Business::Firm,
+ :class_name => 'MyApplication::Business::Firm',
+ :table_name => 'companies'
+
+ assert_reflection MyApplication::Billing::Account,
+ :qualified_billing_firm,
+ :klass => MyApplication::Billing::Firm,
+ :class_name => 'MyApplication::Billing::Firm',
+ :table_name => 'companies'
+
+ assert_reflection MyApplication::Billing::Account,
+ :unqualified_billing_firm,
+ :klass => MyApplication::Billing::Firm,
+ :class_name => 'Firm',
+ :table_name => 'companies'
+
+ assert_reflection MyApplication::Billing::Account,
+ :nested_qualified_billing_firm,
+ :klass => MyApplication::Billing::Nested::Firm,
+ :class_name => 'MyApplication::Billing::Nested::Firm',
+ :table_name => 'companies'
+
+ assert_reflection MyApplication::Billing::Account,
+ :nested_unqualified_billing_firm,
+ :klass => MyApplication::Billing::Nested::Firm,
+ :class_name => 'Nested::Firm',
+ :table_name => 'companies'
+ end
+
+ def test_reflection_of_all_associations
+ # FIXME these assertions bust a lot
+ assert_equal 20, Firm.reflect_on_all_associations.size
+ assert_equal 16, Firm.reflect_on_all_associations(:has_many).size
+ assert_equal 4, Firm.reflect_on_all_associations(:has_one).size
+ assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size
+ end
+
+ private
+ def assert_reflection(klass, association, options)
+ assert reflection = klass.reflect_on_association(association)
+ options.each do |method, value|
+ assert_equal(value, reflection.send(method))
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/reserved_word_test_mysql.rb b/vendor/rails/activerecord/test/cases/reserved_word_test_mysql.rb
new file mode 100644
index 0000000..ce1622b
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/reserved_word_test_mysql.rb
@@ -0,0 +1,176 @@
+require "cases/helper"
+
+class Group < ActiveRecord::Base
+ Group.table_name = 'group'
+ belongs_to :select, :class_name => 'Select'
+ has_one :values
+end
+
+class Select < ActiveRecord::Base
+ Select.table_name = 'select'
+ has_many :groups
+end
+
+class Values < ActiveRecord::Base
+ Values.table_name = 'values'
+end
+
+class Distinct < ActiveRecord::Base
+ Distinct.table_name = 'distinct'
+ has_and_belongs_to_many :selects
+ has_many :values, :through => :groups
+end
+
+# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with
+# reserved word names (ie: group, order, values, etc...)
+class MysqlReservedWordTest < ActiveRecord::TestCase
+ def setup
+ @connection = ActiveRecord::Base.connection
+
+ # we call execute directly here (and do similar below) because ActiveRecord::Base#create_table()
+ # will fail with these table names if these test cases fail
+
+ create_tables_directly 'group'=>'id int auto_increment primary key, `order` varchar(255), select_id int',
+ 'select'=>'id int auto_increment primary key',
+ 'values'=>'id int auto_increment primary key, group_id int',
+ 'distinct'=>'id int auto_increment primary key',
+ 'distincts_selects'=>'distinct_id int, select_id int'
+ end
+
+ def teardown
+ drop_tables_directly ['group', 'select', 'values', 'distinct', 'distincts_selects', 'order']
+ end
+
+ # create tables with reserved-word names and columns
+ def test_create_tables
+ assert_nothing_raised {
+ @connection.create_table :order do |t|
+ t.column :group, :string
+ end
+ }
+ end
+
+ # rename tables with reserved-word names
+ def test_rename_tables
+ assert_nothing_raised { @connection.rename_table(:group, :order) }
+ end
+
+ # alter column with a reserved-word name in a table with a reserved-word name
+ def test_change_columns
+ assert_nothing_raised { @connection.change_column_default(:group, :order, 'whatever') }
+ #the quoting here will reveal any double quoting issues in change_column's interaction with the column method in the adapter
+ assert_nothing_raised { @connection.change_column('group', 'order', :Int, :default => 0) }
+ assert_nothing_raised { @connection.rename_column(:group, :order, :values) }
+ end
+
+ # dump structure of table with reserved word name
+ def test_structure_dump
+ assert_nothing_raised { @connection.structure_dump }
+ end
+
+ # introspect table with reserved word name
+ def test_introspect
+ assert_nothing_raised { @connection.columns(:group) }
+ assert_nothing_raised { @connection.indexes(:group) }
+ end
+
+ #fixtures
+ self.use_instantiated_fixtures = true
+ self.use_transactional_fixtures = false
+
+ #fixtures :group
+
+ def test_fixtures
+ f = create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.delete_existing_fixtures
+ end
+ }
+
+ assert_nothing_raised {
+ f.each do |x|
+ x.insert_fixtures
+ end
+ }
+ end
+
+ #activerecord model class with reserved-word table name
+ def test_activerecord_model
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ x = nil
+ assert_nothing_raised { x = Group.new }
+ x.order = 'x'
+ assert_nothing_raised { x.save }
+ x.order = 'y'
+ assert_nothing_raised { x.save }
+ assert_nothing_raised { y = Group.find_by_order('y') }
+ assert_nothing_raised { y = Group.find(1) }
+ x = Group.find(1)
+ end
+
+ # has_one association with reserved-word table name
+ def test_has_one_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ v = nil
+ assert_nothing_raised { v = Group.find(1).values }
+ assert_equal v.id, 2
+ end
+
+ # belongs_to association with reserved-word table name
+ def test_belongs_to_associations
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ gs = nil
+ assert_nothing_raised { gs = Select.find(2).groups }
+ assert_equal gs.length, 2
+ assert(gs.collect{|x| x.id}.sort == [2, 3])
+ end
+
+ # has_and_belongs_to_many with reserved-word table name
+ def test_has_and_belongs_to_many
+ create_test_fixtures :select, :distinct, :group, :values, :distincts_selects
+ s = nil
+ assert_nothing_raised { s = Distinct.find(1).selects }
+ assert_equal s.length, 2
+ assert(s.collect{|x|x.id}.sort == [1, 2])
+ end
+
+ # activerecord model introspection with reserved-word table and column names
+ def test_activerecord_introspection
+ assert_nothing_raised { Group.table_exists? }
+ assert_nothing_raised { Group.columns }
+ end
+
+ # Calculations
+ def test_calculations_work_with_reserved_words
+ assert_nothing_raised { Group.count }
+ end
+
+ def test_associations_work_with_reserved_words
+ assert_nothing_raised { Select.find(:all, :include => [:groups]) }
+ end
+
+ #the following functions were added to DRY test cases
+
+ private
+ # custom fixture loader, uses Fixtures#create_fixtures and appends base_path to the current file's path
+ def create_test_fixtures(*fixture_names)
+ Fixtures.create_fixtures(FIXTURES_ROOT + "/reserved_words", fixture_names)
+ end
+
+ # custom drop table, uses execute on connection to drop a table if it exists. note: escapes table_name
+ def drop_tables_directly(table_names, connection = @connection)
+ table_names.each do |name|
+ connection.execute("DROP TABLE IF EXISTS `#{name}`")
+ end
+ end
+
+ # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns
+ def create_tables_directly (tables, connection = @connection)
+ tables.each do |table_name, column_properties|
+ connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )")
+ end
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/schema_authorization_test_postgresql.rb b/vendor/rails/activerecord/test/cases/schema_authorization_test_postgresql.rb
new file mode 100644
index 0000000..7a0796e
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/schema_authorization_test_postgresql.rb
@@ -0,0 +1,75 @@
+require "cases/helper"
+
+class SchemaThing < ActiveRecord::Base
+end
+
+class SchemaAuthorizationTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ TABLE_NAME = 'schema_things'
+ COLUMNS = [
+ 'id serial primary key',
+ 'name character varying(50)'
+ ]
+ USERS = ['rails_pg_schema_user1', 'rails_pg_schema_user2']
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.execute "SET search_path TO '$user',public"
+ set_session_auth
+ USERS.each do |u|
+ @connection.execute "CREATE USER #{u}"
+ @connection.execute "CREATE SCHEMA AUTHORIZATION #{u}"
+ set_session_auth u
+ @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
+ @connection.execute "INSERT INTO #{TABLE_NAME} (name) VALUES ('#{u}')"
+ set_session_auth
+ end
+ end
+
+ def teardown
+ set_session_auth
+ @connection.execute "RESET search_path"
+ USERS.each do |u|
+ @connection.execute "DROP SCHEMA #{u} CASCADE"
+ @connection.execute "DROP USER #{u}"
+ end
+ end
+
+ def test_schema_invisible
+ assert_raise(ActiveRecord::StatementInvalid) do
+ set_session_auth
+ @connection.execute "SELECT * FROM #{TABLE_NAME}"
+ end
+ end
+
+ def test_schema_uniqueness
+ assert_nothing_raised do
+ set_session_auth
+ USERS.each do |u|
+ set_session_auth u
+ assert_equal u, @connection.select_value("SELECT name FROM #{TABLE_NAME} WHERE id = 1")
+ set_session_auth
+ end
+ end
+ end
+
+ def test_sequence_schema_caching
+ assert_nothing_raised do
+ USERS.each do |u|
+ set_session_auth u
+ st = SchemaThing.new :name => 'TEST1'
+ st.save!
+ st = SchemaThing.new :id => 5, :name => 'TEST2'
+ st.save!
+ set_session_auth
+ end
+ end
+ end
+
+ private
+ def set_session_auth auth = nil
+ @connection.execute "SET SESSION AUTHORIZATION #{auth || 'default'}"
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/schema_dumper_test.rb b/vendor/rails/activerecord/test/cases/schema_dumper_test.rb
new file mode 100644
index 0000000..c42b0ef
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/schema_dumper_test.rb
@@ -0,0 +1,138 @@
+require "cases/helper"
+require 'active_record/schema_dumper'
+require 'stringio'
+
+
+class SchemaDumperTest < ActiveRecord::TestCase
+ def standard_dump
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.ignore_tables = []
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ stream.string
+ end
+
+ def test_schema_dump
+ output = standard_dump
+ assert_match %r{create_table "accounts"}, output
+ assert_match %r{create_table "authors"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ end
+
+ def test_schema_dump_excludes_sqlite_sequence
+ output = standard_dump
+ assert_no_match %r{create_table "sqlite_sequence"}, output
+ end
+
+ def assert_line_up(lines, pattern, required = false)
+ return assert(true) if lines.empty?
+ matches = lines.map { |line| line.match(pattern) }
+ assert matches.all? if required
+ matches.compact!
+ return assert(true) if matches.empty?
+ assert_equal 1, matches.map{ |match| match.offset(0).first }.uniq.length
+ end
+
+ def column_definition_lines(output = standard_dump)
+ output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map{ |m| m.last.split(/\n/) }
+ end
+
+ def test_types_line_up
+ column_definition_lines.each do |column_set|
+ next if column_set.empty?
+
+ lengths = column_set.map do |column|
+ if match = column.match(/t\.(?:integer|decimal|float|datetime|timestamp|time|date|text|binary|string|boolean)\s+"/)
+ match[0].length
+ end
+ end
+
+ assert_equal 1, lengths.uniq.length
+ end
+ end
+
+ def test_arguments_line_up
+ column_definition_lines.each do |column_set|
+ assert_line_up(column_set, /:default => /)
+ assert_line_up(column_set, /:limit => /)
+ assert_line_up(column_set, /:null => /)
+ end
+ end
+
+ def test_no_dump_errors
+ output = standard_dump
+ assert_no_match %r{\# Could not dump table}, output
+ end
+
+ def test_schema_dump_includes_not_null_columns
+ stream = StringIO.new
+
+ ActiveRecord::SchemaDumper.ignore_tables = [/^[^r]/]
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+ assert_match %r{:null => false}, output
+ end
+
+ def test_schema_dump_with_string_ignored_table
+ stream = StringIO.new
+
+ ActiveRecord::SchemaDumper.ignore_tables = ['accounts']
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+ assert_no_match %r{create_table "accounts"}, output
+ assert_match %r{create_table "authors"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ end
+
+ def test_schema_dump_with_regexp_ignored_table
+ stream = StringIO.new
+
+ ActiveRecord::SchemaDumper.ignore_tables = [/^account/]
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+ assert_no_match %r{create_table "accounts"}, output
+ assert_match %r{create_table "authors"}, output
+ assert_no_match %r{create_table "schema_migrations"}, output
+ end
+
+ def test_schema_dump_illegal_ignored_table_value
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.ignore_tables = [5]
+ assert_raise(StandardError) do
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ end
+ end
+
+ if current_adapter?(:MysqlAdapter)
+ def test_schema_dump_should_not_add_default_value_for_mysql_text_field
+ output = standard_dump
+ assert_match %r{t.text\s+"body",\s+:null => false$}, output
+ end
+
+ def test_mysql_schema_dump_should_honor_nonstandard_primary_keys
+ output = standard_dump
+ match = output.match(%r{create_table "movies"(.*)do})
+ assert_not_nil(match, "nonstandardpk table not found")
+ assert_match %r(:primary_key => "movieid"), match[1], "non-standard primary key not preserved"
+ end
+
+ def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
+ output = standard_dump
+ assert_match %r{t.binary\s+"tiny_blob",\s+:limit => 255$}, output
+ assert_match %r{t.binary\s+"normal_blob"$}, output
+ assert_match %r{t.binary\s+"medium_blob",\s+:limit => 16777215$}, output
+ assert_match %r{t.binary\s+"long_blob",\s+:limit => 2147483647$}, output
+ assert_match %r{t.text\s+"tiny_text",\s+:limit => 255$}, output
+ assert_match %r{t.text\s+"normal_text"$}, output
+ assert_match %r{t.text\s+"medium_text",\s+:limit => 16777215$}, output
+ assert_match %r{t.text\s+"long_text",\s+:limit => 2147483647$}, output
+ end
+ end
+
+ def test_schema_dump_includes_decimal_options
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.ignore_tables = [/^[^n]/]
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
+ output = stream.string
+ assert_match %r{:precision => 3,[[:space:]]+:scale => 2,[[:space:]]+:default => 2.78}, output
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/schema_test_postgresql.rb b/vendor/rails/activerecord/test/cases/schema_test_postgresql.rb
new file mode 100644
index 0000000..336a387
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/schema_test_postgresql.rb
@@ -0,0 +1,102 @@
+require "cases/helper"
+
+class SchemaTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ SCHEMA_NAME = 'test_schema'
+ SCHEMA2_NAME = 'test_schema2'
+ TABLE_NAME = 'things'
+ INDEX_A_NAME = 'a_index_things_on_name'
+ INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
+ INDEX_A_COLUMN = 'name'
+ INDEX_B_COLUMN_S1 = 'email'
+ INDEX_B_COLUMN_S2 = 'moment'
+ COLUMNS = [
+ 'id integer',
+ 'name character varying(50)',
+ 'email character varying(50)',
+ 'moment timestamp without time zone default now()'
+ ]
+
+ def setup
+ @connection = ActiveRecord::Base.connection
+ @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
+ @connection.execute "CREATE SCHEMA #{SCHEMA2_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})"
+ @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_A_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_A_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_A_COLUMN});"
+ @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S1});"
+ @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});"
+ end
+
+ def teardown
+ @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE"
+ @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE"
+ end
+
+ def test_with_schema_prefixed_table_name
+ assert_nothing_raised do
+ assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{TABLE_NAME}")
+ end
+ end
+
+ def test_with_schema_search_path
+ assert_nothing_raised do
+ with_schema_search_path(SCHEMA_NAME) do
+ assert_equal COLUMNS, columns(TABLE_NAME)
+ end
+ end
+ end
+
+ def test_raise_on_unquoted_schema_name
+ assert_raise(ActiveRecord::StatementInvalid) do
+ with_schema_search_path '$user,public'
+ end
+ end
+
+ def test_without_schema_search_path
+ assert_raise(ActiveRecord::StatementInvalid) { columns(TABLE_NAME) }
+ end
+
+ def test_ignore_nil_schema_search_path
+ assert_nothing_raised { with_schema_search_path nil }
+ end
+
+ def test_dump_indexes_for_schema_one
+ do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1)
+ end
+
+ def test_dump_indexes_for_schema_two
+ do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2)
+ end
+
+ private
+ def columns(table_name)
+ @connection.send(:column_definitions, table_name).map do |name, type, default|
+ "#{name} #{type}" + (default ? " default #{default}" : '')
+ end
+ end
+
+ def with_schema_search_path(schema_search_path)
+ @connection.schema_search_path = schema_search_path
+ yield if block_given?
+ ensure
+ @connection.schema_search_path = "'$user', public"
+ end
+
+ def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name)
+ with_schema_search_path(this_schema_name) do
+ indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
+ assert_equal 2,indexes.size
+
+ do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
+ do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
+ end
+ end
+
+ def do_dump_index_assertions_for_one_index(this_index, this_index_name, this_index_column)
+ assert_equal TABLE_NAME, this_index.table
+ assert_equal 1, this_index.columns.size
+ assert_equal this_index_column, this_index.columns[0]
+ assert_equal this_index_name, this_index.name
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/serialization_test.rb b/vendor/rails/activerecord/test/cases/serialization_test.rb
new file mode 100644
index 0000000..8841694
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/serialization_test.rb
@@ -0,0 +1,47 @@
+require "cases/helper"
+require 'models/contact'
+
+class SerializationTest < ActiveRecord::TestCase
+ FORMATS = [ :xml, :json ]
+
+ def setup
+ @contact_attributes = {
+ :name => 'aaron stack',
+ :age => 25,
+ :avatar => 'binarydata',
+ :created_at => Time.utc(2006, 8, 1),
+ :awesome => false,
+ :preferences => { :gem => 'ruby' }
+ }
+
+ @contact = Contact.new(@contact_attributes)
+ end
+
+ def test_serialize_should_be_reversible
+ for format in FORMATS
+ @serialized = Contact.new.send("to_#{format}")
+ contact = Contact.new.send("from_#{format}", @serialized)
+
+ assert_equal @contact_attributes.keys.collect(&:to_s).sort, contact.attributes.keys.collect(&:to_s).sort, "For #{format}"
+ end
+ end
+
+ def test_serialize_should_allow_attribute_only_filtering
+ for format in FORMATS
+ @serialized = Contact.new(@contact_attributes).send("to_#{format}", :only => [ :age, :name ])
+ contact = Contact.new.send("from_#{format}", @serialized)
+ assert_equal @contact_attributes[:name], contact.name, "For #{format}"
+ assert_nil contact.avatar, "For #{format}"
+ end
+ end
+
+ def test_serialize_should_allow_attribute_except_filtering
+ for format in FORMATS
+ @serialized = Contact.new(@contact_attributes).send("to_#{format}", :except => [ :age, :name ])
+ contact = Contact.new.send("from_#{format}", @serialized)
+ assert_nil contact.name, "For #{format}"
+ assert_nil contact.age, "For #{format}"
+ assert_equal @contact_attributes[:awesome], contact.awesome, "For #{format}"
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/synonym_test_oracle.rb b/vendor/rails/activerecord/test/cases/synonym_test_oracle.rb
new file mode 100644
index 0000000..b9a422a
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/synonym_test_oracle.rb
@@ -0,0 +1,17 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/subject'
+
+# confirm that synonyms work just like tables; in this case
+# the "subjects" table in Oracle (defined in oci.sql) is just
+# a synonym to the "topics" table
+
+class TestOracleSynonym < ActiveRecord::TestCase
+
+ def test_oracle_synonym
+ topic = Topic.new
+ subject = Subject.new
+ assert_equal(topic.attributes, subject.attributes)
+ end
+
+end
diff --git a/vendor/rails/activerecord/test/cases/table_name_test_sqlserver.rb b/vendor/rails/activerecord/test/cases/table_name_test_sqlserver.rb
new file mode 100644
index 0000000..fbf38a1
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/table_name_test_sqlserver.rb
@@ -0,0 +1,23 @@
+require "cases/helper"
+require 'active_record/schema'
+
+if ActiveRecord::Base.connection.supports_migrations?
+ class Order < ActiveRecord::Base
+ self.table_name = '[order]'
+ end
+
+ class TableNameTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ # Ensures Model.columns works when using SQLServer escape characters.
+ # Enables legacy schemas using SQL reserved words as table names.
+ # Should work with table names with spaces as well ('table name').
+ def test_escaped_table_name
+ assert_nothing_raised do
+ ActiveRecord::Base.connection.select_all 'SELECT * FROM [order]'
+ end
+ assert_equal '[order]', Order.table_name
+ assert_equal 5, Order.columns.length
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/threaded_connections_test.rb b/vendor/rails/activerecord/test/cases/threaded_connections_test.rb
new file mode 100644
index 0000000..28f8302
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/threaded_connections_test.rb
@@ -0,0 +1,48 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+
+unless %w(FrontBase).include? ActiveRecord::Base.connection.adapter_name
+ class ThreadedConnectionsTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ fixtures :topics
+
+ def setup
+ @connection = ActiveRecord::Base.remove_connection
+ @connections = []
+ @allow_concurrency = ActiveRecord::Base.allow_concurrency
+ end
+
+ def teardown
+ # clear the connection cache
+ ActiveRecord::Base.send(:clear_all_cached_connections!)
+ # set allow_concurrency to saved value
+ ActiveRecord::Base.allow_concurrency = @allow_concurrency
+ # reestablish old connection
+ ActiveRecord::Base.establish_connection(@connection)
+ end
+
+ def gather_connections(use_threaded_connections)
+ ActiveRecord::Base.allow_concurrency = use_threaded_connections
+ ActiveRecord::Base.establish_connection(@connection)
+
+ 5.times do
+ Thread.new do
+ Topic.find :first
+ @connections << ActiveRecord::Base.active_connections.values.first
+ end.join
+ end
+ end
+
+ def test_threaded_connections
+ gather_connections(true)
+ assert_equal @connections.uniq.length, 5
+ end
+
+ def test_unthreaded_connections
+ gather_connections(false)
+ assert_equal @connections.uniq.length, 1
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/transactions_test.rb b/vendor/rails/activerecord/test/cases/transactions_test.rb
new file mode 100644
index 0000000..06a76ea
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/transactions_test.rb
@@ -0,0 +1,307 @@
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/developer'
+
+class TransactionTest < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+ fixtures :topics, :developers
+
+ def setup
+ @first, @second = Topic.find(1, 2).sort_by { |t| t.id }
+ end
+
+ def test_successful
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ end
+
+ assert Topic.find(1).approved?, "First should have been approved"
+ assert !Topic.find(2).approved?, "Second should have been unapproved"
+ end
+
+ def transaction_with_return
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ return
+ end
+ end
+
+ def test_successful_with_return
+ class << Topic.connection
+ alias :real_commit_db_transaction :commit_db_transaction
+ def commit_db_transaction
+ $committed = true
+ real_commit_db_transaction
+ end
+ end
+
+ $committed = false
+ transaction_with_return
+ assert $committed
+
+ assert Topic.find(1).approved?, "First should have been approved"
+ assert !Topic.find(2).approved?, "Second should have been unapproved"
+ ensure
+ class << Topic.connection
+ alias :commit_db_transaction :real_commit_db_transaction rescue nil
+ end
+ end
+
+ def test_successful_with_instance_method
+ @first.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ end
+
+ assert Topic.find(1).approved?, "First should have been approved"
+ assert !Topic.find(2).approved?, "Second should have been unapproved"
+ end
+
+ def test_failing_on_exception
+ begin
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ raise "Bad things!"
+ end
+ rescue
+ # caught it
+ end
+
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
+
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
+ end
+
+
+ def test_callback_rollback_in_save
+ add_exception_raising_after_save_callback_to_topic
+
+ begin
+ @first.approved = true
+ @first.save
+ flunk
+ rescue => e
+ assert_equal "Make the transaction rollback", e.message
+ assert !Topic.find(1).approved?
+ ensure
+ remove_exception_raising_after_save_callback_to_topic
+ end
+ end
+
+ def test_callback_rollback_in_create
+ new_topic = Topic.new(
+ :title => "A new topic",
+ :author_name => "Ben",
+ :author_email_address => "ben@example.com",
+ :written_on => "2003-07-16t15:28:11.2233+01:00",
+ :last_read => "2004-04-15",
+ :bonus_time => "2005-01-30t15:28:00.00+01:00",
+ :content => "Have a nice day",
+ :approved => false)
+ new_record_snapshot = new_topic.new_record?
+ id_present = new_topic.has_attribute?(Topic.primary_key)
+ id_snapshot = new_topic.id
+
+ # Make sure the second save gets the after_create callback called.
+ 2.times do
+ begin
+ add_exception_raising_after_create_callback_to_topic
+ new_topic.approved = true
+ new_topic.save
+ flunk
+ rescue => e
+ assert_equal "Make the transaction rollback", e.message
+ assert_equal new_record_snapshot, new_topic.new_record?, "The topic should have its old new_record value"
+ assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
+ assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
+ ensure
+ remove_exception_raising_after_create_callback_to_topic
+ end
+ end
+ end
+
+ def test_nested_explicit_transactions
+ Topic.transaction do
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+ end
+ end
+
+ assert Topic.find(1).approved?, "First should have been approved"
+ assert !Topic.find(2).approved?, "Second should have been unapproved"
+ end
+
+ def test_manually_rolling_back_a_transaction
+ Topic.transaction do
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+
+ raise ActiveRecord::Rollback
+ end
+
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
+
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
+ end
+
+ uses_mocha 'mocking connection.commit_db_transaction' do
+ def test_rollback_when_commit_raises
+ Topic.connection.expects(:begin_db_transaction)
+ Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
+ Topic.connection.expects(:rollback_db_transaction)
+
+ assert_raise RuntimeError do
+ Topic.transaction do
+ # do nothing
+ end
+ end
+ end
+ end
+
+ def test_sqlite_add_column_in_transaction_raises_statement_invalid
+ return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter)
+
+ # Test first if column creation/deletion works correctly when no
+ # transaction is in place.
+ #
+ # We go back to the connection for the column queries because
+ # Topic.columns is cached and won't report changes to the DB
+
+ assert_nothing_raised do
+ Topic.reset_column_information
+ Topic.connection.add_column('topics', 'stuff', :string)
+ assert Topic.column_names.include?('stuff')
+
+ Topic.reset_column_information
+ Topic.connection.remove_column('topics', 'stuff')
+ assert !Topic.column_names.include?('stuff')
+ end
+
+ # Test now inside a transaction: add_column should raise a StatementInvalid
+ Topic.transaction do
+ assert_raises(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) }
+ raise ActiveRecord::Rollback
+ end
+ end
+
+ private
+ def add_exception_raising_after_save_callback_to_topic
+ Topic.class_eval { def after_save() raise "Make the transaction rollback" end }
+ end
+
+ def remove_exception_raising_after_save_callback_to_topic
+ Topic.class_eval { remove_method :after_save }
+ end
+
+ def add_exception_raising_after_create_callback_to_topic
+ Topic.class_eval { def after_create() raise "Make the transaction rollback" end }
+ end
+
+ def remove_exception_raising_after_create_callback_to_topic
+ Topic.class_eval { remove_method :after_create }
+ end
+end
+
+if current_adapter?(:PostgreSQLAdapter)
+ class ConcurrentTransactionTest < TransactionTest
+ def setup
+ @allow_concurrency = ActiveRecord::Base.allow_concurrency
+ ActiveRecord::Base.allow_concurrency = true
+ super
+ end
+
+ def teardown
+ super
+ ActiveRecord::Base.allow_concurrency = @allow_concurrency
+ end
+
+ # This will cause transactions to overlap and fail unless they are performed on
+ # separate database connections.
+ def test_transaction_per_thread
+ assert_nothing_raised do
+ threads = (1..3).map do
+ Thread.new do
+ Topic.transaction do
+ topic = Topic.find(1)
+ topic.approved = !topic.approved?
+ topic.save!
+ topic.approved = !topic.approved?
+ topic.save!
+ end
+ end
+ end
+
+ threads.each { |t| t.join }
+ end
+ end
+
+ # Test for dirty reads among simultaneous transactions.
+ def test_transaction_isolation__read_committed
+ # Should be invariant.
+ original_salary = Developer.find(1).salary
+ temporary_salary = 200000
+
+ assert_nothing_raised do
+ threads = (1..3).map do
+ Thread.new do
+ Developer.transaction do
+ # Expect original salary.
+ dev = Developer.find(1)
+ assert_equal original_salary, dev.salary
+
+ dev.salary = temporary_salary
+ dev.save!
+
+ # Expect temporary salary.
+ dev = Developer.find(1)
+ assert_equal temporary_salary, dev.salary
+
+ dev.salary = original_salary
+ dev.save!
+
+ # Expect original salary.
+ dev = Developer.find(1)
+ assert_equal original_salary, dev.salary
+ end
+ end
+ end
+
+ # Keep our eyes peeled.
+ threads << Thread.new do
+ 10.times do
+ sleep 0.05
+ Developer.transaction do
+ # Always expect original salary.
+ assert_equal original_salary, Developer.find(1).salary
+ end
+ end
+ end
+
+ threads.each { |t| t.join }
+ end
+
+ assert_equal original_salary, Developer.find(1).salary
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/unconnected_test.rb b/vendor/rails/activerecord/test/cases/unconnected_test.rb
new file mode 100644
index 0000000..23ad10f
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/unconnected_test.rb
@@ -0,0 +1,32 @@
+require "cases/helper"
+
+class TestRecord < ActiveRecord::Base
+end
+
+class TestUnconnectedAdapter < ActiveRecord::TestCase
+ self.use_transactional_fixtures = false
+
+ def setup
+ @underlying = ActiveRecord::Base.connection
+ @specification = ActiveRecord::Base.remove_connection
+ end
+
+ def teardown
+ @underlying = nil
+ ActiveRecord::Base.establish_connection(@specification)
+ end
+
+ def test_connection_no_longer_established
+ assert_raise(ActiveRecord::ConnectionNotEstablished) do
+ TestRecord.find(1)
+ end
+
+ assert_raise(ActiveRecord::ConnectionNotEstablished) do
+ TestRecord.new.save
+ end
+ end
+
+ def test_underlying_adapter_no_longer_active
+ assert !@underlying.active?, "Removed adapter should no longer be active"
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/validations_test.rb b/vendor/rails/activerecord/test/cases/validations_test.rb
new file mode 100644
index 0000000..7b71647
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/validations_test.rb
@@ -0,0 +1,1509 @@
+# encoding: utf-8
+require "cases/helper"
+require 'models/topic'
+require 'models/reply'
+require 'models/person'
+require 'models/developer'
+require 'models/warehouse_thing'
+require 'models/guid'
+
+# 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"
+
+ def condition_is_true
+ true
+ end
+
+ def condition_is_true_but_its_not
+ false
+ end
+end
+
+class ProtectedPerson < ActiveRecord::Base
+ set_table_name 'people'
+ attr_accessor :addon
+ attr_protected :first_name
+end
+
+class UniqueReply < Reply
+ validates_uniqueness_of :content, :scope => 'parent_id'
+end
+
+class PlagiarizedReply < Reply
+ validates_acceptance_of :author_name
+end
+
+class SillyUniqueReply < UniqueReply
+end
+
+class Wizard < ActiveRecord::Base
+ self.abstract_class = true
+
+ validates_uniqueness_of :name
+end
+
+class IneptWizard < Wizard
+ validates_uniqueness_of :city
+end
+
+class Conjurer < IneptWizard
+end
+
+class Thaumaturgist < IneptWizard
+end
+
+
+class ValidationsTest < ActiveRecord::TestCase
+ fixtures :topics, :developers, 'warehouse-things'
+
+ def setup
+ Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+
+ def test_single_field_validation
+ r = Reply.new
+ r.title = "There's no content!"
+ assert !r.valid?, "A reply without content shouldn't be saveable"
+
+ r.content = "Messa content!"
+ assert r.valid?, "A reply with content should be saveable"
+ end
+
+ def test_single_attr_validation_and_error_msg
+ r = Reply.new
+ r.title = "There's no content!"
+ assert !r.valid?
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
+ assert_equal 1, r.errors.count
+ end
+
+ def test_double_attr_validation_and_error_msg
+ r = Reply.new
+ assert !r.valid?
+
+ assert r.errors.invalid?("title"), "A reply without title should mark that attribute as invalid"
+ assert_equal "Empty", r.errors.on("title"), "A reply without title should contain an error"
+
+ assert r.errors.invalid?("content"), "A reply without content should mark that attribute as invalid"
+ assert_equal "Empty", r.errors.on("content"), "A reply without content should contain an error"
+
+ assert_equal 2, r.errors.count
+ end
+
+ def test_error_on_create
+ r = Reply.new
+ r.title = "Wrong Create"
+ assert !r.valid?
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
+ assert_equal "is Wrong Create", r.errors.on("title"), "A reply with a bad content should contain an error"
+ end
+
+ def test_error_on_update
+ r = Reply.new
+ r.title = "Bad"
+ r.content = "Good"
+ assert r.save, "First save should be successful"
+
+ r.title = "Wrong Update"
+ assert !r.save, "Second save should fail"
+
+ assert r.errors.invalid?("title"), "A reply with a bad title should mark that attribute as invalid"
+ assert_equal "is Wrong Update", r.errors.on("title"), "A reply with a bad content should contain an error"
+ end
+
+ def test_invalid_record_exception
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.new.save! }
+
+ begin
+ r = Reply.new
+ r.save!
+ flunk
+ rescue ActiveRecord::RecordInvalid => invalid
+ assert_equal r, invalid.record
+ end
+ end
+
+ def test_exception_on_create_bang_many
+ assert_raises(ActiveRecord::RecordInvalid) do
+ Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }])
+ end
+ end
+
+ def test_exception_on_create_bang_with_block
+ assert_raises(ActiveRecord::RecordInvalid) do
+ Reply.create!({ "title" => "OK" }) do |r|
+ r.content = nil
+ end
+ end
+ end
+
+ def test_exception_on_create_bang_many_with_block
+ assert_raises(ActiveRecord::RecordInvalid) do
+ Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r|
+ r.content = nil
+ end
+ end
+ end
+
+ def test_scoped_create_without_attributes
+ Reply.with_scope(:create => {}) do
+ assert_raises(ActiveRecord::RecordInvalid) { Reply.create! }
+ end
+ end
+
+ def test_create_with_exceptions_using_scope_for_protected_attributes
+ assert_nothing_raised do
+ ProtectedPerson.with_scope( :create => { :first_name => "Mary" } ) do
+ person = ProtectedPerson.create! :addon => "Addon"
+ assert_equal person.first_name, "Mary", "scope should ignore attr_protected"
+ end
+ end
+ end
+
+ def test_create_with_exceptions_using_scope_and_empty_attributes
+ assert_nothing_raised do
+ ProtectedPerson.with_scope( :create => { :first_name => "Mary" } ) do
+ person = ProtectedPerson.create!
+ assert_equal person.first_name, "Mary", "should be ok when no attributes are passed to create!"
+ end
+ end
+ end
+
+ def test_single_error_per_attr_iteration
+ r = Reply.new
+ r.save
+
+ errors = []
+ r.errors.each { |attr, msg| errors << [attr, msg] }
+
+ assert errors.include?(["title", "Empty"])
+ assert errors.include?(["content", "Empty"])
+ end
+
+ def test_multiple_errors_per_attr_iteration_with_full_error_composition
+ r = Reply.new
+ r.title = "Wrong Create"
+ r.content = "Mismatch"
+ r.save
+
+ errors = []
+ r.errors.each_full { |error| errors << error }
+
+ assert_equal "Title is Wrong Create", errors[0]
+ assert_equal "Title is Content Mismatch", errors[1]
+ assert_equal 2, r.errors.count
+ end
+
+ def test_errors_on_base
+ r = Reply.new
+ r.content = "Mismatch"
+ r.save
+ r.errors.add_to_base "Reply is not dignifying"
+
+ errors = []
+ r.errors.each_full { |error| errors << error }
+
+ assert_equal "Reply is not dignifying", r.errors.on_base
+
+ assert errors.include?("Title Empty")
+ assert errors.include?("Reply is not dignifying")
+ assert_equal 2, r.errors.count
+ end
+
+ def test_create_without_validation
+ reply = Reply.new
+ assert !reply.save
+ assert reply.save(false)
+ end
+
+ def test_create_without_validation_bang
+ count = Reply.count
+ assert_nothing_raised { Reply.new.save_without_validation! }
+ assert count+1, Reply.count
+ end
+
+ def test_validates_each
+ perform = true
+ hits = 0
+ Topic.validates_each(:title, :content, [:title, :content]) do |record, attr|
+ if perform
+ record.errors.add attr, 'gotcha'
+ hits += 1
+ end
+ end
+ t = Topic.new("title" => "valid", "content" => "whatever")
+ assert !t.save
+ assert_equal 4, hits
+ assert_equal %w(gotcha gotcha), t.errors.on(:title)
+ assert_equal %w(gotcha gotcha), t.errors.on(:content)
+ ensure
+ perform = false
+ end
+
+ def test_no_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new(:author_name => "Plutarch")
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert !t.valid?
+
+ t.title_confirmation = nil
+ t.title = "Parallel Lives"
+ assert t.valid?
+
+ t.title_confirmation = "Parallel Lives"
+ assert t.valid?
+ end
+
+ def test_title_confirmation
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.create("title" => "We should be confirmed","title_confirmation" => "")
+ assert !t.save
+
+ t.title_confirmation = "We should be confirmed"
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement_no_acceptance
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should not be confirmed")
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","terms_of_service" => "")
+ assert !t.save
+ assert_equal "must be accepted", t.errors.on(:terms_of_service)
+
+ t.terms_of_service = "1"
+ assert t.save
+ end
+
+
+ def test_eula
+ Topic.validates_acceptance_of(:eula, :message => "must be abided", :on => :create)
+
+ t = Topic.create("title" => "We should be confirmed","eula" => "")
+ assert !t.save
+ assert_equal "must be abided", t.errors.on(:eula)
+
+ t.eula = "1"
+ assert t.save
+ end
+
+ def test_terms_of_service_agreement_with_accept_value
+ Topic.validates_acceptance_of(:terms_of_service, :on => :create, :accept => "I agree.")
+
+ t = Topic.create("title" => "We should be confirmed", "terms_of_service" => "")
+ assert !t.save
+ assert_equal "must be accepted", t.errors.on(:terms_of_service)
+
+ t.terms_of_service = "I agree."
+ assert t.save
+ end
+
+ def test_validates_acceptance_of_as_database_column
+ reply = PlagiarizedReply.create("author_name" => "Dan Brown")
+ assert_equal "Dan Brown", reply["author_name"]
+ end
+
+ def test_validates_acceptance_of_with_non_existant_table
+ Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base)
+
+ assert_nothing_raised ActiveRecord::StatementInvalid do
+ IncorporealModel.validates_acceptance_of(:incorporeal_column)
+ end
+ end
+
+ def test_validate_presences
+ Topic.validates_presence_of(:title, :content)
+
+ t = Topic.create
+ assert !t.save
+ assert_equal "can't be blank", t.errors.on(:title)
+ assert_equal "can't be blank", t.errors.on(:content)
+
+ t.title = "something"
+ t.content = " "
+
+ assert !t.save
+ assert_equal "can't be blank", t.errors.on(:content)
+
+ t.content = "like stuff"
+
+ assert t.save
+ end
+
+ def test_validate_uniqueness
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'm unique!")
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert_equal "has already been taken", t2.errors.on(:title)
+
+ t2.title = "Now Im really also unique"
+ assert t2.save, "Should now save t2 as unique"
+ end
+
+ def test_validate_uniqueness_with_scope
+ Reply.validates_uniqueness_of(:content, :scope => "parent_id")
+
+ t = Topic.create("title" => "I'm unique!")
+
+ r1 = t.replies.create "title" => "r1", "content" => "hello world"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "title" => "r2", "content" => "hello world"
+ assert !r2.valid?, "Saving r2 first time"
+
+ r2.content = "something else"
+ assert r2.save, "Saving r2 second time"
+
+ t2 = Topic.create("title" => "I'm unique too!")
+ r3 = t2.replies.create "title" => "r3", "content" => "hello world"
+ assert r3.valid?, "Saving r3"
+ end
+
+ def test_validate_uniqueness_scoped_to_defining_class
+ t = Topic.create("title" => "What, me worry?")
+
+ r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun"
+ assert !r2.valid?, "Saving r2"
+
+ # Should succeed as validates_uniqueness_of only applies to
+ # UniqueReply and its subclasses
+ r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun"
+ assert r3.valid?, "Saving r3"
+ end
+
+ def test_validate_uniqueness_with_scope_array
+ Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id])
+
+ t = Topic.create("title" => "The earth is actually flat!")
+
+ r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply"
+ assert r1.valid?, "Saving r1"
+
+ r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..."
+ assert !r2.valid?, "Saving r2. Double reply by same author."
+
+ r2.author_email_address = "jeremy_alt_email@rubyonrails.com"
+ assert r2.save, "Saving r2 the second time."
+
+ r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic"
+ assert !r3.valid?, "Saving r3"
+
+ r3.author_name = "jj"
+ assert r3.save, "Saving r3 the second time."
+
+ r3.author_name = "jeremy"
+ assert !r3.save, "Saving r3 the third time."
+ end
+
+ def test_validate_case_insensitive_uniqueness
+ Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true)
+
+ t = Topic.new("title" => "I'm unique!", :parent_id => 2)
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1)
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert t2.errors.on(:title)
+ assert t2.errors.on(:parent_id)
+ assert_equal "has already been taken", t2.errors.on(:title)
+
+ t2.title = "I'm truly UNIQUE!"
+ assert !t2.valid?, "Shouldn't be valid"
+ assert !t2.save, "Shouldn't save t2 as unique"
+ assert_nil t2.errors.on(:title)
+ assert t2.errors.on(:parent_id)
+
+ t2.parent_id = 4
+ assert t2.save, "Should now save t2 as unique"
+
+ t2.parent_id = nil
+ t2.title = nil
+ assert t2.valid?, "should validate with nil"
+ assert t2.save, "should save with nil"
+ end
+
+ def test_validate_case_sensitive_uniqueness
+ Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true)
+
+ t = Topic.new("title" => "I'm unique!")
+ assert t.save, "Should save t as unique"
+
+ t.content = "Remaining unique"
+ assert t.save, "Should still save t as unique"
+
+ t2 = Topic.new("title" => "I'M UNIQUE!")
+ assert t2.valid?, "Should be valid"
+ assert t2.save, "Should save t2 as unique"
+ assert !t2.errors.on(:title)
+ assert !t2.errors.on(:parent_id)
+ assert_not_equal "has already been taken", t2.errors.on(:title)
+
+ t3 = Topic.new("title" => "I'M uNiQUe!")
+ assert t3.valid?, "Should be valid"
+ assert t3.save, "Should save t2 as unique"
+ assert !t3.errors.on(:title)
+ assert !t3.errors.on(:parent_id)
+ assert_not_equal "has already been taken", t3.errors.on(:title)
+ end
+
+ def test_validate_uniqueness_with_non_standard_table_names
+ i1 = WarehouseThing.create(:value => 1000)
+ assert !i1.valid?, "i1 should not be valid"
+ assert i1.errors.on(:value), "Should not be empty"
+ end
+
+ def test_validates_uniqueness_inside_with_scope
+ Topic.validates_uniqueness_of(:title)
+
+ Topic.with_scope(:find => { :conditions => { :author_name => "David" } }) do
+ t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary")
+ assert t1.save
+ t2 = Topic.new("title" => "I'm unique!", "author_name" => "David")
+ assert !t2.valid?
+ end
+ end
+
+ def test_validate_uniqueness_with_columns_which_are_sql_keywords
+ Guid.validates_uniqueness_of :key
+ g = Guid.new
+ g.key = "foo"
+ assert_nothing_raised { !g.valid? }
+ end
+
+ def test_validate_straight_inheritance_uniqueness
+ w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork")
+ assert w1.valid?, "Saving w1"
+
+ # Should use validation from base class (which is abstract)
+ w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm")
+ assert !w2.valid?, "w2 shouldn't be valid"
+ assert w2.errors.on(:name), "Should have errors for name"
+ assert_equal "has already been taken", w2.errors.on(:name), "Should have uniqueness message for name"
+
+ w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm")
+ assert !w3.valid?, "w3 shouldn't be valid"
+ assert w3.errors.on(:name), "Should have errors for name"
+ assert_equal "has already been taken", w3.errors.on(:name), "Should have uniqueness message for name"
+
+ w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm")
+ assert w4.valid?, "Saving w4"
+
+ w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre")
+ assert !w5.valid?, "w5 shouldn't be valid"
+ assert w5.errors.on(:name), "Should have errors for name"
+ assert_equal "has already been taken", w5.errors.on(:name), "Should have uniqueness message for name"
+
+ w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm")
+ assert !w6.valid?, "w6 shouldn't be valid"
+ assert w6.errors.on(:city), "Should have errors for city"
+ assert_equal "has already been taken", w6.errors.on(:city), "Should have uniqueness message for city"
+ end
+
+ def test_validate_format
+ Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
+
+ t = Topic.create("title" => "i'm incorrect", "content" => "Validation macros rule!")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal "is bad data", t.errors.on(:title)
+ assert_nil t.errors.on(:content)
+
+ t.title = "Validation macros rule!"
+
+ assert t.save
+ assert_nil t.errors.on(:title)
+
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :content) }
+ end
+
+ def test_validate_format_with_allow_blank
+ Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank=>true)
+ assert !Topic.create("title" => "Shouldn't be valid").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "Validation macros rule!").valid?
+ end
+
+ # testing ticket #3142
+ def test_validate_format_numeric
+ Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
+
+ t = Topic.create("title" => "72x", "content" => "6789")
+ assert !t.valid?, "Shouldn't be valid"
+ assert !t.save, "Shouldn't save because it's invalid"
+ assert_equal "is bad data", t.errors.on(:title)
+ assert_nil t.errors.on(:content)
+
+ t.title = "-11"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "03"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "z44"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "5v7"
+ assert !t.valid?, "Shouldn't be valid"
+
+ t.title = "1"
+
+ assert t.save
+ assert_nil t.errors.on(:title)
+ end
+
+ def test_validate_format_with_formatted_message
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %s")
+ t = Topic.create(:title => 'Invalid title')
+ assert_equal "can't be Invalid title", t.errors.on(:title)
+ end
+
+ def test_validates_inclusion_of
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "a b", "content" => "abc").valid?
+ assert !Topic.create("title" => nil, "content" => "def").valid?
+
+ t = Topic.create("title" => "a", "content" => "I know you are but what am I?")
+ assert t.valid?
+ t.title = "uhoh"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is not included in the list", t.errors["title"]
+
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) }
+ assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) }
+
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) }
+ assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) }
+ end
+
+ def test_validates_inclusion_of_with_allow_nil
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil=>true )
+
+ assert !Topic.create("title" => "a!", "content" => "abc").valid?
+ assert !Topic.create("title" => "", "content" => "abc").valid?
+ assert Topic.create("title" => nil, "content" => "abc").valid?
+ end
+
+ def test_numericality_with_getter_method
+ Developer.validates_numericality_of( :salary )
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
+
+ def test_validates_length_of_with_allow_nil
+ Topic.validates_length_of( :title, :is => 5, :allow_nil=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert !Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_length_of_with_allow_blank
+ Topic.validates_length_of( :title, :is => 5, :allow_blank=>true )
+
+ assert !Topic.create("title" => "ab").valid?
+ assert Topic.create("title" => "").valid?
+ assert Topic.create("title" => nil).valid?
+ assert Topic.create("title" => "abcde").valid?
+ end
+
+ def test_validates_inclusion_of_with_formatted_message
+ Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %s is not in the list" )
+
+ assert Topic.create("title" => "a", "content" => "abc").valid?
+
+ t = Topic.create("title" => "uhoh", "content" => "abc")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "option uhoh is not in the list", t.errors["title"]
+ end
+
+ def test_numericality_with_allow_nil_and_getter_method
+ Developer.validates_numericality_of( :salary, :allow_nil => true)
+ developer = Developer.new("name" => "michael", "salary" => nil)
+ developer.instance_eval("def salary; read_attribute('salary') ? read_attribute('salary') : 100000; end")
+ assert developer.valid?
+ end
+
+ def test_validates_exclusion_of
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) )
+
+ assert Topic.create("title" => "something", "content" => "abc").valid?
+ assert !Topic.create("title" => "monkey", "content" => "abc").valid?
+ end
+
+ def test_validates_exclusion_of_with_formatted_message
+ Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %s is restricted" )
+
+ assert Topic.create("title" => "something", "content" => "abc")
+
+ t = Topic.create("title" => "monkey")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "option monkey is restricted", t.errors["title"]
+ end
+
+ def test_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "not"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
+
+ t.title = ""
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
+
+ t.title = nil
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
+ end
+
+ def test_optionally_validates_length_of_using_minimum
+ Topic.validates_length_of :title, :minimum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
+
+ t.title = ""
+ assert t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_maximum
+ Topic.validates_length_of :title, :maximum => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_within
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long")
+ assert !t.valid?
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
+ assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
+
+ t.title = nil
+ t.content = nil
+ assert !t.valid?
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:content)
+
+ t.title = "abe"
+ t.content = "mad"
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within
+ Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true
+
+ t = Topic.create('title' => 'abc', 'content' => 'abcd')
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :create, :too_long => "my string is too long: %d"
+
+ t = Topic.create("title" => "thisisnotvalid", "content" => "whatever")
+ assert !t.save
+ assert t.errors.on(:title)
+ assert_equal "my string is too long: 10", t.errors[:title]
+
+ t.title = "butthisis"
+ assert t.save
+
+ t.title = "few"
+ assert t.save
+
+ t.content = "andthisislong"
+ assert t.save
+
+ t.content = t.title = "iamfine"
+ assert t.save
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update
+ Topic.validates_length_of :title, :content, :within => 5..10, :on => :update, :too_short => "my string is too short: %d"
+
+ t = Topic.create("title" => "vali", "content" => "whatever")
+ assert !t.save
+ assert t.errors.on(:title)
+
+ t.title = "not"
+ assert !t.save
+ assert t.errors.on(:title)
+ assert_equal "my string is too short: 5", t.errors[:title]
+
+ t.title = "valid"
+ t.content = "andthisistoolong"
+ assert !t.save
+ assert t.errors.on(:content)
+
+ t.content = "iamfine"
+ assert t.save
+ end
+
+ def test_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "notvalid"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
+
+ t.title = ""
+ assert !t.valid?
+
+ t.title = nil
+ assert !t.valid?
+ end
+
+ def test_optionally_validates_length_of_using_is
+ Topic.validates_length_of :title, :is => 5, :allow_nil => true
+
+ t = Topic.create("title" => "valid", "content" => "whatever")
+ assert t.valid?
+
+ t.title = nil
+ assert t.valid?
+ end
+
+ def test_validates_length_of_using_bignum
+ bigmin = 2 ** 30
+ bigmax = 2 ** 32
+ bigrange = bigmin...bigmax
+ assert_nothing_raised do
+ Topic.validates_length_of :title, :is => bigmin + 5
+ Topic.validates_length_of :title, :within => bigrange
+ Topic.validates_length_of :title, :in => bigrange
+ Topic.validates_length_of :title, :minimum => bigmin
+ Topic.validates_length_of :title, :maximum => bigmax
+ end
+ end
+
+ def test_validates_length_with_globally_modified_error_message
+ ActiveRecord::Errors.default_error_messages[:too_short] = 'tu est trops petit hombre %d'
+ Topic.validates_length_of :title, :minimum => 10
+ t = Topic.create(:title => 'too short')
+ assert !t.valid?
+
+ assert_equal 'tu est trops petit hombre 10', t.errors['title']
+ end
+
+ def test_validates_size_of_association
+ assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
+ t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
+ assert !t.save
+ assert t.errors.on(:replies)
+ reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
+ assert t.valid?
+ end
+
+ def test_validates_size_of_association_using_within
+ assert_nothing_raised { Topic.validates_size_of :replies, :within => 1..2 }
+ t = Topic.new('title' => 'noreplies', 'content' => 'whatever')
+ assert !t.save
+ assert t.errors.on(:replies)
+
+ reply = t.replies.build('title' => 'areply', 'content' => 'whateveragain')
+ assert t.valid?
+
+ 2.times { t.replies.build('title' => 'areply', 'content' => 'whateveragain') }
+ assert !t.save
+ assert t.errors.on(:replies)
+ end
+
+ def test_validates_length_of_nasty_params
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :maximum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :minimum=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>6, :is=>9) }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within=>"a") }
+ assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is=>"a") }
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_message
+ Topic.validates_length_of( :title, :minimum=>5, :message=>"boo %d" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "boo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_minimum_with_too_short
+ Topic.validates_length_of( :title, :minimum=>5, :too_short=>"hoo %d" )
+ t = Topic.create("title" => "uhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_message
+ Topic.validates_length_of( :title, :maximum=>5, :message=>"boo %d" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "boo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_maximum_with_too_long
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_message
+ Topic.validates_length_of( :title, :is=>5, :message=>"boo %d" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "boo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_custom_errors_for_is_with_wrong_length
+ Topic.validates_length_of( :title, :is=>5, :wrong_length=>"hoo %d" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_validates_length_of_using_minimum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :minimum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二三四"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too short (minimum is 5 characters)", t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_maximum_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :maximum => 5
+
+ t = Topic.create("title" => "一二三四五", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二34五å…"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is too long (maximum is 5 characters)", t.errors["title"]
+ end
+ end
+
+ def test_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of(:title, :content, :within => 3..5)
+
+ t = Topic.new("title" => "一二", "content" => "12三四五å…七")
+ assert !t.valid?
+ assert_equal "is too short (minimum is 3 characters)", t.errors.on(:title)
+ assert_equal "is too long (maximum is 5 characters)", t.errors.on(:content)
+ t.title = "一二三"
+ t.content = "12三"
+ assert t.valid?
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 3..5, :allow_nil => true
+
+ t = Topic.create(:title => "一二三四五")
+ assert t.valid?, t.errors.inspect
+
+ t = Topic.create(:title => "一二三")
+ assert t.valid?, t.errors.inspect
+
+ t.title = nil
+ assert t.valid?, t.errors.inspect
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_create_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :create, :too_long => "é•·ã™ãŽã¾ã™: %d"
+
+ t = Topic.create("title" => "一二三四五å…七八ä¹åA", "content" => "whatever")
+ assert !t.save
+ assert t.errors.on(:title)
+ assert_equal "é•·ã™ãŽã¾ã™: 10", t.errors[:title]
+
+ t.title = "一二三四五å…七八ä¹"
+ assert t.save
+
+ t.title = "一二3"
+ assert t.save
+
+ t.content = "一二三四五å…七八ä¹å"
+ assert t.save
+
+ t.content = t.title = "一二三四五å…"
+ assert t.save
+ end
+ end
+
+ def test_optionally_validates_length_of_using_within_on_update_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :within => 5..10, :on => :update, :too_short => "çŸã™ãŽã¾ã™: %d"
+
+ t = Topic.create("title" => "一二三4", "content" => "whatever")
+ assert !t.save
+ assert t.errors.on(:title)
+
+ t.title = "1二三4"
+ assert !t.save
+ assert t.errors.on(:title)
+ assert_equal "çŸã™ãŽã¾ã™: 5", t.errors[:title]
+
+ t.title = "一二三四五å…七八ä¹åA"
+ assert !t.save
+ assert t.errors.on(:title)
+
+ t.title = "一二345"
+ assert t.save
+ end
+ end
+
+ def test_validates_length_of_using_is_utf8
+ with_kcode('UTF8') do
+ Topic.validates_length_of :title, :is => 5
+
+ t = Topic.create("title" => "一二345", "content" => "whatever")
+ assert t.valid?
+
+ t.title = "一二345å…"
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "is the wrong length (should be 5 characters)", t.errors["title"]
+ end
+ end
+
+ def test_validates_size_of_association_utf8
+ with_kcode('UTF8') do
+ assert_nothing_raised { Topic.validates_size_of :replies, :minimum => 1 }
+ t = Topic.new('title' => 'ã‚ã„ã†ãˆãŠ', 'content' => 'ã‹ããã‘ã“')
+ assert !t.save
+ assert t.errors.on(:replies)
+ t.replies.build('title' => 'ã‚ã„ã†ãˆãŠ', 'content' => 'ã‹ããã‘ã“')
+ assert t.valid?
+ end
+ end
+
+ def test_validates_associated_many
+ Topic.validates_associated( :replies )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")]
+ assert !t.valid?
+ assert t.errors.on(:replies)
+ assert_equal 1, r.errors.count # make sure all associated objects have been validated
+ assert_equal 0, r2.errors.count
+ assert_equal 1, r3.errors.count
+ assert_equal 0, r4.errors.count
+ r.content = r3.content = "non-empty"
+ assert t.valid?
+ end
+
+ def test_validates_associated_one
+ Reply.validates_associated( :topic )
+ Topic.validates_presence_of( :content )
+ r = Reply.new("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert r.errors.on(:topic)
+ r.topic.content = "non-empty"
+ assert r.valid?
+ end
+
+ def test_validate_block
+ Topic.validate { |topic| topic.errors.add("title", "will never be valid") }
+ t = Topic.create("title" => "Title", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "will never be valid", t.errors["title"]
+ end
+
+ def test_invalid_validator
+ Topic.validate 3
+ assert_raise(ArgumentError) { t = Topic.create }
+ end
+
+ def test_throw_away_typing
+ d = Developer.new("name" => "David", "salary" => "100,000")
+ assert !d.valid?
+ assert_equal 100, d.salary
+ assert_equal "100,000", d.salary_before_type_cast
+ end
+
+ def test_validates_acceptance_of_with_custom_error_using_quotes
+ Developer.validates_acceptance_of :salary, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "0"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ end
+
+ def test_validates_confirmation_of_with_custom_error_using_quotes
+ Developer.validates_confirmation_of :name, :message=> "confirm 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "John"
+ d.name_confirmation = "Johnny"
+ assert !d.valid?
+ assert_equal "confirm 'single' and \"double\" quotes", d.errors.on(:name)
+ end
+
+ def test_validates_format_of_with_custom_error_using_quotes
+ Developer.validates_format_of :name, :with => /^(A-Z*)$/, :message=> "format 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = d.name_confirmation = "John 32"
+ assert !d.valid?
+ assert_equal "format 'single' and \"double\" quotes", d.errors.on(:name)
+ end
+
+ def test_validates_inclusion_of_with_custom_error_using_quotes
+ Developer.validates_inclusion_of :salary, :in => 1000..80000, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.salary = "90,000"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:salary).last
+ end
+
+ def test_validates_length_of_with_custom_too_long_using_quotes
+ Developer.validates_length_of :name, :maximum => 4, :too_long=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Jeffrey"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ end
+
+ def test_validates_length_of_with_custom_too_short_using_quotes
+ Developer.validates_length_of :name, :minimum => 4, :too_short=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ end
+
+ def test_validates_length_of_with_custom_message_using_quotes
+ Developer.validates_length_of :name, :minimum => 4, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ end
+
+ def test_validates_presence_of_with_custom_message_using_quotes
+ Developer.validates_presence_of :non_existent, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "Joe"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:non_existent)
+ end
+
+ def test_validates_uniqueness_of_with_custom_message_using_quotes
+ Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes"
+ d = Developer.new
+ d.name = "David"
+ assert !d.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", d.errors.on(:name).last
+ end
+
+ def test_validates_associated_with_custom_message_using_quotes
+ Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes"
+ Topic.validates_presence_of :content
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ r.topic = Topic.create("title" => "uhohuhoh")
+ assert !r.valid?
+ assert_equal "This string contains 'single' and \"double\" quotes", r.errors.on(:topic).last
+ end
+
+ def test_if_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_unless_validation_using_method_true
+ # When the method returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_if_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_unless_validation_using_method_false
+ # When the method returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => :condition_is_true_but_its_not )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_if_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_unless_validation_using_string_true
+ # When the evaluated string returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "a = 1; a == 1" )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_if_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :if => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_unless_validation_using_string_false
+ # When the evaluated string returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d", :unless => "false")
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_if_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ :if => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_unless_validation_using_block_true
+ # When the block returns true
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ :unless => Proc.new { |r| r.content.size > 4 } )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_if_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ :if => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert t.valid?
+ assert !t.errors.on(:title)
+ end
+
+ def test_unless_validation_using_block_false
+ # When the block returns false
+ Topic.validates_length_of( :title, :maximum=>5, :too_long=>"hoo %d",
+ :unless => Proc.new { |r| r.title != "uhohuhoh"} )
+ t = Topic.create("title" => "uhohuhoh", "content" => "whatever")
+ assert !t.valid?
+ assert t.errors.on(:title)
+ assert_equal "hoo 5", t.errors["title"]
+ end
+
+ def test_validates_associated_missing
+ Reply.validates_presence_of(:topic)
+ r = Reply.create("title" => "A reply", "content" => "with content!")
+ assert !r.valid?
+ assert r.errors.on(:topic)
+
+ r.topic = Topic.find :first
+ assert r.valid?
+ end
+
+ def test_errors_to_xml
+ r = Reply.new :title => "Wrong Create"
+ assert !r.valid?
+ xml = r.errors.to_xml(:skip_instruct => true)
+ assert_equal "", xml.first(8)
+ assert xml.include?("Title is Wrong Create")
+ assert xml.include?("Content Empty")
+ end
+
+ def test_validation_order
+ Topic.validates_presence_of :title
+ Topic.validates_length_of :title, :minimum => 2
+
+ t = Topic.new("title" => "")
+ assert !t.valid?
+ assert_equal "can't be blank", t.errors.on("title").first
+ end
+
+ # previous implementation of validates_presence_of eval'd the
+ # string with the wrong binding, this regression test is to
+ # ensure that it works correctly
+ def test_validation_with_if_as_string
+ Topic.validates_presence_of(:title)
+ Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')")
+
+ t = Topic.new
+ assert !t.valid?, "A topic without a title should not be valid"
+ assert !t.errors.invalid?("author_name"), "A topic without an 'important' title should not require an author"
+
+ t.title = "Just a title"
+ assert t.valid?, "A topic with a basic title should be valid"
+
+ t.title = "A very important title"
+ assert !t.valid?, "A topic with an important title, but without an author, should not be valid"
+ assert t.errors.invalid?("author_name"), "A topic with an 'important' title should require an author"
+
+ t.author_name = "Hubert J. Farnsworth"
+ assert t.valid?, "A topic with an important title and author should be valid"
+ end
+
+ private
+ def with_kcode(kcode)
+ if RUBY_VERSION < '1.9'
+ orig_kcode, $KCODE = $KCODE, kcode
+ begin
+ yield
+ ensure
+ $KCODE = orig_kcode
+ end
+ else
+ yield
+ end
+ end
+end
+
+
+class ValidatesNumericalityTest < ActiveRecord::TestCase
+ NIL = [nil]
+ BLANK = ["", " ", " \t \r \n"]
+ BIGDECIMAL_STRINGS = %w(12345678901234567890.1234567890) # 30 significent digits
+ FLOAT_STRINGS = %w(0.0 +0.0 -0.0 10.0 10.5 -10.5 -0.0001 -090.1 90.1e1 -90.1e5 -90.1e-5 90e-5)
+ INTEGER_STRINGS = %w(0 +0 -0 10 +10 -10 0090 -090)
+ FLOATS = [0.0, 10.0, 10.5, -10.5, -0.0001] + FLOAT_STRINGS
+ INTEGERS = [0, 10, -10] + INTEGER_STRINGS
+ BIGDECIMAL = BIGDECIMAL_STRINGS.collect! { |bd| BigDecimal.new(bd) }
+ JUNK = ["not a number", "42 not a number", "0xdeadbeef", "00-1", "--3", "+-3", "+3-1", "-+019.0", "12.12.13.12", "123\nnot a number"]
+
+ def setup
+ Topic.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Topic.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ Topic.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new)
+ end
+
+ def test_default_validates_numericality_of
+ Topic.validates_numericality_of :approved
+
+ invalid!(NIL + BLANK + JUNK)
+ valid!(FLOATS + INTEGERS + BIGDECIMAL)
+ end
+
+ def test_validates_numericality_of_with_nil_allowed
+ Topic.validates_numericality_of :approved, :allow_nil => true
+
+ invalid!(BLANK + JUNK)
+ valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL)
+ end
+
+ def test_validates_numericality_of_with_integer_only
+ Topic.validates_numericality_of :approved, :only_integer => true
+
+ invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL)
+ valid!(INTEGERS)
+ end
+
+ def test_validates_numericality_of_with_integer_only_and_nil_allowed
+ Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true
+
+ invalid!(BLANK + JUNK + FLOATS + BIGDECIMAL)
+ valid!(NIL + INTEGERS)
+ end
+
+ def test_validates_numericality_with_greater_than
+ Topic.validates_numericality_of :approved, :greater_than => 10
+
+ invalid!([-10, 10], 'must be greater than 10')
+ valid!([11])
+ end
+
+ def test_validates_numericality_with_greater_than_or_equal
+ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10
+
+ invalid!([-9, 9], 'must be greater than or equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_equal_to
+ Topic.validates_numericality_of :approved, :equal_to => 10
+
+ invalid!([-10, 11], 'must be equal to 10')
+ valid!([10])
+ end
+
+ def test_validates_numericality_with_less_than
+ Topic.validates_numericality_of :approved, :less_than => 10
+
+ invalid!([10], 'must be less than 10')
+ valid!([-9, 9])
+ end
+
+ def test_validates_numericality_with_less_than_or_equal_to
+ Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10
+
+ invalid!([11], 'must be less than or equal to 10')
+ valid!([-10, 10])
+ end
+
+ def test_validates_numericality_with_odd
+ Topic.validates_numericality_of :approved, :odd => true
+
+ invalid!([-2, 2], 'must be odd')
+ valid!([-1, 1])
+ end
+
+ def test_validates_numericality_with_even
+ Topic.validates_numericality_of :approved, :even => true
+
+ invalid!([-1, 1], 'must be even')
+ valid!([-2, 2])
+ end
+
+ def test_validates_numericality_with_greater_than_less_than_and_even
+ Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true
+
+ invalid!([1, 3, 4])
+ valid!([2])
+ end
+
+ def test_validates_numericality_with_numeric_message
+ Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %d"
+ topic = Topic.new("title" => "numeric test", "approved" => 10)
+
+ assert !topic.valid?
+ assert_equal "smaller than 4", topic.errors.on(:approved)
+
+ Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %d"
+ topic = Topic.new("title" => "numeric test", "approved" => 1)
+
+ assert !topic.valid?
+ assert_equal "greater than 4", topic.errors.on(:approved)
+ end
+
+ private
+ def invalid!(values, error=nil)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert !topic.valid?, "#{value.inspect} not rejected as a number"
+ assert topic.errors.on(:approved)
+ assert_equal error, topic.errors.on(:approved) if error
+ end
+ end
+
+ def valid!(values)
+ with_each_topic_approved_value(values) do |topic, value|
+ assert topic.valid?, "#{value.inspect} not accepted as a number"
+ end
+ end
+
+ def with_each_topic_approved_value(values)
+ topic = Topic.new("title" => "numeric test", "content" => "whatever")
+ values.each do |value|
+ topic.approved = value
+ yield topic, value
+ end
+ end
+end
diff --git a/vendor/rails/activerecord/test/cases/xml_serialization_test.rb b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb
new file mode 100644
index 0000000..63f4886
--- /dev/null
+++ b/vendor/rails/activerecord/test/cases/xml_serialization_test.rb
@@ -0,0 +1,202 @@
+require "cases/helper"
+require 'models/contact'
+require 'models/post'
+require 'models/author'
+require 'models/tagging'
+require 'models/comment'
+
+class XmlSerializationTest < ActiveRecord::TestCase
+ def test_should_serialize_default_root
+ @xml = Contact.new.to_xml
+ assert_match %r{^}, @xml
+ assert_match %r{$}, @xml
+ end
+
+ def test_should_serialize_default_root_with_namespace
+ @xml = Contact.new.to_xml :namespace=>"http://xml.rubyonrails.org/contact"
+ assert_match %r{^}, @xml
+ assert_match %r{$}, @xml
+ end
+
+ def test_should_serialize_custom_root
+ @xml = Contact.new.to_xml :root => 'xml_contact'
+ assert_match %r{^}, @xml
+ assert_match %r{$}, @xml
+ end
+
+ def test_should_allow_undasherized_tags
+ @xml = Contact.new.to_xml :root => 'xml_contact', :dasherize => false
+ assert_match %r{^}, @xml
+ assert_match %r{$}, @xml
+ assert_match %r{David}, @xml
+ end
+end
+
+class DefaultXmlSerializationTest < ActiveRecord::TestCase
+ def setup
+ @xml = Contact.new(:name => 'aaron stack', :age => 25, :avatar => 'binarydata', :created_at => Time.utc(2006, 8, 1), :awesome => false, :preferences => { :gem => 'ruby' }).to_xml
+ end
+
+ def test_should_serialize_string
+ assert_match %r{aaron stack}, @xml
+ end
+
+ def test_should_serialize_integer
+ assert_match %r{25}, @xml
+ end
+
+ def test_should_serialize_binary
+ assert_match %r{YmluYXJ5ZGF0YQ==\n}, @xml
+ assert_match %r{2006-08-01T00:00:00Z}, @xml
+ end
+
+ def test_should_serialize_boolean
+ assert_match %r{false}, @xml
+ end
+
+ def test_should_serialize_yaml
+ assert_match %r{--- \n:gem: ruby\n}, @xml
+ end
+end
+
+class NilXmlSerializationTest < ActiveRecord::TestCase
+ def setup
+ @xml = Contact.new.to_xml(:root => 'xml_contact')
+ end
+
+ def test_should_serialize_string
+ assert_match %r{}, @xml
+ end
+
+ def test_should_serialize_integer
+ assert %r{}.match(@xml)
+ attributes = $1
+ assert_match %r{nil="true"}, attributes
+ assert_match %r{type="integer"}, attributes
+ end
+
+ def test_should_serialize_binary
+ assert %r{}.match(@xml)
+ attributes = $1
+ assert_match %r{type="binary"}, attributes
+ assert_match %r{encoding="base64"}, attributes
+ assert_match %r{nil="true"}, attributes
+ end
+
+ def test_should_serialize_datetime
+ assert %r{}.match(@xml)
+ attributes = $1
+ assert_match %r{nil="true"}, attributes
+ assert_match %r{type="datetime"}, attributes
+ end
+
+ def test_should_serialize_boolean
+ assert %r{}.match(@xml)
+ attributes = $1
+ assert_match %r{type="boolean"}, attributes
+ assert_match %r{nil="true"}, attributes
+ end
+
+ def test_should_serialize_yaml
+ assert %r{}.match(@xml)
+ attributes = $1
+ assert_match %r{type="yaml"}, attributes
+ assert_match %r{nil="true"}, attributes
+ end
+end
+
+class DatabaseConnectedXmlSerializationTest < ActiveRecord::TestCase
+ fixtures :authors, :posts
+ # to_xml used to mess with the hash the user provided which
+ # caused the builder to be reused. This meant the document kept
+ # getting appended to.
+ def test_passing_hash_shouldnt_reuse_builder
+ options = {:include=>:posts}
+ david = authors(:david)
+ first_xml_size = david.to_xml(options).size
+ second_xml_size = david.to_xml(options).size
+ assert_equal first_xml_size, second_xml_size
+ end
+
+ def test_include_uses_association_name
+ xml = authors(:david).to_xml :include=>:hello_posts, :indent => 0
+ assert_match %r{}, xml
+ assert_match %r{}, xml
+ assert_match %r{}, xml
+ end
+
+ def test_methods_are_called_on_object
+ xml = authors(:david).to_xml :methods => :label, :indent => 0
+ assert_match %r{}, xml
+ end
+
+ def test_should_not_call_methods_on_associations_that_dont_respond
+ xml = authors(:david).to_xml :include=>:hello_posts, :methods => :label, :indent => 2
+ assert !authors(:david).hello_posts.first.respond_to?(:label)
+ assert_match %r{^ }, xml
+ assert_no_match %r{^