diff options
413 files changed, 5694 insertions, 3951 deletions
@@ -61,11 +61,12 @@ platforms :ruby do end platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7' + gem 'json' + gem 'activerecord-jdbcsqlite3-adapter', '>= 1.3.0' group :db do - gem 'activerecord-jdbcmysql-adapter', '>= 1.2.7' - gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.7' + gem 'activerecord-jdbcmysql-adapter', '>= 1.3.0' + gem 'activerecord-jdbcpostgresql-adapter', '>= 1.3.0' end end @@ -8,11 +8,6 @@ pattern. Understanding the MVC pattern is key to understanding Rails. MVC divides your application into three layers, each with a specific responsibility. -The _View layer_ is composed of "templates" that are responsible for providing -appropriate representations of your application's resources. Templates can -come in a variety of formats, but most view templates are HTML with embedded -Ruby code (ERB files). - The _Model layer_ represents your domain model (such as Account, Product, Person, Post, etc.) and encapsulates the business logic that is specific to your application. In Rails, database-backed model classes are derived from @@ -24,16 +19,26 @@ as provided by the Active Model module. You can read more about Active Record in its [README](activerecord/README.rdoc). The _Controller layer_ is responsible for handling incoming HTTP requests and -providing a suitable response. Usually this means returning HTML, but Rails -controllers can also generate XML, JSON, PDFs, mobile-specific views, and -more. Controllers manipulate models and render view templates in order to -generate the appropriate HTTP response. - -In Rails, the Controller and View layers are handled together by Action Pack. -These two layers are bundled in a single package due to their heavy interdependence. -This is unlike the relationship between Active Record and Action Pack, which are -independent. Each of these packages can be used independently outside of Rails. You -can read more about Action Pack in its [README](actionpack/README.rdoc). +providing a suitable response. Usually this means returning HTML, but Rails controllers +can also generate XML, JSON, PDFs, mobile-specific views, and more. Controllers load and +manipulate models, and render view templates in order to generate the appropriate HTTP response. +In Rails, incoming requests are routed by Action Dispatch to an appropriate controller, and +controller classes are derived from `ActionController::Base`. Action Dispatch and Action Controller +are bundled together in Action Pack. You can read more about Action Pack in its +[README](actionpack/README.rdoc). + +The _View layer_ is composed of "templates" that are responsible for providing +appropriate representations of your application's resources. Templates can +come in a variety of formats, but most view templates are HTML with embedded +Ruby code (ERB files). Views are typically rendered to generate a controller response, +or to generate the body of an email. In Rails, View generation is handled by Action View. +You can read more about Action View in its [README](actionview/README.rdoc). + +Active Record, Action Pack, and Action View can each be used independently outside Rails. +In addition to them, Rails also comes with Action Mailer ([README](actionmailer/README.rdoc)), a library +to generate and send emails; and Active Support ([README](activesupport/README.rdoc)), a collection of +utility classes and standard library extensions that are useful for Rails, and may also be used +independently outside Rails. ## Getting Started diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index a14a6ba18f..178572706c 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -61,7 +61,7 @@ generated would look like this: Thank you for signing up! -In previous version of Rails you would call <tt>create_method_name</tt> and +In previous versions of Rails you would call <tt>create_method_name</tt> and <tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface - you simply call the method and optionally call +deliver+ on the return value. diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index c56b6979ef..9b25feaf75 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency 'actionpack', version + s.add_dependency 'actionview', version s.add_dependency 'mail', '~> 2.5.4' end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index c45124be80..5b6960c8fc 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -22,7 +22,6 @@ #++ require 'abstract_controller' -require 'action_view' require 'action_mailer/version' # Common Active Support usage in Action Mailer diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index cc3a412221..ada86fbc4f 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -3,6 +3,7 @@ require 'action_mailer/collector' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/module/anonymous' + require 'action_mailer/log_subscriber' module ActionMailer @@ -361,11 +362,12 @@ module ActionMailer # <tt>delivery_method :test</tt>. Most useful for unit and functional testing. class Base < AbstractController::Base include DeliveryMethods + abstract! - include AbstractController::Logger include AbstractController::Rendering - include AbstractController::Layouts + + include AbstractController::Logger include AbstractController::Helpers include AbstractController::Translation include AbstractController::AssetPaths diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index c108156792..8467d45986 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -1,3 +1,5 @@ +require 'active_support/log_subscriber' + module ActionMailer # Implements the ActiveSupport::LogSubscriber for logging notifications when # email is delivered and received. diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index ed8cf72cd0..aa18c512c7 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -11,11 +11,11 @@ end require 'active_support/testing/autorun' require 'action_mailer' require 'action_mailer/test_case' +require 'mail' -silence_warnings do - # These external dependencies have warnings :/ - require 'mail' -end +# Emulate AV railtie +require 'action_view' +ActionMailer::Base.send(:include, ActionView::Layouts) # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/actionmailer/test/fixtures/raw_email10 b/actionmailer/test/fixtures/raw_email10 deleted file mode 100644 index edad5ccff1..0000000000 --- a/actionmailer/test/fixtures/raw_email10 +++ /dev/null @@ -1,20 +0,0 @@ -Return-Path: <xxx@xxxx.xxx> -Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500 -Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500 -Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500 -Date: Tue, 10 May 2005 15:27:03 -0500 -From: xxx@xxxx.xxx -Sender: xxx@xxxx.xxx -To: xxxxxxxxxxx@xxxx.xxxx.xxx -Message-Id: <xxx@xxxx.xxx> -X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx -Delivered-To: xxx@xxxx.xxx -Importance: normal -Content-Type: text/plain; charset=X-UNKNOWN - -Test test. Hi. Waving. m - ----------------------------------------------------------------- -Sent via Bell Mobility's Text Messaging service. -Envoyé par le service de messagerie texte de Bell Mobilité. ----------------------------------------------------------------- diff --git a/actionmailer/test/fixtures/raw_email12 b/actionmailer/test/fixtures/raw_email12 deleted file mode 100644 index 2cd31720d3..0000000000 --- a/actionmailer/test/fixtures/raw_email12 +++ /dev/null @@ -1,32 +0,0 @@ -Mime-Version: 1.0 (Apple Message framework v730) -Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 -Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> -From: foo@example.com -Subject: testing -Date: Mon, 6 Jun 2005 22:21:22 +0200 -To: blah@example.com - - ---Apple-Mail-13-196941151 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; - charset=ISO-8859-1; - delsp=yes; - format=flowed - -This is the first part. - ---Apple-Mail-13-196941151 -Content-Type: image/jpeg -Content-Transfer-Encoding: base64 -Content-Location: Photo25.jpg -Content-ID: <qbFGyPQAS8> -Content-Disposition: inline - -jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw -ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE -QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB -gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5= - ---Apple-Mail-13-196941151-- - diff --git a/actionmailer/test/fixtures/raw_email13 b/actionmailer/test/fixtures/raw_email13 deleted file mode 100644 index 7d9314e36a..0000000000 --- a/actionmailer/test/fixtures/raw_email13 +++ /dev/null @@ -1,29 +0,0 @@ -Mime-Version: 1.0 (Apple Message framework v730) -Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 -Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> -From: foo@example.com -Subject: testing -Date: Mon, 6 Jun 2005 22:21:22 +0200 -To: blah@example.com - - ---Apple-Mail-13-196941151 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; - charset=ISO-8859-1; - delsp=yes; - format=flowed - -This is the first part. - ---Apple-Mail-13-196941151 -Content-Type: text/x-ruby-script; name="hello.rb" -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; - filename="api.rb" - -puts "Hello, world!" -gets - ---Apple-Mail-13-196941151-- - diff --git a/actionmailer/test/fixtures/raw_email2 b/actionmailer/test/fixtures/raw_email2 deleted file mode 100644 index 9f87bb2a98..0000000000 --- a/actionmailer/test/fixtures/raw_email2 +++ /dev/null @@ -1,114 +0,0 @@ -From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005 -Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com> -X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com -Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com -Received: from localhost (localhost [127.0.0.1]) - by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D - for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT) -Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1]) - by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024) - with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>; - Sun, 8 May 2005 19:09:12 +0000 (GMT) -Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150]) - by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960 - for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT) -Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199]) - by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB - for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT) -Received: by zproxy.gmail.com with SMTP id 13so1233405nzp - for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT) -DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; - s=beta; d=gmail.com; - h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references; - b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8= -Received: by 10.36.10.16 with SMTP id 16mr1012493nzj; - Sun, 08 May 2005 12:09:11 -0700 (PDT) -Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT) -Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com> -Date: Sun, 8 May 2005 14:09:11 -0500 -From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com> -Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com> -To: xxxxx xxxx <xxxxx@xxxxxxxxx.com> -Subject: Fwd: Signed email causes file attachments -In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com> -Mime-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_Part_5028_7368284.1115579351471" -References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com> - -------=_Part_5028_7368284.1115579351471 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable -Content-Disposition: inline - -We should not include these files or vcards as attachments. - ----------- Forwarded message ---------- -From: xxxxx xxxxxx <xxxxxxxx@xxx.com> -Date: May 8, 2005 1:17 PM -Subject: Signed email causes file attachments -To: xxxxxxx@xxxxxxxxxx.com - - -Hi, - -Just started to use my xxxxxxxx account (to set-up a GTD system, -natch) and noticed that when I send content via email the signature/ -certificate from my email account gets added as a file (e.g. -"smime.p7s"). - -Obviously I can uncheck the signature option in the Mail compose -window but how often will I remember to do that? - -Is there any way these kind of files could be ignored, e.g. via some -sort of exclusions list? - -------=_Part_5028_7368284.1115579351471 -Content-Type: application/pkcs7-signature; name=smime.p7s -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="smime.p7s" - -MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w -ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh -d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt -YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD -ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL -7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw -o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla -hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp -+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1 -ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw -DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd -L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/ -lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3 -DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD -YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0 -aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg -Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3 -MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv -bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz -c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f -6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk -KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj -gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo -YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R -BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM -0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ -GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ -Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl -IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls -IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB -MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp -fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3 -dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h -aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG -A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv -bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW -8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8 -rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA -nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7 -Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0 -fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA -------=_Part_5028_7368284.1115579351471-- - diff --git a/actionmailer/test/fixtures/raw_email3 b/actionmailer/test/fixtures/raw_email3 deleted file mode 100644 index 3a0927490a..0000000000 --- a/actionmailer/test/fixtures/raw_email3 +++ /dev/null @@ -1,70 +0,0 @@ -From xxxx@xxxx.com Tue May 10 11:28:07 2005 -Return-Path: <xxxx@xxxx.com> -X-Original-To: xxxx@xxxx.com -Delivered-To: xxxx@xxxx.com -Received: from localhost (localhost [127.0.0.1]) - by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F - for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT) -Received: from xxx.xxxxx.com ([127.0.0.1]) - by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024) - with LMTP id 70060-03 for <xxxx@xxxx.com>; - Tue, 10 May 2005 17:26:49 +0000 (GMT) -Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150]) - by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B - for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT) -Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203]) - by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C - for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT) -Received: by xxx.xxxxx.com with SMTP id 68so1694448wri - for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT) -DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; - s=beta; d=xxxxx.com; - h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type; - b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A= -Received: by 10.54.96.19 with SMTP id t19mr621017wrb; - Tue, 10 May 2005 10:26:39 -0700 (PDT) -Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT) -Message-ID: <xxxx@xxxx.com> -Date: Tue, 10 May 2005 11:26:39 -0600 -From: Test Tester <xxxx@xxxx.com> -Reply-To: Test Tester <xxxx@xxxx.com> -To: xxxx@xxxx.com, xxxx@xxxx.com -Subject: Another PDF -Mime-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_Part_2192_32400445.1115745999735" -X-Virus-Scanned: amavisd-new at textdrive.com - -------=_Part_2192_32400445.1115745999735 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable -Content-Disposition: inline - -Just attaching another PDF, here, to see what the message looks like, -and to see if I can figure out what is going wrong here. - -------=_Part_2192_32400445.1115745999735 -Content-Type: application/pdf; name="broken.pdf" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; filename="broken.pdf" - -JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G -bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE -ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu -rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD -QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU -x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab -gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg -1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw -wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI -8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl -fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ -fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR -xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss -nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU -OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF -yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS -HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk -ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw -------=_Part_2192_32400445.1115745999735-- - diff --git a/actionmailer/test/fixtures/raw_email4 b/actionmailer/test/fixtures/raw_email4 deleted file mode 100644 index 639ad40e49..0000000000 --- a/actionmailer/test/fixtures/raw_email4 +++ /dev/null @@ -1,59 +0,0 @@ -Return-Path: <xxx@xxxx.xxx> -Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500 -Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500 -Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500 -Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500 -Date: Sun, 8 May 2005 12:30:08 -0500 -From: xxx@xxxx.xxx -To: xxx@xxxx.xxx -Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx> -Subject: Filth -Mime-Version: 1.0 -Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef -X-Mms-Priority: 1 -X-Mms-Transaction-Id: 3198421808-0 -X-Mms-Message-Type: 0 -X-Mms-Sender-Visibility: 1 -X-Mms-Read-Reply: 1 -X-Original-To: xxx@xxxx.xxx -X-Mms-Message-Class: 0 -X-Mms-Delivery-Report: 0 -X-Mms-Mms-Version: 16 -Delivered-To: xxx@xxxx.xxx -X-Nokia-Ag-Version: 2.0 - -This is a multi-part message in MIME format. - ---mimepart_427e4cb4ca329_133ae40413c81ef -Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217 - - - ---mimepart_427e4cb4cbd97_133ae40413c8217 -Content-Type: text/plain; charset=utf-8 -Content-Transfer-Encoding: 7bit -Content-Disposition: inline -Content-Location: text.txt - -Some text - ---mimepart_427e4cb4cbd97_133ae40413c8217-- - ---mimepart_427e4cb4ca329_133ae40413c81ef -Content-Type: text/plain; charset=us-ascii -Content-Transfer-Encoding: 7bit - - --- -This Orange Multi Media Message was sent wirefree from an Orange -MMS phone. If you would like to reply, please text or phone the -sender directly by using the phone number listed in the sender's -address. To learn more about Orange's Multi Media Messaging -Service, find us on the Web at xxx.xxxx.xxx.uk/mms - - ---mimepart_427e4cb4ca329_133ae40413c81ef - - ---mimepart_427e4cb4ca329_133ae40413c81ef- - diff --git a/actionmailer/test/fixtures/raw_email5 b/actionmailer/test/fixtures/raw_email5 deleted file mode 100644 index bbe31bcdc5..0000000000 --- a/actionmailer/test/fixtures/raw_email5 +++ /dev/null @@ -1,19 +0,0 @@ -Return-Path: <xxx@xxxx.xxx> -Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500 -Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500 -Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500 -Date: Tue, 10 May 2005 15:27:03 -0500 -From: xxx@xxxx.xxx -Sender: xxx@xxxx.xxx -To: xxxxxxxxxxx@xxxx.xxxx.xxx -Message-Id: <xxx@xxxx.xxx> -X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx -Delivered-To: xxx@xxxx.xxx -Importance: normal - -Test test. Hi. Waving. m - ----------------------------------------------------------------- -Sent via Bell Mobility's Text Messaging service. -Envoyé par le service de messagerie texte de Bell Mobilité. ----------------------------------------------------------------- diff --git a/actionmailer/test/fixtures/raw_email6 b/actionmailer/test/fixtures/raw_email6 deleted file mode 100644 index 8e37bd7392..0000000000 --- a/actionmailer/test/fixtures/raw_email6 +++ /dev/null @@ -1,20 +0,0 @@ -Return-Path: <xxx@xxxx.xxx> -Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500 -Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500 -Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500 -Date: Tue, 10 May 2005 15:27:03 -0500 -From: xxx@xxxx.xxx -Sender: xxx@xxxx.xxx -To: xxxxxxxxxxx@xxxx.xxxx.xxx -Message-Id: <xxx@xxxx.xxx> -X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx -Delivered-To: xxx@xxxx.xxx -Importance: normal -Content-Type: text/plain; charset=us-ascii - -Test test. Hi. Waving. m - ----------------------------------------------------------------- -Sent via Bell Mobility's Text Messaging service. -Envoyé par le service de messagerie texte de Bell Mobilité. ----------------------------------------------------------------- diff --git a/actionmailer/test/fixtures/raw_email7 b/actionmailer/test/fixtures/raw_email7 deleted file mode 100644 index da64ada8a5..0000000000 --- a/actionmailer/test/fixtures/raw_email7 +++ /dev/null @@ -1,66 +0,0 @@ -Mime-Version: 1.0 (Apple Message framework v730) -Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151 -Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com> -From: foo@example.com -Subject: testing -Date: Mon, 6 Jun 2005 22:21:22 +0200 -To: blah@example.com - - ---Apple-Mail-13-196941151 -Content-Type: multipart/mixed; - boundary=Apple-Mail-12-196940926 - - ---Apple-Mail-12-196940926 -Content-Transfer-Encoding: quoted-printable -Content-Type: text/plain; - charset=ISO-8859-1; - delsp=yes; - format=flowed - -This is the first part. - ---Apple-Mail-12-196940926 -Content-Transfer-Encoding: 7bit -Content-Type: text/x-ruby-script; - x-unix-mode=0666; - name="test.rb" -Content-Disposition: attachment; - filename=test.rb - -puts "testing, testing" - ---Apple-Mail-12-196940926 -Content-Transfer-Encoding: base64 -Content-Type: application/pdf; - x-unix-mode=0666; - name="test.pdf" -Content-Disposition: inline; - filename=test.pdf - -YmxhaCBibGFoIGJsYWg= - ---Apple-Mail-12-196940926 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset=US-ASCII; - format=flowed - - - ---Apple-Mail-12-196940926-- - ---Apple-Mail-13-196941151 -Content-Transfer-Encoding: base64 -Content-Type: application/pkcs7-signature; - name=smime.p7s -Content-Disposition: attachment; - filename=smime.p7s - -jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw -ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE -QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB -gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5= - ---Apple-Mail-13-196941151-- diff --git a/actionmailer/test/fixtures/raw_email8 b/actionmailer/test/fixtures/raw_email8 deleted file mode 100644 index 79996365b3..0000000000 --- a/actionmailer/test/fixtures/raw_email8 +++ /dev/null @@ -1,47 +0,0 @@ -From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005 -Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com> -Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com> -Date: Sun, 8 May 2005 14:09:11 -0500 -From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com> -Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com> -To: xxxxx xxxx <xxxxx@xxxxxxxxx.com> -Subject: Fwd: Signed email causes file attachments -In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com> -Mime-Version: 1.0 -Content-Type: multipart/mixed; - boundary="----=_Part_5028_7368284.1115579351471" -References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com> - -------=_Part_5028_7368284.1115579351471 -Content-Type: text/plain; charset=ISO-8859-1 -Content-Transfer-Encoding: quoted-printable -Content-Disposition: inline - -We should not include these files or vcards as attachments. - ----------- Forwarded message ---------- -From: xxxxx xxxxxx <xxxxxxxx@xxx.com> -Date: May 8, 2005 1:17 PM -Subject: Signed email causes file attachments -To: xxxxxxx@xxxxxxxxxx.com - - -Hi, - -Test attachments oddly encoded with japanese charset. - - -------=_Part_5028_7368284.1115579351471 -Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3 -Content-Transfer-Encoding: base64 -Content-Disposition: attachment - -MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w -ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh -d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt -YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD -ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL -7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw -------=_Part_5028_7368284.1115579351471-- - diff --git a/actionmailer/test/fixtures/raw_email9 b/actionmailer/test/fixtures/raw_email9 deleted file mode 100644 index 02ea0b05c5..0000000000 --- a/actionmailer/test/fixtures/raw_email9 +++ /dev/null @@ -1,28 +0,0 @@ -Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified) - by xxx.com (CommuniGate Pro SMTP 4.2.8) - with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500 -Received-SPF: softfail - receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx -quite Delivered-To: xxx@xxx.xxx -Received: by xxx.xxx.xxx (Wostfix, from userid xxx) - id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600 -Date: Wed, 23 Feb 2005 18:20:17 -0400 -From: "xxx xxx" <xxx@xxx.xxx> -Message-ID: <4D6AA7EB.6490534@xxx.xxx> -To: xxx@xxx.com -Subject: Stop adware/spyware once and for all. -X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang) - -You are infected with: -Ad Ware and Spy Ware - -Get your free scan and removal download now, -before it gets any worse. - -http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt - - - - -no more? (you will still be infected) -http://xxx.xxx.info/discon/?xxx@xxx.com diff --git a/actionmailer/test/fixtures/raw_email_quoted_with_0d0a b/actionmailer/test/fixtures/raw_email_quoted_with_0d0a deleted file mode 100644 index 8a2c25a5dd..0000000000 --- a/actionmailer/test/fixtures/raw_email_quoted_with_0d0a +++ /dev/null @@ -1,14 +0,0 @@ -Mime-Version: 1.0 (Apple Message framework v730)
-Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
-From: foo@example.com
-Subject: testing
-Date: Mon, 6 Jun 2005 22:21:22 +0200
-To: blah@example.com
-Content-Transfer-Encoding: quoted-printable
-Content-Type: text/plain
-
-A fax has arrived from remote ID ''.=0D=0A-----------------------=
--------------------------------------=0D=0ATime: 3/9/2006 3:50:52=
- PM=0D=0AReceived from remote ID: =0D=0AInbound user ID XXXXXXXXXX, r=
-outing code XXXXXXXXX=0D=0AResult: (0/352;0/0) Successful Send=0D=0AP=
-age record: 1 - 1=0D=0AElapsed time: 00:58 on channel 11=0D=0A
diff --git a/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type b/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type deleted file mode 100644 index a8ff7ed4cb..0000000000 --- a/actionmailer/test/fixtures/raw_email_with_invalid_characters_in_content_type +++ /dev/null @@ -1,104 +0,0 @@ -Return-Path: <mikel.other@baci> -Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000 -Date: Sun, 21 Oct 2007 19:38:13 +1000 -From: Mikel Lindsaar <mikel.other@baci> -Reply-To: Mikel Lindsaar <mikel.other@baci> -To: mikel.lindsaar@baci -Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a> -Subject: Testing outlook -Mime-Version: 1.0 -Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850 -Delivered-To: mikel.lindsaar@baci -X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138 -X-Msmail-Priority: Normal - -This is a multi-part message in MIME format. - - -------=_NextPart_000_0093_01C81419.EB75E850 -Content-Type: text/plain; charset=iso-8859-1 -Content-Transfer-Encoding: Quoted-printable - -Hello -This is an outlook test - -So there. - -Me. - -------=_NextPart_000_0093_01C81419.EB75E850 -Content-Type: text/html; charset=iso-8859-1 -Content-Transfer-Encoding: Quoted-printable - -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<HTML><HEAD> -<META http-equiv=3DContent-Type content=3D"text/html; = -charset=3Diso-8859-1"> -<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR> -<STYLE></STYLE> -</HEAD> -<BODY bgColor=3D#ffffff> -<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV> -<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20 -test</STRONG></FONT></DIV> -<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT> </DIV> -<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV> -<DIV><FONT face=3DArial size=3D2></FONT> </DIV> -<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML> - - -------=_NextPart_000_0093_01C81419.EB75E850-- - - -Return-Path: <mikel.other@baci> -Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000 -Date: Sun, 21 Oct 2007 19:38:13 +1000 -From: Mikel Lindsaar <mikel.other@baci> -Reply-To: Mikel Lindsaar <mikel.other@baci> -To: mikel.lindsaar@baci -Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a> -Subject: Testing outlook -Mime-Version: 1.0 -Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850 -Delivered-To: mikel.lindsaar@baci -X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138 -X-Msmail-Priority: Normal - -This is a multi-part message in MIME format. - - -------=_NextPart_000_0093_01C81419.EB75E850 -Content-Type: text/plain; charset=iso-8859-1 -Content-Transfer-Encoding: Quoted-printable - -Hello -This is an outlook test - -So there. - -Me. - -------=_NextPart_000_0093_01C81419.EB75E850 -Content-Type: text/html; charset=iso-8859-1 -Content-Transfer-Encoding: Quoted-printable - -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> -<HTML><HEAD> -<META http-equiv=3DContent-Type content=3D"text/html; = -charset=3Diso-8859-1"> -<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR> -<STYLE></STYLE> -</HEAD> -<BODY bgColor=3D#ffffff> -<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV> -<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20 -test</STRONG></FONT></DIV> -<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT> </DIV> -<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV> -<DIV><FONT face=3DArial size=3D2></FONT> </DIV> -<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML> - - -------=_NextPart_000_0093_01C81419.EB75E850-- - - diff --git a/actionmailer/test/fixtures/raw_email_with_nested_attachment b/actionmailer/test/fixtures/raw_email_with_nested_attachment deleted file mode 100644 index 429c408c5d..0000000000 --- a/actionmailer/test/fixtures/raw_email_with_nested_attachment +++ /dev/null @@ -1,100 +0,0 @@ -From jamis@37signals.com Thu Feb 22 11:20:31 2007 -Mime-Version: 1.0 (Apple Message framework v752.3) -Message-Id: <2CCE0408-10C7-4045-9B16-A1C11C31469B@37signals.com> -Content-Type: multipart/signed; - micalg=sha1; - boundary=Apple-Mail-42-587703407; - protocol="application/pkcs7-signature" -To: Jamis Buck <jamis@jamisbuck.org> -Subject: Testing attachments -From: Jamis Buck <jamis@37signals.com> -Date: Thu, 22 Feb 2007 11:20:31 -0700 - - ---Apple-Mail-42-587703407 -Content-Type: multipart/mixed; - boundary=Apple-Mail-41-587703287 - - ---Apple-Mail-41-587703287 -Content-Transfer-Encoding: 7bit -Content-Type: text/plain; - charset=US-ASCII; - format=flowed - -Here is a test of an attachment via email. - -- Jamis - - ---Apple-Mail-41-587703287 -Content-Transfer-Encoding: base64 -Content-Type: image/png; - x-unix-mode=0644; - name=byo-ror-cover.png -Content-Disposition: inline; - filename=truncated.png - -iVBORw0KGgoAAAANSUhEUgAAAKUAAADXCAYAAAB7wZEQAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz -AAALEgAACxIB0t1+/AAAABd0RVh0Q3JlYXRpb24gVGltZQAxLzI1LzIwMDeD9CJVAAAAGHRFWHRT -b2Z0d2FyZQBBZG9iZSBGaXJld29ya3NPsx9OAAAyBWlUWHRYTUw6Y29tLmFkb2JlLnhtcDw/eHBh -Y2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1l -dGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1j -MDIwIDEuMjU1NzE2LCBUdWUgT2N0IDEwIDIwMDYgMjM6MTY6MzQiPgogICA8cmRmOlJERiB4bWxu -czpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAg -ICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0 -dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFk -b2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVE -YXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpN -b2RpZnlEYXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9y -ZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg -ICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAg -ICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0 -hhojpmnJMfaYFmSkXWg5PGCmHXVj/c9At0hSK2xGdd8F3muk0VFjb4f5Ue0ksQ8qAcq0delaXhdb -DjKNnF+3B3t9kObZYmk7AZgWYqO9anpR3wpM9sQ5XslB9a+kWyTtNb0fOmudzGHfPFBQDKesyycm -DBL7Cw5bXjIEuci+SSOm/LYnXDZu6iuPEj8lYBb+OU8xx1f9m+e5rhJiYKqjo5vHfiZp+VUkW9xc -Ufd6JHNWc47PkQqb9ie3SLEZB/ZqyAssiqURY+G35iOMZUrHbasHnb80QAPv9FHtAbJIyro7bi5b -ai2TEAKen5+LJNWrglZjm3UbZvt7KryA2J5b5J1jZF8kL6GzvG1Zqx54Y1y7J7n20wMOt9frG2sW -uwGP07kNz3732vf6bfvAvLldfS+9fts2euXY37D+R29FGZdlnhzV4TTFmPJduBP2RbNNua4rTqcT -Qt7Xy1KUB0AHSdP5AZQYvHZg7WD1XvYeMO1A9HhZPqMX5KXbMBrn2efxns/ee21674efxz4Tp/fq -2HZ648dgYaC1i3Vq1IbNPq3PvDTPezY9FaRISjvnzWqdgcWN8EJgjnNq+Z7ktOm9l2Nfth28EZi4 -bG/we5JwxM+Tql47/D/X6b38I8/RyxvxPJrX6zvQbo3h9jyJx+C0ALX327QETHl5eYlaYCT5rPTb -+5/rAq26t3lKIxV/p88hq6ptngdgCzoPjJqndiLfc/6y5A14WeDFGNPct4iUsJBV2bYzLEV7m83s -6Rp63VPhHKC/g/LzaU9qexJRr56043JWinqAtfZqsSm1sjoznthl54dtCqv+uL4nIY+oYWuc3+nH -kGfn8b0HQpvOYLQAZUDanbJs3jQhITZEgdarZK+cO6ySlL13rut5nFaN23s7u3Snz6eRPTkCoc2/ -Vp1zHfZVFpZ87FiMVLV1iqyK5rlzfji2GzjfDsodlD+Weo5UD4h6PwKqzQMqID0tq2VjjFVSMpis -ZLRAs7sePZBZAHI+gIanB8I7MD+femAceeUe2Kxa5jS950kZ1p5eNEdeX1+jFmSpZ+1EdWCsDcne -NPNgUHNw3aYpnzv9PGTX0uo94EtN9qq1rOdxe3kc79T8ukeHJJ8Fnxej6qlylbLLsjQLOy6Xy2a1 -kefs/N+nM7+S7IG5/E5Yc7F003pWErLjbH0O5cGadiMptSB/DZ5U5DI9yeg5MFYyMj8lC/Y7/Xjq -OZlWcnpg9aQfXz2HRq+Wn5xOp6gN8tWq8R44e2pfyzLYemEgprst+XXk2Zj2nXlbsG05BprndTMv -C3QRaXczshhVsHnMgfYn80Y2g5JureA6wBasPeP7LkE/jvZMJAaf/g/U2RelHsisvan5FqweIAHg -Pwc7L68GxvVDAAAAAElFTkSuQmCC - ---Apple-Mail-41-587703287-- - ---Apple-Mail-42-587703407 -Content-Transfer-Encoding: base64 -Content-Type: application/pkcs7-signature; - name=smime.p7s -Content-Disposition: attachment; - filename=smime.p7s - -MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGJzCCAuAw -ggJJoAMCAQICEFjnFNYXwDEZRWY5EkfzopUwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCWkEx -JTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5nIChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQ -ZXJzb25hbCBGcmVlbWFpbCBJc3N1aW5nIENBMB4XDTA2MDkxMjE3MDExMloXDTA3MDkxMjE3MDEx -MlowRTEfMB0GA1UEAxMWVGhhd3RlIEZyZWVtYWlsIE1lbWJlcjEiMCAGCSqGSIb3DQEJARYTamFt -aXNAMzdzaWduYWxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO2A9JeOFIFJ -G6z8pTcAldrZ2nMe+Xb1tNrbHgoVzN/QhHXM4qst2Ml93cmFLjMmwG7P9RJeU4oNx+jTqVoBB7NV -Ne1/o56Do0KhfMZ9iUDQdPLbkZMq4EEpFMdm6PyM3muRKwPhj66iAWe/osCb8DowUK2f66vaRx0Z -Y0MQHIIrXE02Ta4IfAhIfPqBLkZ4WgTYBHN9vMdYea1jF0GO4gqGk1wqwb3yxv2QMYMbwJ6SI+k/ -ZjkSR/OilTCBhwYLKoZIhvcNAQkQAgsxeKB2MGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3 -dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h -aWwgSXNzdWluZyBDQQIQWOcU1hfAMRlFZjkSR/OilTANBgkqhkiG9w0BAQEFAASCAQCfwQiC3v6/ -yleRDGv3bJ4nQYQ+c3mz3+mn3Xi6uU35n3piwxZZaWRdmLyiXPvU+QReHpSf3l2qsEZM3sdE0XF9 -eRul/+QTFJcDNXOEAxG1zC2Gpz+6c6RrX4Ou12Pwkp+pNrZWTSY/mZgdqcArupOBcZi7qBjoWcy5 -wb54dfvSSjrjmqLbkH/E8ww/6gGQuU/xXpAUZgUrTmQHrNKeIdSh5oDkOxFaFWvnmb8Z/2ixKqW/ -Ux6WqamyvBtTs/5YBEtnpZOk+uVoscYEUBhU+DVJ2OSvTdXSivMtBdXmGTsG22k+P1NGUHi/A7ev -xPaO0uk4V8xyjNlN4HPuGpkrlXwPAAAAAAAA - ---Apple-Mail-42-587703407-- diff --git a/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject b/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject deleted file mode 100644 index e86108da1e..0000000000 --- a/actionmailer/test/fixtures/raw_email_with_partially_quoted_subject +++ /dev/null @@ -1,14 +0,0 @@ -From jamis@37signals.com Mon May 2 16:07:05 2005 -Mime-Version: 1.0 (Apple Message framework v622) -Content-Transfer-Encoding: base64 -Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com> -Content-Type: text/plain; - charset=EUC-KR; - format=flowed -To: jamis@37signals.com -From: Jamis Buck <jamis@37signals.com> -Subject: Re: Test: =?UTF-8?B?Iua8ouWtlyI=?= mid =?UTF-8?B?Iua8ouWtlyI=?= tail -Date: Mon, 2 May 2005 16:07:05 -0600 - -tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin -wLogSmFtaXPA1LTPtNku diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index a3e93c9c31..ab5eaaa9d5 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'action_view' require 'action_controller' class I18nTestMailer < ActionMailer::Base @@ -14,6 +15,9 @@ class I18nTestMailer < ActionMailer::Base end end +# Emulate AV railtie +ActionController::Base.superclass.send(:include, ActionView::Layouts) + class TestController < ActionController::Base def send_mail I18nTestMailer.mail_with_i18n_subject("test@localhost").deliver diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d848095e01..ecbdfd51ed 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,24 @@ +* Do not break params filtering on `nil` values. + + Fixes #12149. + + *Vasiliy Ermolovich* + +* Separate Action View completely from Action Pack. + + *Łukasz Strzałkowski* + +* Development mode exceptions are rendered in text format in case of XHR request. + + *Kir Shatrov* + +* Fix an issue where :if and :unless controller action procs were being run + before checking for the correct action in the :only and :unless options. + + Fixes #11799 + + *Nicholas Jakobsen* + * Fix an issue where `assert_dom_equal` and `assert_dom_not_equal` were ignoring the passed failure message argument. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index e3aa84ba0f..b4315c1f57 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -20,11 +20,11 @@ Gem::Specification.new do |s| s.requirements << 'none' s.add_dependency 'activesupport', version - s.add_dependency 'actionview', version s.add_dependency 'rack', '~> 1.5.2' s.add_dependency 'rack-test', '~> 0.6.2' - s.add_development_dependency 'activemodel', version - s.add_development_dependency 'tzinfo', '~> 0.3.37' + s.add_development_dependency 'actionview', version + s.add_development_dependency 'activemodel', version + s.add_development_dependency 'tzinfo', '~> 0.3.37' end diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 867a7954e0..fe9802e395 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -10,12 +10,11 @@ module AbstractController autoload :Base autoload :Callbacks autoload :Collector + autoload :DoubleRenderError, "abstract_controller/rendering" autoload :Helpers - autoload :Layouts autoload :Logger autoload :Rendering autoload :Translation autoload :AssetPaths - autoload :ViewPaths autoload :UrlFor end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 21c6191691..d6c941832f 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -38,7 +38,7 @@ module AbstractController def _normalize_callback_option(options, from, to) # :nodoc: if from = options[from] from = Array(from).map {|o| "action_name == '#{o}'"}.join(" || ") - options[to] = Array(options[to]) << from + options[to] = Array(options[to]).unshift(from) end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 3f34add790..6f6079d3c5 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,5 @@ -require "abstract_controller/base" -require "action_view" +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' module AbstractController class DoubleRenderError < Error @@ -10,91 +10,22 @@ module AbstractController end end - # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, - # it will trigger the lookup_context and consequently expire the cache. - class I18nProxy < ::I18n::Config #:nodoc: - attr_reader :original_config, :lookup_context - - def initialize(original_config, lookup_context) - original_config = original_config.original_config if original_config.respond_to?(:original_config) - @original_config, @lookup_context = original_config, lookup_context - end - - def locale - @original_config.locale - end - - def locale=(value) - @lookup_context.locale = value - end - end - module Rendering extend ActiveSupport::Concern - include AbstractController::ViewPaths included do class_attribute :protected_instance_variables self.protected_instance_variables = [] end - # Overwrite process to setup I18n proxy. - def process(*) #:nodoc: - old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) - super - ensure - I18n.config = old_config - end - - module ClassMethods - def view_context_class - @view_context_class ||= begin - routes = respond_to?(:_routes) && _routes - helpers = respond_to?(:_helpers) && _helpers - - Class.new(ActionView::Base) do - if routes - include routes.url_helpers - include routes.mounted_helpers - end - - if helpers - include helpers - end - end - end - end - end - - attr_internal_writer :view_context_class - - def view_context_class - @_view_context_class ||= self.class.view_context_class - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.new[lookup_context, assigns, controller] - # Create a new ActionView instance for a controller - # View#render[options] - # Returns String with the rendered template - # - # Override this method in a module to change the default behavior. - def view_context - view_context_class.new(view_renderer, view_assigns, self) - end - - # Returns an object that is able to render templates. - def view_renderer - @_view_renderer ||= ActionView::Renderer.new(lookup_context) - end - # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. + # :api: public def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) + _process_format(rendered_format) + self.response_body end # Raw rendering of a template to a string. @@ -113,24 +44,21 @@ module AbstractController render_to_body(options) end - # Raw rendering of a template. - # :api: plugin + # Performs the actual template rendering. + # :api: public def render_to_body(options = {}) - _process_options(options) - _render_template(options) end - # Find and renders a template based on the options given. - # :api: private - def _render_template(options) #:nodoc: - lookup_context.rendered_format = nil if options[:formats] - view_renderer.render(view_context, options) + # Return Content-Type of rendered content + # :api: public + def rendered_format + Mime::TEXT end - DEFAULT_PROTECTED_INSTANCE_VARIABLES = [ - :@_action_name, :@_response_body, :@_formats, :@_prefixes, :@_config, - :@_view_context_class, :@_view_renderer, :@_lookup_context - ] + DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w( + @_action_name @_response_body @_formats @_prefixes @_config + @_view_context_class @_view_renderer @_lookup_context + ) # This method should return a hash with assigns. # You can overwrite this configuration per controller. @@ -144,53 +72,40 @@ module AbstractController hash end - private - - # Normalize args and options. - # :api: private - def _normalize_render(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - options - end - # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". # :api: plugin def _normalize_args(action=nil, options={}) - case action - when NilClass - when Hash - options = action - when String, Symbol - action = action.to_s - key = action.include?(?/) ? :file : :action - options[key] = action + if action.is_a? Hash + action else - options[:partial] = action + options end - - options end # Normalize options. # :api: plugin def _normalize_options(options) - if options[:partial] == true - options[:partial] = action_name - end - - if (options.keys & [:partial, :file, :template]).empty? - options[:prefixes] ||= _prefixes - end - - options[:template] ||= (options[:action] || action_name).to_s options end # Process extra options. # :api: plugin def _process_options(options) + options + end + + # Process the rendered format. + # :api: private + def _process_format(format) + end + + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) + options end end end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index cc03da4904..417d2efec2 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -46,18 +46,9 @@ module ActionController def self.eager_load! super ActionController::Caching.eager_load! - HTML.eager_load! end end -# All of these simply register additional autoloads -require 'action_view' -require 'action_view/vendor/html-scanner' - -ActiveSupport.on_load(:action_view) do - ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) -end - # Common Active Support usage in Action Controller require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/load_error' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 1faecc850b..4b2c00c86a 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -2,6 +2,20 @@ require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController + # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>. + # Modules needes to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included, + # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that + # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly + # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of + # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt> + # beetween them to perserve the required order, we can simply do this by: + # + # ActionController::Base.superclass.send(:include, ActionView::Rendering) + # + metal = Class.new(Metal) do + include AbstractController::Rendering + end + # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed # on request and then either it renders a template or redirects to another action. An action is defined as a public method # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. @@ -59,7 +73,7 @@ module ActionController # <input type="text" name="post[address]" value="hyacintvej"> # # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>. - # If the address input had been named <tt>post[address][street]</tt>, the params would have included + # If the address input had been named "post[address][street]", the params would have included # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting. # # == Sessions @@ -160,7 +174,7 @@ module ActionController # render action: "overthere" # won't be called if monkeys is nil # end # - class Base < Metal + class Base < metal abstract! # We document the request and response methods here because albeit they are @@ -200,7 +214,6 @@ module ActionController end MODULES = [ - AbstractController::Layouts, AbstractController::Translation, AbstractController::AssetPaths, diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index b8afce42c9..a2cb6d1e66 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -48,7 +48,7 @@ module ActionController # You can pass any of the following options to affect the redirect status and response # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently) # * <tt>flash</tt> - Set a flash message when redirecting - # * <tt>alert</tt> - Set a alert message when redirecting + # * <tt>alert</tt> - Set an alert message when redirecting # * <tt>notice</tt> - Set a notice message when redirecting # # ==== Action Options diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 8092fd639f..0dd788645b 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -1,5 +1,6 @@ require 'action_dispatch/http/response' require 'delegate' +require 'active_support/json' module ActionController # Mix this module in to your controller, and all actions in that controller @@ -32,6 +33,79 @@ module ActionController # the main thread. Make sure your actions are thread safe, and this shouldn't # be a problem (don't share state across threads, etc). module Live + # This class provides the ability to write an SSE (Server Sent Event) + # to an IO stream. The class is initialized with a stream and can be used + # to either write a JSON string or an object which can be converted to JSON. + # + # Writing an object will convert it into standard SSE format with whatever + # options you have configured. You may choose to set the following options: + # + # 1) Event. If specified, an event with this name will be dispatched on + # the browser. + # 2) Retry. The reconnection time in milliseconds used when attempting + # to send the event. + # 3) Id. If the connection dies while sending an SSE to the browser, then + # the server will receive a +Last-Event-ID+ header with value equal to +id+. + # + # After setting an option in the constructor of the SSE object, all future + # SSEs sent accross the stream will use those options unless overridden. + # + # Example Usage: + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def index + # response.headers['Content-Type'] = 'text/event-stream' + # sse = SSE.new(response.stream, retry: 300, event: "event-name") + # sse.write({ name: 'John'}) + # sse.write({ name: 'John'}, id: 10) + # sse.write({ name: 'John'}, id: 10, event: "other-event") + # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) + # ensure + # sse.close + # end + # end + # + # Note: SSEs are not currently supported by IE. However, they are supported + # by Chrome, Firefox, Opera, and Safari. + class SSE + + WHITELISTED_OPTIONS = %w( retry event id ) + + def initialize(stream, options = {}) + @stream = stream + @options = options + end + + def close + @stream.close + end + + def write(object, options = {}) + case object + when String + perform_write(object, options) + else + perform_write(ActiveSupport::JSON.encode(object), options) + end + end + + private + + def perform_write(json, options) + current_options = @options.merge(options).stringify_keys + + WHITELISTED_OPTIONS.each do |option_name| + if (option_value = current_options[option_name]) + @stream.write "#{option_name}: #{option_value}\n" + end + end + + @stream.write "data: #{json}\n\n" + end + end + class Buffer < ActionDispatch::Response::Buffer #:nodoc: def initialize(response) @error_callback = nil diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 834d44f045..66dabd821f 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -364,9 +364,7 @@ module ActionController #:nodoc: format = collector.negotiate_format(request) if format - self.content_type ||= format.to_s - lookup_context.formats = [format.to_sym] - lookup_context.rendered_format = lookup_context.formats.first + _process_format(format) collector else raise ActionController::UnknownFormat diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index 5272dc6cdb..62a3844b04 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -6,6 +6,12 @@ module ActionController Renderers.add(key, &block) end + class MissingRenderer < LoadError + def initialize(format) + super "No renderer defined for format: #{format}" + end + end + module Renderers extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index bea6b88f91..90f0ef0b1c 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -2,8 +2,6 @@ module ActionController module Rendering extend ActiveSupport::Concern - include AbstractController::Rendering - # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact @@ -12,29 +10,37 @@ module ActionController # Check for double render errors and set the content_type after rendering. def render(*args) #:nodoc: - raise ::AbstractController::DoubleRenderError if response_body + raise ::AbstractController::DoubleRenderError if self.response_body super - self.content_type ||= Mime[lookup_context.rendered_format].to_s - response_body end # Overwrite render_to_string because body can now be set to a rack body. def render_to_string(*) - if self.response_body = super + result = super + if result.respond_to?(:each) string = "" - response_body.each { |r| string << r } + result.each { |r| string << r } string + else + result end - ensure - self.response_body = nil end - def render_to_body(*) - super || " " + def render_to_body(options = {}) + super || if options[:text].present? + options[:text] + else + " " + end end private + def _process_format(format) + super + self.content_type ||= format.to_s + end + # Normalize arguments by catching blocks and setting them on :update. def _normalize_args(action=nil, options={}, &blk) #:nodoc: options = super diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index fd5b661209..66ff34a794 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -202,6 +202,7 @@ module ActionController #:nodoc: # This is the common behavior for formats associated with APIs, such as :xml and :json. def api_behavior(error) raise error unless resourceful? + raise MissingRenderer.new(format) unless has_renderer? if get? display resource @@ -269,6 +270,11 @@ module ActionController #:nodoc: resource.respond_to?(:errors) && !resource.errors.empty? end + # Check whether the neceessary Renderer is available + def has_renderer? + Renderers::RENDERERS.include?(format) + end + # By default, render the <code>:edit</code> action for HTML requests with errors, unless # the verb was POST. # diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 73e9b5660d..62d5931b45 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -193,31 +193,29 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include AbstractController::Rendering - protected - # Set proper cache control and transfer encoding when streaming - def _process_options(options) #:nodoc: - super - if options[:stream] - if env["HTTP_VERSION"] == "HTTP/1.0" - options.delete(:stream) - else - headers["Cache-Control"] ||= "no-cache" - headers["Transfer-Encoding"] = "chunked" - headers.delete("Content-Length") + # Set proper cache control and transfer encoding when streaming + def _process_options(options) #:nodoc: + super + if options[:stream] + if env["HTTP_VERSION"] == "HTTP/1.0" + options.delete(:stream) + else + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") + end end end - end - # Call render_body if we are streaming instead of usual +render+. - def _render_template(options) #:nodoc: - if options.delete(:stream) - Rack::Chunked::Body.new view_renderer.render_body(view_context, options) - else - super + # Call render_body if we are streaming instead of usual +render+. + def _render_template(options) #:nodoc: + if options.delete(:stream) + Rack::Chunked::Body.new view_renderer.render_body(view_context, options) + else + super + end end - end end end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index b279ef81a9..b495ab3f0f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -298,7 +298,7 @@ module ActionController # params.slice(:d) # => {} def slice(*keys) self.class.new(super).tap do |new_instance| - new_instance.instance_variable_set :@permitted, @permitted + new_instance.permitted = @permitted end end @@ -312,10 +312,15 @@ module ActionController # copy_params.permitted? # => true def dup super.tap do |duplicate| - duplicate.instance_variable_set :@permitted, @permitted + duplicate.permitted = @permitted end end + protected + def permitted=(new_permitted) + @permitted = new_permitted + end + private def convert_hashes_to_parameters(key, value) if value.is_a?(Parameters) || !value.is_a?(Hash) @@ -416,7 +421,7 @@ module ActionController # Slicing filters out non-declared keys. slice(*filter.keys).each do |key, value| - return unless value + next unless value if filter[key] == EMPTY_ARRAY # Declaration { comment_ids: [] }. diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 5379547c57..0833e65d23 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,7 +1,6 @@ require "rails" require "action_controller" require "action_dispatch/railtie" -require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/helpers" diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 8e992070f1..dcb299ed03 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -57,22 +57,25 @@ module ActionDispatch # you'll get a weird error down the road, but our form handling # should really prevent that from happening def normalize_encode_params(params) - if params.is_a?(String) - return params.force_encoding(Encoding::UTF_8).encode! - elsif !params.is_a?(Hash) - return params - end - - new_hash = {} - params.each do |key, val| - new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key - new_hash[new_key] = if val.is_a?(Array) - val.map! { |el| normalize_encode_params(el) } + case params + when String + params.force_encoding(Encoding::UTF_8).encode! + when Hash + if params.has_key?(:tempfile) + UploadedFile.new(params) else - normalize_encode_params(val) + params.each_with_object({}) do |(key, val), new_hash| + new_key = key.is_a?(String) ? key.dup.force_encoding(Encoding::UTF_8).encode! : key + new_hash[new_key] = if val.is_a?(Array) + val.map! { |el| normalize_encode_params(el) } + else + normalize_encode_params(val) + end + end.with_indifferent_access end + else + params end - new_hash.with_indifferent_access end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index b4cf8ad2f7..aba8f66118 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -18,7 +18,6 @@ module ActionDispatch include ActionDispatch::Http::MimeNegotiation include ActionDispatch::Http::Parameters include ActionDispatch::Http::FilterParameters - include ActionDispatch::Http::Upload include ActionDispatch::Http::URL autoload :Session, 'action_dispatch/request/session' diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index d3696cbb8a..5247e61a23 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -41,7 +41,7 @@ module ActionDispatch # :nodoc: # Get and set headers for this response. attr_accessor :header - + alias_method :headers=, :header= alias_method :headers, :header @@ -181,9 +181,9 @@ module ActionDispatch # :nodoc: end alias_method :status_message, :message - def respond_to?(method) + def respond_to?(method, include_private = false) if method.to_s == 'to_path' - stream.respond_to?(:to_path) + stream.respond_to?(method) else super end diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index b57c84dec8..a8d2dc3950 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -73,18 +73,5 @@ module ActionDispatch filename.force_encoding(Encoding::UTF_8).encode! if filename end end - - module Upload # :nodoc: - # Replace file upload hash with UploadedFile objects - # when normalize and encode parameters. - def normalize_encode_params(value) - if Hash === value && value.has_key?(:tempfile) - UploadedFile.new(value) - else - super - end - end - private :normalize_encode_params - end end end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 64230ff1ae..0ca1a87645 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -34,27 +34,35 @@ module ActionDispatch log_error(env, wrapper) if env['action_dispatch.show_detailed_exceptions'] + request = Request.new(env) template = ActionView::Base.new([RESCUES_TEMPLATE_PATH], - :request => Request.new(env), - :exception => wrapper.exception, - :application_trace => wrapper.application_trace, - :framework_trace => wrapper.framework_trace, - :full_trace => wrapper.full_trace, - :routes_inspector => routes_inspector(exception), - :source_extract => wrapper.source_extract, - :line_number => wrapper.line_number, - :file => wrapper.file + request: request, + exception: wrapper.exception, + application_trace: wrapper.application_trace, + framework_trace: wrapper.framework_trace, + full_trace: wrapper.full_trace, + routes_inspector: routes_inspector(exception), + source_extract: wrapper.source_extract, + line_number: wrapper.line_number, + file: wrapper.file ) file = "rescues/#{wrapper.rescue_template}" - body = template.render(:template => file, :layout => 'rescues/layout') - render(wrapper.status_code, body) + + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: 'rescues/layout') + format = "text/html" + end + render(wrapper.status_code, body, format) else raise exception end end - def render(status, body) - [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] + def render(status, body, format) + [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end def log_error(env, wrapper) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb index db219c8fa9..db219c8fa9 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb new file mode 100644 index 0000000000..396768ecee --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb @@ -0,0 +1,23 @@ +<% + clean_params = @request.filtered_parameters.clone + clean_params.delete("action") + clean_params.delete("controller") + + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end unless self.class.method_defined?(:debug_hash) +%> + +Request parameters +<%= request_dump %> + +Session dump +<%= debug_hash @request.session %> + +Env dump +<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %> + +Response headers +<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb index b181909bff..b181909bff 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb new file mode 100644 index 0000000000..d4af5c9b06 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb @@ -0,0 +1,15 @@ +<% + traces = { "Application Trace" => @application_trace, + "Framework Trace" => @framework_trace, + "Full Trace" => @full_trace } +%> + +Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %> + +<% traces.each do |name, trace| %> +<% if trace.any? %> +<%= name %> +<%= trace.join("\n") %> + +<% end %> +<% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index 57a2940802..f154021ae6 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -8,7 +8,7 @@ </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> <%= render template: "rescues/_source" %> <%= render template: "rescues/_trace" %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb index ca14215946..5c016e544e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb @@ -3,5 +3,5 @@ </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb new file mode 100644 index 0000000000..ae62d9eb02 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb @@ -0,0 +1,3 @@ +Template is missing + +<%= @exception.message %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb index cd3daff065..7e9cedb95e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb @@ -2,7 +2,7 @@ <h1>Routing Error</h1> </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> <% unless @exception.failures.empty? %> <p> <h2>Failure reasons:</h2> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb new file mode 100644 index 0000000000..f6e4dac1f3 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb @@ -0,0 +1,11 @@ +Routing Error + +<%= @exception.message %> +<% unless @exception.failures.empty? %> +Failure reasons: +<% @exception.failures.each do |route, reason| %> + - <%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %> +<% end %> +<% end %> + +<%= render template: "rescues/_trace", format: :text %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb index 31f46ee340..027a0f5b3e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb @@ -10,7 +10,7 @@ <p> Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised: </p> - <pre><code><%= @exception.message %></code></pre> + <pre><code><%= h @exception.message %></code></pre> <div class="source"> <div class="info"> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb new file mode 100644 index 0000000000..5da21d9784 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb @@ -0,0 +1,8 @@ +<% @source_extract = @exception.source_extract(0, :html) %> +<%= @exception.original_exception.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> + +Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: +<%= @exception.message %> +<%= @exception.sub_template_message %> +<%= render template: "rescues/_trace", format: :text %> +<%= render template: "rescues/_request_and_response", format: :text %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb index c1fbf67eed..259fb2bb3b 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb @@ -2,5 +2,5 @@ <h1>Unknown action</h1> </header> <div id="container"> - <h2><%= @exception.message %></h2> + <h2><%= h @exception.message %></h2> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb new file mode 100644 index 0000000000..83973addcb --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb @@ -0,0 +1,3 @@ +Unknown action + +<%= @exception.message %> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 288ce3e867..db9c993590 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -362,8 +362,9 @@ module ActionDispatch # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show) # - # Because request various HTTP verbs with a single action has security - # implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers] + # Because requesting various HTTP verbs with a single action has security + # implications, you must either specify the actions in + # the via options or use one of the HtttpHelpers[rdoc-ref:HttpHelpers] # instead +match+ # # === Options @@ -432,10 +433,10 @@ module ActionDispatch # # match 'json_only', constraints: { format: 'json' } # - # class Blacklist + # class Whitelist # def matches?(request) request.remote_ip == '1.2.3.4' end # end - # match 'path', to: 'c#a', constraints: Blacklist.new + # match 'path', to: 'c#a', constraints: Whitelist.new # # See <tt>Scoping#constraints</tt> for more examples with its scope # equivalent. @@ -1066,18 +1067,18 @@ module ActionDispatch # a singular resource to map /profile (rather than /profile/:id) to # the show action: # - # resource :geocoder + # resource :profile # # creates six different routes in your application, all mapping to - # the +GeoCoders+ controller (note that the controller is named after + # the +Profiles+ controller (note that the controller is named after # the plural): # - # GET /geocoder/new - # POST /geocoder - # GET /geocoder - # GET /geocoder/edit - # PATCH/PUT /geocoder - # DELETE /geocoder + # GET /profile/new + # POST /profile + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile # # === Options # Takes same options as +resources+. diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 8213997f4e..a0d90f7eee 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -64,28 +64,6 @@ module RackTestUtils extend self end -module RenderERBUtils - def view - @view ||= begin - path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH) - view_paths = ActionView::PathSet.new([path]) - ActionView::Base.new(view_paths) - end - end - - def render_erb(string) - @virtual_path = nil - - template = ActionView::Template.new( - string.strip, - "test template", - ActionView::Template::Handlers::ERB, - {}) - - template.render(self, {}).strip - end -end - SharedTestRoutes = ActionDispatch::Routing::RouteSet.new module ActionDispatch @@ -268,6 +246,8 @@ class Rack::TestCase < ActionDispatch::IntegrationTest end end +ActionController::Base.superclass.send(:include, ActionView::Layouts) + module ActionController class Base include ActionController::Testing @@ -290,15 +270,8 @@ module ActionController end end -class ::ApplicationController < ActionController::Base -end -module ActionView - class TestCase - # Must repeat the setup because AV::TestCase is a duplication - # of AC::TestCase - include ActionDispatch::SharedRoutes - end +class ::ApplicationController < ActionController::Base end class Workshop diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 3d667f0a2f..114bbf3c22 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -8,6 +8,9 @@ require 'abstract_unit' require 'controller/fake_controllers' require 'action_mailer' +require 'action_view' + +ActionMailer::Base.send(:include, ActionView::Layouts) ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH class AssertSelectTest < ActionController::TestCase diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index a67dff5436..57b45b8f7b 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -20,7 +20,7 @@ class FragmentCachingMetalTest < ActionController::TestCase @controller = FragmentCachingMetalTestController.new @controller.perform_caching = true @controller.cache_store = @store - @params = { controller: 'posts', action: 'index'} + @params = { controller: 'posts', action: 'index' } @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @controller.params = @params @@ -40,7 +40,7 @@ class CachingController < ActionController::Base end class FragmentCachingTestController < CachingController - def some_action; end; + def some_action; end end class FragmentCachingTest < ActionController::TestCase diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 4c82625e8e..3b5d7ef446 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -208,6 +208,10 @@ class FilterTest < ActionController::TestCase before_filter(ConditionalClassFilter, :ensure_login, Proc.new {|c| c.instance_variable_set(:"@ran_proc_filter1", true)}, :except => :show_without_filter) { |c| c.instance_variable_set(:"@ran_proc_filter2", true)} end + class OnlyConditionalOptionsFilter < ConditionalFilterController + before_filter :ensure_login, :only => :index, :if => Proc.new {|c| c.instance_variable_set(:"@ran_conditional_index_proc", true) } + end + class ConditionalOptionsFilter < ConditionalFilterController before_filter :ensure_login, :if => Proc.new { |c| true } before_filter :clean_up_tmp, :if => Proc.new { |c| false } @@ -649,6 +653,11 @@ class FilterTest < ActionController::TestCase assert !assigns["ran_class_filter"] end + def test_running_only_condition_and_conditional_options + test_process(OnlyConditionalOptionsFilter, "show") + assert_not assigns["ran_conditional_index_proc"] + end + def test_running_before_and_after_condition_filters test_process(BeforeAndAfterConditionController) assert_equal %w( ensure_login clean_up_tmp), assigns["ran_filter"] diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index e727b27db0..0a431270b5 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -2,6 +2,94 @@ require 'abstract_unit' require 'active_support/concurrency/latch' module ActionController + class SSETest < ActionController::TestCase + class SSETestController < ActionController::Base + include ActionController::Live + + def basic_sse + response.headers['Content-Type'] = 'text/event-stream' + sse = SSE.new(response.stream) + sse.write("{\"name\":\"John\"}") + sse.write({ name: "Ryan" }) + ensure + sse.close + end + + def sse_with_event + sse = SSE.new(response.stream, event: "send-name") + sse.write("{\"name\":\"John\"}") + sse.write({ name: "Ryan" }) + ensure + sse.close + end + + def sse_with_retry + sse = SSE.new(response.stream, retry: 1000) + sse.write("{\"name\":\"John\"}") + sse.write({ name: "Ryan" }, retry: 1500) + ensure + sse.close + end + + def sse_with_id + sse = SSE.new(response.stream) + sse.write("{\"name\":\"John\"}", id: 1) + sse.write({ name: "Ryan" }, id: 2) + ensure + sse.close + end + end + + tests SSETestController + + def wait_for_response_stream_close + while !response.stream.closed? + sleep 0.01 + end + end + + def test_basic_sse + get :basic_sse + + wait_for_response_stream_close + assert_match(/data: {\"name\":\"John\"}/, response.body) + assert_match(/data: {\"name\":\"Ryan\"}/, response.body) + end + + def test_sse_with_event_name + get :sse_with_event + + wait_for_response_stream_close + assert_match(/data: {\"name\":\"John\"}/, response.body) + assert_match(/data: {\"name\":\"Ryan\"}/, response.body) + assert_match(/event: send-name/, response.body) + end + + def test_sse_with_retry + get :sse_with_retry + + wait_for_response_stream_close + first_response, second_response = response.body.split("\n\n") + assert_match(/data: {\"name\":\"John\"}/, first_response) + assert_match(/retry: 1000/, first_response) + + assert_match(/data: {\"name\":\"Ryan\"}/, second_response) + assert_match(/retry: 1500/, second_response) + end + + def test_sse_with_id + get :sse_with_id + + wait_for_response_stream_close + first_response, second_response = response.body.split("\n\n") + assert_match(/data: {\"name\":\"John\"}/, first_response) + assert_match(/id: 1/, first_response) + + assert_match(/data: {\"name\":\"Ryan\"}/, second_response) + assert_match(/id: 2/, second_response) + end + end + class LiveStreamTest < ActionController::TestCase class TestController < ActionController::Base include ActionController::Live diff --git a/actionpack/test/controller/mime/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb new file mode 100644 index 0000000000..c03c7edeb8 --- /dev/null +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -0,0 +1,94 @@ +require 'abstract_unit' + +class StarStarMimeController < ActionController::Base + layout nil + + def index + render + end +end + +class StarStarMimeControllerTest < ActionController::TestCase + tests StarStarMimeController + + def test_javascript_with_format + @request.accept = "text/javascript" + get :index, :format => 'js' + assert_match "function addition(a,b){ return a+b; }", @response.body + end + + def test_javascript_with_no_format + @request.accept = "text/javascript" + get :index + assert_match "function addition(a,b){ return a+b; }", @response.body + end + + def test_javascript_with_no_format_only_star_star + @request.accept = "*/*" + get :index + assert_match "function addition(a,b){ return a+b; }", @response.body + end +end + +class AbstractPostController < ActionController::Base + self.view_paths = File.dirname(__FILE__) + "/../../fixtures/post_test/" +end + +# For testing layouts which are set automatically +class PostController < AbstractPostController + around_action :with_iphone + + def index + respond_to(:html, :iphone, :js) + end + +protected + + def with_iphone + request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" + yield + end +end + +class SuperPostController < PostController +end + +class MimeControllerLayoutsTest < ActionController::TestCase + tests PostController + + def setup + super + @request.host = "www.example.com" + Mime::Type.register_alias("text/html", :iphone) + end + + def teardown + super + Mime::Type.unregister(:iphone) + end + + def test_missing_layout_renders_properly + get :index + assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body + + @request.accept = "text/iphone" + get :index + assert_equal 'Hello iPhone', @response.body + end + + def test_format_with_inherited_layouts + @controller = SuperPostController.new + + get :index + assert_equal '<html><div id="html">Super Firefox</div></html>', @response.body + + @request.accept = "text/iphone" + get :index + assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body + end + + def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout + get :index, :format => :js + assert_equal "Hello Firefox", @response.body + end +end diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb new file mode 100644 index 0000000000..774dabe105 --- /dev/null +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -0,0 +1,493 @@ +require 'abstract_unit' + +class RespondToController < ActionController::Base + layout :set_layout + + def html_xml_or_rss + respond_to do |type| + type.html { render :text => "HTML" } + type.xml { render :text => "XML" } + type.rss { render :text => "RSS" } + type.all { render :text => "Nothing" } + end + end + + def js_or_html + respond_to do |type| + type.html { render :text => "HTML" } + type.js { render :text => "JS" } + type.all { render :text => "Nothing" } + end + end + + def json_or_yaml + respond_to do |type| + type.json { render :text => "JSON" } + type.yaml { render :text => "YAML" } + end + end + + def html_or_xml + respond_to do |type| + type.html { render :text => "HTML" } + type.xml { render :text => "XML" } + type.all { render :text => "Nothing" } + end + end + + def json_xml_or_html + respond_to do |type| + type.json { render :text => 'JSON' } + type.xml { render :xml => 'XML' } + type.html { render :text => 'HTML' } + end + end + + + def forced_xml + request.format = :xml + + respond_to do |type| + type.html { render :text => "HTML" } + type.xml { render :text => "XML" } + end + end + + def just_xml + respond_to do |type| + type.xml { render :text => "XML" } + end + end + + def using_defaults + respond_to do |type| + type.html + type.xml + end + end + + def using_defaults_with_type_list + respond_to(:html, :xml) + end + + def using_defaults_with_all + respond_to do |type| + type.html + type.all{ render text: "ALL" } + end + end + + def made_for_content_type + respond_to do |type| + type.rss { render :text => "RSS" } + type.atom { render :text => "ATOM" } + type.all { render :text => "Nothing" } + end + end + + def custom_type_handling + respond_to do |type| + type.html { render :text => "HTML" } + type.custom("application/crazy-xml") { render :text => "Crazy XML" } + type.all { render :text => "Nothing" } + end + end + + + def custom_constant_handling + respond_to do |type| + type.html { render :text => "HTML" } + type.mobile { render :text => "Mobile" } + end + end + + def custom_constant_handling_without_block + respond_to do |type| + type.html { render :text => "HTML" } + type.mobile + end + end + + def handle_any + respond_to do |type| + type.html { render :text => "HTML" } + type.any(:js, :xml) { render :text => "Either JS or XML" } + end + end + + def handle_any_any + respond_to do |type| + type.html { render :text => 'HTML' } + type.any { render :text => 'Whatever you ask for, I got it' } + end + end + + def all_types_with_layout + respond_to do |type| + type.html + end + end + + def iphone_with_html_response_type + request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone" + + respond_to do |type| + type.html { @type = "Firefox" } + type.iphone { @type = "iPhone" } + end + end + + def iphone_with_html_response_type_without_layout + request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" + + respond_to do |type| + type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" } + type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" } + end + end + + protected + def set_layout + case action_name + when "all_types_with_layout", "iphone_with_html_response_type" + "respond_to/layouts/standard" + when "iphone_with_html_response_type_without_layout" + "respond_to/layouts/missing" + end + end +end + +class RespondToControllerTest < ActionController::TestCase + tests RespondToController + + def setup + super + @request.host = "www.example.com" + Mime::Type.register_alias("text/html", :iphone) + Mime::Type.register("text/x-mobile", :mobile) + end + + def teardown + super + Mime::Type.unregister(:iphone) + Mime::Type.unregister(:mobile) + end + + def test_html + @request.accept = "text/html" + get :js_or_html + assert_equal 'HTML', @response.body + + get :html_or_xml + assert_equal 'HTML', @response.body + + assert_raises(ActionController::UnknownFormat) do + get :just_xml + end + end + + def test_all + @request.accept = "*/*" + get :js_or_html + assert_equal 'HTML', @response.body # js is not part of all + + get :html_or_xml + assert_equal 'HTML', @response.body + + get :just_xml + assert_equal 'XML', @response.body + end + + def test_xml + @request.accept = "application/xml" + get :html_xml_or_rss + assert_equal 'XML', @response.body + end + + def test_js_or_html + @request.accept = "text/javascript, text/html" + xhr :get, :js_or_html + assert_equal 'JS', @response.body + + @request.accept = "text/javascript, text/html" + xhr :get, :html_or_xml + assert_equal 'HTML', @response.body + + @request.accept = "text/javascript, text/html" + + assert_raises(ActionController::UnknownFormat) do + xhr :get, :just_xml + end + end + + def test_json_or_yaml_with_leading_star_star + @request.accept = "*/*, application/json" + get :json_xml_or_html + assert_equal 'HTML', @response.body + + @request.accept = "*/* , application/json" + get :json_xml_or_html + assert_equal 'HTML', @response.body + end + + def test_json_or_yaml + xhr :get, :json_or_yaml + assert_equal 'JSON', @response.body + + get :json_or_yaml, :format => 'json' + assert_equal 'JSON', @response.body + + get :json_or_yaml, :format => 'yaml' + assert_equal 'YAML', @response.body + + { 'YAML' => %w(text/yaml), + 'JSON' => %w(application/json text/x-json) + }.each do |body, content_types| + content_types.each do |content_type| + @request.accept = content_type + get :json_or_yaml + assert_equal body, @response.body + end + end + end + + def test_js_or_anything + @request.accept = "text/javascript, */*" + xhr :get, :js_or_html + assert_equal 'JS', @response.body + + xhr :get, :html_or_xml + assert_equal 'HTML', @response.body + + xhr :get, :just_xml + assert_equal 'XML', @response.body + end + + def test_using_defaults + @request.accept = "*/*" + get :using_defaults + assert_equal "text/html", @response.content_type + assert_equal 'Hello world!', @response.body + + @request.accept = "application/xml" + get :using_defaults + assert_equal "application/xml", @response.content_type + assert_equal "<p>Hello world!</p>\n", @response.body + end + + def test_using_defaults_with_all + @request.accept = "*/*" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "text/html" + get :using_defaults_with_all + assert_equal "HTML!", @response.body.strip + + @request.accept = "application/json" + get :using_defaults_with_all + assert_equal "ALL", @response.body + end + + def test_using_defaults_with_type_list + @request.accept = "*/*" + get :using_defaults_with_type_list + assert_equal "text/html", @response.content_type + assert_equal 'Hello world!', @response.body + + @request.accept = "application/xml" + get :using_defaults_with_type_list + assert_equal "application/xml", @response.content_type + assert_equal "<p>Hello world!</p>\n", @response.body + end + + def test_with_atom_content_type + @request.accept = "" + @request.env["CONTENT_TYPE"] = "application/atom+xml" + xhr :get, :made_for_content_type + assert_equal "ATOM", @response.body + end + + def test_with_rss_content_type + @request.accept = "" + @request.env["CONTENT_TYPE"] = "application/rss+xml" + xhr :get, :made_for_content_type + assert_equal "RSS", @response.body + end + + def test_synonyms + @request.accept = "application/javascript" + get :js_or_html + assert_equal 'JS', @response.body + + @request.accept = "application/x-xml" + get :html_xml_or_rss + assert_equal "XML", @response.body + end + + def test_custom_types + @request.accept = "application/crazy-xml" + get :custom_type_handling + assert_equal "application/crazy-xml", @response.content_type + assert_equal 'Crazy XML', @response.body + + @request.accept = "text/html" + get :custom_type_handling + assert_equal "text/html", @response.content_type + assert_equal 'HTML', @response.body + end + + def test_xhtml_alias + @request.accept = "application/xhtml+xml,application/xml" + get :html_or_xml + assert_equal 'HTML', @response.body + end + + def test_firefox_simulation + @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" + get :html_or_xml + assert_equal 'HTML', @response.body + end + + def test_handle_any + @request.accept = "*/*" + get :handle_any + assert_equal 'HTML', @response.body + + @request.accept = "text/javascript" + get :handle_any + assert_equal 'Either JS or XML', @response.body + + @request.accept = "text/xml" + get :handle_any + assert_equal 'Either JS or XML', @response.body + end + + def test_handle_any_any + @request.accept = "*/*" + get :handle_any_any + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_parameter_format + get :handle_any_any, {:format=>'html'} + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_explicit_html + @request.accept = "text/html" + get :handle_any_any + assert_equal 'HTML', @response.body + end + + def test_handle_any_any_javascript + @request.accept = "text/javascript" + get :handle_any_any + assert_equal 'Whatever you ask for, I got it', @response.body + end + + def test_handle_any_any_xml + @request.accept = "text/xml" + get :handle_any_any + assert_equal 'Whatever you ask for, I got it', @response.body + end + + def test_browser_check_with_any_any + @request.accept = "application/json, application/xml" + get :json_xml_or_html + assert_equal 'JSON', @response.body + + @request.accept = "application/json, application/xml, */*" + get :json_xml_or_html + assert_equal 'HTML', @response.body + end + + def test_html_type_with_layout + @request.accept = "text/html" + get :all_types_with_layout + assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body + end + + def test_xhr + xhr :get, :js_or_html + assert_equal 'JS', @response.body + end + + def test_custom_constant + get :custom_constant_handling, :format => "mobile" + assert_equal "text/x-mobile", @response.content_type + assert_equal "Mobile", @response.body + end + + def test_custom_constant_handling_without_block + get :custom_constant_handling_without_block, :format => "mobile" + assert_equal "text/x-mobile", @response.content_type + assert_equal "Mobile", @response.body + end + + def test_forced_format + get :html_xml_or_rss + assert_equal "HTML", @response.body + + get :html_xml_or_rss, :format => "html" + assert_equal "HTML", @response.body + + get :html_xml_or_rss, :format => "xml" + assert_equal "XML", @response.body + + get :html_xml_or_rss, :format => "rss" + assert_equal "RSS", @response.body + end + + def test_internally_forced_format + get :forced_xml + assert_equal "XML", @response.body + + get :forced_xml, :format => "html" + assert_equal "XML", @response.body + end + + def test_extension_synonyms + get :html_xml_or_rss, :format => "xhtml" + assert_equal "HTML", @response.body + end + + def test_render_action_for_html + @controller.instance_eval do + def render(*args) + @action = args.first[:action] unless args.empty? + @action ||= action_name + + response.body = "#{@action} - #{formats}" + end + end + + get :using_defaults + assert_equal "using_defaults - #{[:html].to_s}", @response.body + + get :using_defaults, :format => "xml" + assert_equal "using_defaults - #{[:xml].to_s}", @response.body + end + + def test_format_with_custom_response_type + get :iphone_with_html_response_type + assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body + + get :iphone_with_html_response_type, :format => "iphone" + assert_equal "text/html", @response.content_type + assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body + end + + def test_format_with_custom_response_type_and_request_headers + @request.accept = "text/iphone" + get :iphone_with_html_response_type + assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body + assert_equal "text/html", @response.content_type + end + + def test_invalid_format + assert_raises(ActionController::UnknownFormat) do + get :using_defaults, :format => "invalidformat" + end + end +end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime/respond_with_test.rb index 2d89969fd3..76af9e3414 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime/respond_with_test.rb @@ -1,529 +1,5 @@ require 'abstract_unit' require 'controller/fake_models' -require 'active_support/core_ext/hash/conversions' - -class StarStarMimeController < ActionController::Base - layout nil - - def index - render - end -end - -class RespondToController < ActionController::Base - layout :set_layout - - def html_xml_or_rss - respond_to do |type| - type.html { render :text => "HTML" } - type.xml { render :text => "XML" } - type.rss { render :text => "RSS" } - type.all { render :text => "Nothing" } - end - end - - def js_or_html - respond_to do |type| - type.html { render :text => "HTML" } - type.js { render :text => "JS" } - type.all { render :text => "Nothing" } - end - end - - def json_or_yaml - respond_to do |type| - type.json { render :text => "JSON" } - type.yaml { render :text => "YAML" } - end - end - - def html_or_xml - respond_to do |type| - type.html { render :text => "HTML" } - type.xml { render :text => "XML" } - type.all { render :text => "Nothing" } - end - end - - def json_xml_or_html - respond_to do |type| - type.json { render :text => 'JSON' } - type.xml { render :xml => 'XML' } - type.html { render :text => 'HTML' } - end - end - - - def forced_xml - request.format = :xml - - respond_to do |type| - type.html { render :text => "HTML" } - type.xml { render :text => "XML" } - end - end - - def just_xml - respond_to do |type| - type.xml { render :text => "XML" } - end - end - - def using_defaults - respond_to do |type| - type.html - type.xml - end - end - - def using_defaults_with_type_list - respond_to(:html, :xml) - end - - def using_defaults_with_all - respond_to do |type| - type.html - type.all{ render text: "ALL" } - end - end - - def made_for_content_type - respond_to do |type| - type.rss { render :text => "RSS" } - type.atom { render :text => "ATOM" } - type.all { render :text => "Nothing" } - end - end - - def custom_type_handling - respond_to do |type| - type.html { render :text => "HTML" } - type.custom("application/crazy-xml") { render :text => "Crazy XML" } - type.all { render :text => "Nothing" } - end - end - - - def custom_constant_handling - respond_to do |type| - type.html { render :text => "HTML" } - type.mobile { render :text => "Mobile" } - end - end - - def custom_constant_handling_without_block - respond_to do |type| - type.html { render :text => "HTML" } - type.mobile - end - end - - def handle_any - respond_to do |type| - type.html { render :text => "HTML" } - type.any(:js, :xml) { render :text => "Either JS or XML" } - end - end - - def handle_any_any - respond_to do |type| - type.html { render :text => 'HTML' } - type.any { render :text => 'Whatever you ask for, I got it' } - end - end - - def all_types_with_layout - respond_to do |type| - type.html - end - end - - def iphone_with_html_response_type - request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone" - - respond_to do |type| - type.html { @type = "Firefox" } - type.iphone { @type = "iPhone" } - end - end - - def iphone_with_html_response_type_without_layout - request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" - - respond_to do |type| - type.html { @type = "Firefox"; render :action => "iphone_with_html_response_type" } - type.iphone { @type = "iPhone" ; render :action => "iphone_with_html_response_type" } - end - end - - protected - def set_layout - case action_name - when "all_types_with_layout", "iphone_with_html_response_type" - "respond_to/layouts/standard" - when "iphone_with_html_response_type_without_layout" - "respond_to/layouts/missing" - end - end -end - -class StarStarMimeControllerTest < ActionController::TestCase - tests StarStarMimeController - - def test_javascript_with_format - @request.accept = "text/javascript" - get :index, :format => 'js' - assert_match "function addition(a,b){ return a+b; }", @response.body - end - - def test_javascript_with_no_format - @request.accept = "text/javascript" - get :index - assert_match "function addition(a,b){ return a+b; }", @response.body - end - - def test_javascript_with_no_format_only_star_star - @request.accept = "*/*" - get :index - assert_match "function addition(a,b){ return a+b; }", @response.body - end - -end - -class RespondToControllerTest < ActionController::TestCase - tests RespondToController - - def setup - super - @request.host = "www.example.com" - Mime::Type.register_alias("text/html", :iphone) - Mime::Type.register("text/x-mobile", :mobile) - end - - def teardown - super - Mime::Type.unregister(:iphone) - Mime::Type.unregister(:mobile) - end - - def test_html - @request.accept = "text/html" - get :js_or_html - assert_equal 'HTML', @response.body - - get :html_or_xml - assert_equal 'HTML', @response.body - - assert_raises(ActionController::UnknownFormat) do - get :just_xml - end - end - - def test_all - @request.accept = "*/*" - get :js_or_html - assert_equal 'HTML', @response.body # js is not part of all - - get :html_or_xml - assert_equal 'HTML', @response.body - - get :just_xml - assert_equal 'XML', @response.body - end - - def test_xml - @request.accept = "application/xml" - get :html_xml_or_rss - assert_equal 'XML', @response.body - end - - def test_js_or_html - @request.accept = "text/javascript, text/html" - xhr :get, :js_or_html - assert_equal 'JS', @response.body - - @request.accept = "text/javascript, text/html" - xhr :get, :html_or_xml - assert_equal 'HTML', @response.body - - @request.accept = "text/javascript, text/html" - - assert_raises(ActionController::UnknownFormat) do - xhr :get, :just_xml - end - end - - def test_json_or_yaml_with_leading_star_star - @request.accept = "*/*, application/json" - get :json_xml_or_html - assert_equal 'HTML', @response.body - - @request.accept = "*/* , application/json" - get :json_xml_or_html - assert_equal 'HTML', @response.body - end - - def test_json_or_yaml - xhr :get, :json_or_yaml - assert_equal 'JSON', @response.body - - get :json_or_yaml, :format => 'json' - assert_equal 'JSON', @response.body - - get :json_or_yaml, :format => 'yaml' - assert_equal 'YAML', @response.body - - { 'YAML' => %w(text/yaml), - 'JSON' => %w(application/json text/x-json) - }.each do |body, content_types| - content_types.each do |content_type| - @request.accept = content_type - get :json_or_yaml - assert_equal body, @response.body - end - end - end - - def test_js_or_anything - @request.accept = "text/javascript, */*" - xhr :get, :js_or_html - assert_equal 'JS', @response.body - - xhr :get, :html_or_xml - assert_equal 'HTML', @response.body - - xhr :get, :just_xml - assert_equal 'XML', @response.body - end - - def test_using_defaults - @request.accept = "*/*" - get :using_defaults - assert_equal "text/html", @response.content_type - assert_equal 'Hello world!', @response.body - - @request.accept = "application/xml" - get :using_defaults - assert_equal "application/xml", @response.content_type - assert_equal "<p>Hello world!</p>\n", @response.body - end - - def test_using_defaults_with_all - @request.accept = "*/*" - get :using_defaults_with_all - assert_equal "HTML!", @response.body.strip - - @request.accept = "text/html" - get :using_defaults_with_all - assert_equal "HTML!", @response.body.strip - - @request.accept = "application/json" - get :using_defaults_with_all - assert_equal "ALL", @response.body - end - - def test_using_defaults_with_type_list - @request.accept = "*/*" - get :using_defaults_with_type_list - assert_equal "text/html", @response.content_type - assert_equal 'Hello world!', @response.body - - @request.accept = "application/xml" - get :using_defaults_with_type_list - assert_equal "application/xml", @response.content_type - assert_equal "<p>Hello world!</p>\n", @response.body - end - - def test_with_atom_content_type - @request.accept = "" - @request.env["CONTENT_TYPE"] = "application/atom+xml" - xhr :get, :made_for_content_type - assert_equal "ATOM", @response.body - end - - def test_with_rss_content_type - @request.accept = "" - @request.env["CONTENT_TYPE"] = "application/rss+xml" - xhr :get, :made_for_content_type - assert_equal "RSS", @response.body - end - - def test_synonyms - @request.accept = "application/javascript" - get :js_or_html - assert_equal 'JS', @response.body - - @request.accept = "application/x-xml" - get :html_xml_or_rss - assert_equal "XML", @response.body - end - - def test_custom_types - @request.accept = "application/crazy-xml" - get :custom_type_handling - assert_equal "application/crazy-xml", @response.content_type - assert_equal 'Crazy XML', @response.body - - @request.accept = "text/html" - get :custom_type_handling - assert_equal "text/html", @response.content_type - assert_equal 'HTML', @response.body - end - - def test_xhtml_alias - @request.accept = "application/xhtml+xml,application/xml" - get :html_or_xml - assert_equal 'HTML', @response.body - end - - def test_firefox_simulation - @request.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5" - get :html_or_xml - assert_equal 'HTML', @response.body - end - - def test_handle_any - @request.accept = "*/*" - get :handle_any - assert_equal 'HTML', @response.body - - @request.accept = "text/javascript" - get :handle_any - assert_equal 'Either JS or XML', @response.body - - @request.accept = "text/xml" - get :handle_any - assert_equal 'Either JS or XML', @response.body - end - - def test_handle_any_any - @request.accept = "*/*" - get :handle_any_any - assert_equal 'HTML', @response.body - end - - def test_handle_any_any_parameter_format - get :handle_any_any, {:format=>'html'} - assert_equal 'HTML', @response.body - end - - def test_handle_any_any_explicit_html - @request.accept = "text/html" - get :handle_any_any - assert_equal 'HTML', @response.body - end - - def test_handle_any_any_javascript - @request.accept = "text/javascript" - get :handle_any_any - assert_equal 'Whatever you ask for, I got it', @response.body - end - - def test_handle_any_any_xml - @request.accept = "text/xml" - get :handle_any_any - assert_equal 'Whatever you ask for, I got it', @response.body - end - - def test_browser_check_with_any_any - @request.accept = "application/json, application/xml" - get :json_xml_or_html - assert_equal 'JSON', @response.body - - @request.accept = "application/json, application/xml, */*" - get :json_xml_or_html - assert_equal 'HTML', @response.body - end - - def test_html_type_with_layout - @request.accept = "text/html" - get :all_types_with_layout - assert_equal '<html><div id="html">HTML for all_types_with_layout</div></html>', @response.body - end - - def test_xhr - xhr :get, :js_or_html - assert_equal 'JS', @response.body - end - - def test_custom_constant - get :custom_constant_handling, :format => "mobile" - assert_equal "text/x-mobile", @response.content_type - assert_equal "Mobile", @response.body - end - - def test_custom_constant_handling_without_block - get :custom_constant_handling_without_block, :format => "mobile" - assert_equal "text/x-mobile", @response.content_type - assert_equal "Mobile", @response.body - end - - def test_forced_format - get :html_xml_or_rss - assert_equal "HTML", @response.body - - get :html_xml_or_rss, :format => "html" - assert_equal "HTML", @response.body - - get :html_xml_or_rss, :format => "xml" - assert_equal "XML", @response.body - - get :html_xml_or_rss, :format => "rss" - assert_equal "RSS", @response.body - end - - def test_internally_forced_format - get :forced_xml - assert_equal "XML", @response.body - - get :forced_xml, :format => "html" - assert_equal "XML", @response.body - end - - def test_extension_synonyms - get :html_xml_or_rss, :format => "xhtml" - assert_equal "HTML", @response.body - end - - def test_render_action_for_html - @controller.instance_eval do - def render(*args) - @action = args.first[:action] unless args.empty? - @action ||= action_name - - response.body = "#{@action} - #{formats}" - end - end - - get :using_defaults - assert_equal "using_defaults - #{[:html].to_s}", @response.body - - get :using_defaults, :format => "xml" - assert_equal "using_defaults - #{[:xml].to_s}", @response.body - end - - def test_format_with_custom_response_type - get :iphone_with_html_response_type - assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body - - get :iphone_with_html_response_type, :format => "iphone" - assert_equal "text/html", @response.content_type - assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body - end - - def test_format_with_custom_response_type_and_request_headers - @request.accept = "text/iphone" - get :iphone_with_html_response_type - assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body - assert_equal "text/html", @response.content_type - end - - def test_invalid_format - assert_raises(ActionController::UnknownFormat) do - get :using_defaults, :format => "invalidformat" - end - end -end class RespondWithController < ActionController::Base respond_to :html, :json, :touch @@ -631,6 +107,20 @@ class RenderJsonRespondWithController < RespondWithController end end +class CsvRespondWithController < ActionController::Base + respond_to :csv + + class RespondWithCsv + def to_csv + "c,s,v" + end + end + + def index + respond_with(RespondWithCsv.new) + end +end + class EmptyRespondWithController < ActionController::Base def index respond_with(Customer.new("david", 13)) @@ -646,7 +136,6 @@ class RespondWithControllerTest < ActionController::TestCase Mime::Type.register_alias('text/html', :iphone) Mime::Type.register_alias('text/html', :touch) Mime::Type.register('text/x-mobile', :mobile) - Customer.send(:undef_method, :to_json) if Customer.method_defined?(:to_json) end def teardown @@ -848,6 +337,7 @@ class RespondWithControllerTest < ActionController::TestCase errors = { :name => :invalid } Customer.any_instance.stubs(:errors).returns(errors) put :using_resource + assert_equal "text/html", @response.content_type assert_equal 200, @response.status assert_equal "Edit world!\n", @response.body @@ -1132,6 +622,23 @@ class RespondWithControllerTest < ActionController::TestCase RespondWithController.responder = ActionController::Responder end + def test_uses_renderer_if_an_api_behavior + ActionController::Renderers.add :csv do |obj, options| + send_data obj.to_csv, type: Mime::CSV + end + @controller = CsvRespondWithController.new + get :index, format: 'csv' + assert_equal Mime::CSV, @response.content_type + assert_equal "c,s,v", @response.body + end + + def test_raises_missing_renderer_if_an_api_behavior_with_no_renderer + @controller = CsvRespondWithController.new + assert_raise ActionController::MissingRenderer do + get :index, format: 'csv' + end + end + def test_error_is_raised_if_no_respond_to_is_declared_and_respond_with_is_called @controller = EmptyRespondWithController.new @request.accept = "*/*" @@ -1155,69 +662,6 @@ class RespondWithControllerTest < ActionController::TestCase end end -class AbstractPostController < ActionController::Base - self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/" -end - -# For testing layouts which are set automatically -class PostController < AbstractPostController - around_action :with_iphone - - def index - respond_to(:html, :iphone, :js) - end - -protected - - def with_iphone - request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" - yield - end -end - -class SuperPostController < PostController -end - -class MimeControllerLayoutsTest < ActionController::TestCase - tests PostController - - def setup - super - @request.host = "www.example.com" - Mime::Type.register_alias("text/html", :iphone) - end - - def teardown - super - Mime::Type.unregister(:iphone) - end - - def test_missing_layout_renders_properly - get :index - assert_equal '<html><div id="html">Hello Firefox</div></html>', @response.body - - @request.accept = "text/iphone" - get :index - assert_equal 'Hello iPhone', @response.body - end - - def test_format_with_inherited_layouts - @controller = SuperPostController.new - - get :index - assert_equal '<html><div id="html">Super Firefox</div></html>', @response.body - - @request.accept = "text/iphone" - get :index - assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body - end - - def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout - get :index, :format => :js - assert_equal "Hello Firefox", @response.body - end -end - class FlashResponder < ActionController::Responder def initialize(controller, resources, options={}) super diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb index d6c3926a4d..2a253799f3 100644 --- a/actionpack/test/controller/new_base/render_text_test.rb +++ b/actionpack/test/controller/new_base/render_text_test.rb @@ -1,6 +1,15 @@ require 'abstract_unit' module RenderText + class MinimalController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + + def index + render text: "Hello World!" + end + end + class SimpleController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new] @@ -63,6 +72,12 @@ module RenderText end class RenderTextTest < Rack::TestCase + test "rendering text from a minimal controller" do + get "/render_text/minimal/index" + assert_body "Hello World!" + assert_status 200 + end + test "rendering text from an action with default options renders the text with the layout" do with_routing do |set| set.draw { get ':controller', :action => 'index' } diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 437da43d9b..84e007b5d0 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -107,6 +107,15 @@ class ParametersPermitTest < ActiveSupport::TestCase assert_equal [], permitted[:id] end + test 'do not break params filtering on nil values' do + params = ActionController::Parameters.new(a: 1, b: [1, 2, 3], c: nil) + + permitted = params.permit(:a, c: [], b: []) + assert_equal 1, permitted[:a] + assert_equal [1, 2, 3], permitted[:b] + assert_equal nil, permitted[:c] + end + test 'key to empty array: arrays of permitted scalars pass' do [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array| params = ActionController::Parameters.new(id: array) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 98b34a872b..f41287381a 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -2,26 +2,6 @@ require 'abstract_unit' require 'controller/fake_models' require 'pathname' -module Fun - class GamesController < ActionController::Base - # :ported: - def hello_world - end - - def nested_partial_with_form_builder - render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) - end - end -end - -module Quiz - class QuestionsController < ActionController::Base - def new - render :partial => Quiz::Question.new("Namespaced Partial") - end - end -end - class TestControllerWithExtraEtags < ActionController::Base etag { nil } etag { 'ab' } @@ -58,10 +38,6 @@ class TestController < ActionController::Base def hello_world end - def hello_world_file - render :file => File.expand_path("../../fixtures/hello", __FILE__), :formats => [:html] - end - def conditional_hello if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123]) render :action => 'hello_world' @@ -147,327 +123,10 @@ class TestController < ActionController::Base fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) end - # :ported: - def render_hello_world - render :template => "test/hello_world" - end - - def render_hello_world_with_last_modified_set - response.last_modified = Date.new(2008, 10, 10).to_time - render :template => "test/hello_world" - end - - # :ported: compatibility - def render_hello_world_with_forward_slash - render :template => "/test/hello_world" - end - - # :ported: - def render_template_in_top_directory - render :template => 'shared' - end - - # :deprecated: - def render_template_in_top_directory_with_slash - render :template => '/shared' - end - - # :ported: - def render_hello_world_from_variable - @person = "david" - render :text => "hello #{@person}" - end - - # :ported: - def render_action_hello_world - render :action => "hello_world" - end - - def render_action_upcased_hello_world - render :action => "Hello_world" - end - - def render_action_hello_world_as_string - render "hello_world" - end - - def render_action_hello_world_with_symbol - render :action => :hello_world - end - - # :ported: - def render_text_hello_world - render :text => "hello world" - end - - # :ported: - def render_text_hello_world_with_layout - @variable_for_layout = ", I am here!" - render :text => "hello world", :layout => true - end - - def hello_world_with_layout_false - render :layout => false - end - - # :ported: - def render_file_with_instance_variables - @secret = 'in the sauce' - path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar') - render :file => path - end - - # :ported: - def render_file_as_string_with_instance_variables - @secret = 'in the sauce' - path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar')) - render path - end - - # :ported: - def render_file_not_using_full_path - @secret = 'in the sauce' - render :file => 'test/render_file_with_ivar' - end - - def render_file_not_using_full_path_with_dot_in_path - @secret = 'in the sauce' - render :file => 'test/dot.directory/render_file_with_ivar' - end - - def render_file_using_pathname - @secret = 'in the sauce' - render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar') - end - - def render_file_from_template - @secret = 'in the sauce' - @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar')) - end - - def render_file_with_locals - path = File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals') - render :file => path, :locals => {:secret => 'in the sauce'} - end - - def render_file_as_string_with_locals - path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals')) - render path, :locals => {:secret => 'in the sauce'} - end - - def accessing_request_in_template - render :inline => "Hello: <%= request.host %>" - end - - def accessing_logger_in_template - render :inline => "<%= logger.class %>" - end - - def accessing_action_name_in_template - render :inline => "<%= action_name %>" - end - - def accessing_controller_name_in_template - render :inline => "<%= controller_name %>" - end - - # :ported: - def render_custom_code - render :text => "hello world", :status => 404 - end - - # :ported: - def render_text_with_nil - render :text => nil - end - - # :ported: - def render_text_with_false - render :text => false - end - - def render_text_with_resource - render :text => Customer.new("David") - end - - # :ported: - def render_nothing_with_appendix - render :text => "appended" - end - - # This test is testing 3 things: - # render :file in AV :ported: - # render :template in AC :ported: - # setting content type - def render_xml_hello - @name = "David" - render :template => "test/hello" - end - - def render_xml_hello_as_string_template - @name = "David" - render "test/hello" - end - - def render_line_offset - render :inline => '<% raise %>', :locals => {:foo => 'bar'} - end - def heading head :ok end - def greeting - # let's just rely on the template - end - - # :ported: - def blank_response - render :text => ' ' - end - - # :ported: - def layout_test - render :action => "hello_world" - end - - # :ported: - def builder_layout_test - @name = nil - render :action => "hello", :layout => "layouts/builder" - end - - # :move: test this in Action View - def builder_partial_test - render :action => "hello_world_container" - end - - # :ported: - def partials_list - @test_unchanged = 'hello' - @customers = [ Customer.new("david"), Customer.new("mary") ] - render :action => "list" - end - - def partial_only - render :partial => true - end - - def hello_in_a_string - @customers = [ Customer.new("david"), Customer.new("mary") ] - render :text => "How's there? " + render_to_string(:template => "test/list") - end - - def accessing_params_in_template - render :inline => "Hello: <%= params[:name] %>" - end - - def accessing_local_assigns_in_inline_template - name = params[:local_name] - render :inline => "<%= 'Goodbye, ' + local_name %>", - :locals => { :local_name => name } - end - - def render_implicit_html_template_from_xhr_request - end - - def render_implicit_js_template_without_layout - end - - def formatted_html_erb - end - - def formatted_xml_erb - end - - def render_to_string_test - @foo = render_to_string :inline => "this is a test" - end - - def default_render - @alternate_default_render ||= nil - if @alternate_default_render - @alternate_default_render.call - else - super - end - end - - def render_action_hello_world_as_symbol - render :action => :hello_world - end - - def layout_test_with_different_layout - render :action => "hello_world", :layout => "standard" - end - - def layout_test_with_different_layout_and_string_action - render "hello_world", :layout => "standard" - end - - def layout_test_with_different_layout_and_symbol_action - render :hello_world, :layout => "standard" - end - - def rendering_without_layout - render :action => "hello_world", :layout => false - end - - def layout_overriding_layout - render :action => "hello_world", :layout => "standard" - end - - def rendering_nothing_on_layout - render :nothing => true - end - - def render_to_string_with_assigns - @before = "i'm before the render" - render_to_string :text => "foo" - @after = "i'm after the render" - render :template => "test/hello_world" - end - - def render_to_string_with_exception - render_to_string :file => "exception that will not be caught - this will certainly not work" - end - - def render_to_string_with_caught_exception - @before = "i'm before the render" - begin - render_to_string :file => "exception that will be caught- hope my future instance vars still work!" - rescue - end - @after = "i'm after the render" - render :template => "test/hello_world" - end - - def accessing_params_in_template_with_layout - render :layout => true, :inline => "Hello: <%= params[:name] %>" - end - - # :ported: - def render_with_explicit_template - render :template => "test/hello_world" - end - - def render_with_explicit_unescaped_template - render :template => "test/h*llo_world" - end - - def render_with_explicit_escaped_template - render :template => "test/hello,world" - end - - def render_with_explicit_string_template - render "test/hello_world" - end - - # :ported: - def render_with_explicit_template_with_locals - render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' } - end - # :ported: def double_render render :text => "hello" @@ -508,25 +167,6 @@ class TestController < ActionController::Base render :template => "test/hello_world_from_rxml", :handlers => [:builder] end - def action_talk_to_layout - # Action template sets variable that's picked up by layout - end - - # :addressed: - def render_text_with_assigns - @hello = "world" - render :text => "foo" - end - - def yield_content_for - render :action => "content_for", :layout => "yield" - end - - def render_content_type_from_body - response.content_type = Mime::RSS - render :text => "hello world!" - end - def head_created head :created end @@ -571,162 +211,6 @@ class TestController < ActionController::Base head :forbidden, :x_custom_header => "something" end - def render_using_layout_around_block - render :action => "using_layout_around_block" - end - - def render_using_layout_around_block_in_main_layout_and_within_content_for_layout - render :action => "using_layout_around_block", :layout => "layouts/block_with_layout" - end - - def partial_formats_html - render :partial => 'partial', :formats => [:html] - end - - def partial - render :partial => 'partial' - end - - def partial_html_erb - render :partial => 'partial_html_erb' - end - - def render_to_string_with_partial - @partial_only = render_to_string :partial => "partial_only" - @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } - render :template => "test/hello_world" - end - - def render_to_string_with_template_and_html_partial - @text = render_to_string :template => "test/with_partial", :formats => [:text] - @html = render_to_string :template => "test/with_partial", :formats => [:html] - render :template => "test/with_html_partial" - end - - def render_to_string_and_render_with_different_formats - @html = render_to_string :template => "test/with_partial", :formats => [:html] - render :template => "test/with_partial", :formats => [:text] - end - - def render_template_within_a_template_with_other_format - render :template => "test/with_xml_template", - :formats => [:html], - :layout => "with_html_partial" - end - - def partial_with_counter - render :partial => "counter", :locals => { :counter_counter => 5 } - end - - def partial_with_locals - render :partial => "customer", :locals => { :customer => Customer.new("david") } - end - - def partial_with_form_builder - render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) - end - - def partial_with_form_builder_subclass - render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}) - end - - def partial_collection - render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] - end - - def partial_collection_with_as - render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer - end - - def partial_collection_with_counter - render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] - end - - def partial_collection_with_as_and_counter - render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client - end - - def partial_collection_with_locals - render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } - end - - def partial_collection_with_spacer - render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] - end - - def partial_collection_with_spacer_which_uses_render - render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] - end - - def partial_collection_shorthand_with_locals - render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } - end - - def partial_collection_shorthand_with_different_types_of_records - render :partial => [ - BadCustomer.new("mark"), - GoodCustomer.new("craig"), - BadCustomer.new("john"), - GoodCustomer.new("zach"), - GoodCustomer.new("brandon"), - BadCustomer.new("dan") ], - :locals => { :greeting => "Bonjour" } - end - - def empty_partial_collection - render :partial => "customer", :collection => [] - end - - def partial_collection_shorthand_with_different_types_of_records_with_counter - partial_collection_shorthand_with_different_types_of_records - end - - def missing_partial - render :partial => 'thisFileIsntHere' - end - - def partial_with_hash_object - render :partial => "hash_object", :object => {:first_name => "Sam"} - end - - def partial_with_nested_object - render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") - end - - def partial_with_nested_object_shorthand - render Quiz::Question.new("first") - end - - def partial_hash_collection - render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] - end - - def partial_hash_collection_with_locals - render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" } - end - - def partial_with_implicit_local_assignment - @customer = Customer.new("Marcel") - render :partial => "customer" - end - - def render_call_to_partial_with_layout - render :action => "calling_partial_with_layout" - end - - def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout - render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" - end - - before_action only: :render_with_filters do - request.format = :xml - end - - # Ensure that the before filter is executed *before* self.formats is set. - def render_with_filters - render :action => :formatted_xml_erb - end - private def set_variable_for_layout @@ -755,6 +239,8 @@ class TestController < ActionController::Base end class MetalTestController < ActionController::Metal + include AbstractController::Rendering + include ActionView::Rendering include ActionController::Rendering def accessing_logger_in_template @@ -762,792 +248,9 @@ class MetalTestController < ActionController::Metal end end -class RenderTest < ActionController::TestCase - tests TestController - - def setup - # 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". - super - @controller.logger = ActiveSupport::Logger.new(nil) - ActionView::Base.logger = ActiveSupport::Logger.new(nil) - - @request.host = "www.nextangle.com" - end - - def teardown - ActionView::Base.logger = nil - end - - # :ported: - def test_simple_show - get :hello_world - assert_response 200 - assert_response :success - assert_template "test/hello_world" - assert_equal "<html>Hello world!</html>", @response.body - end - - # :ported: - def test_renders_default_template_for_missing_action - get :'hyphen-ated' - assert_template 'test/hyphen-ated' - end - - # :ported: - def test_render - get :render_hello_world - assert_template "test/hello_world" - end - - def test_line_offset - get :render_line_offset - flunk "the action should have raised an exception" - rescue StandardError => exc - line = exc.backtrace.first - assert(line =~ %r{:(\d+):}) - assert_equal "1", $1, - "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" - end - - # :ported: compatibility - def test_render_with_forward_slash - get :render_hello_world_with_forward_slash - assert_template "test/hello_world" - end - - # :ported: - def test_render_in_top_directory - get :render_template_in_top_directory - assert_template "shared" - assert_equal "Elastica", @response.body - end - - # :ported: - def test_render_in_top_directory_with_slash - get :render_template_in_top_directory_with_slash - assert_template "shared" - assert_equal "Elastica", @response.body - end - - # :ported: - def test_render_from_variable - get :render_hello_world_from_variable - assert_equal "hello david", @response.body - end - - # :ported: - def test_render_action - get :render_action_hello_world - assert_template "test/hello_world" - end - - def test_render_action_upcased - assert_raise ActionView::MissingTemplate do - get :render_action_upcased_hello_world - end - end - - # :ported: - def test_render_action_hello_world_as_string - get :render_action_hello_world_as_string - assert_equal "Hello world!", @response.body - assert_template "test/hello_world" - end - - # :ported: - def test_render_action_with_symbol - get :render_action_hello_world_with_symbol - assert_template "test/hello_world" - end - - # :ported: - def test_render_text - get :render_text_hello_world - assert_equal "hello world", @response.body - end - - # :ported: - def test_do_with_render_text_and_layout - get :render_text_hello_world_with_layout - assert_equal "<html>hello world, I am here!</html>", @response.body - end - - # :ported: - def test_do_with_render_action_and_layout_false - get :hello_world_with_layout_false - assert_equal 'Hello world!', @response.body - end - - # :ported: - def test_render_file_with_instance_variables - get :render_file_with_instance_variables - assert_equal "The secret is in the sauce\n", @response.body - end - - def test_render_file - get :hello_world_file - assert_equal "Hello world!", @response.body - end - - # :ported: - def test_render_file_as_string_with_instance_variables - get :render_file_as_string_with_instance_variables - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_file_not_using_full_path - get :render_file_not_using_full_path - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_file_not_using_full_path_with_dot_in_path - get :render_file_not_using_full_path_with_dot_in_path - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_file_using_pathname - get :render_file_using_pathname - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_file_with_locals - get :render_file_with_locals - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_file_as_string_with_locals - get :render_file_as_string_with_locals - assert_equal "The secret is in the sauce\n", @response.body - end - - # :assessed: - def test_render_file_from_template - get :render_file_from_template - assert_equal "The secret is in the sauce\n", @response.body - end - - # :ported: - def test_render_custom_code - get :render_custom_code - assert_response 404 - assert_response :missing - assert_equal 'hello world', @response.body - end - - # :ported: - def test_render_text_with_nil - get :render_text_with_nil - assert_response 200 - assert_equal ' ', @response.body - end - - # :ported: - def test_render_text_with_false - get :render_text_with_false - assert_equal 'false', @response.body - end - - # :ported: - def test_render_nothing_with_appendix - get :render_nothing_with_appendix - assert_response 200 - assert_equal 'appended', @response.body - end - - def test_render_text_with_resource - get :render_text_with_resource - assert_equal 'name: "David"', @response.body - end - - # :ported: - def test_attempt_to_access_object_method - assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone } - end - - # :ported: - def test_private_methods - assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout } - end - - # :ported: - def test_access_to_request_in_view - get :accessing_request_in_template - assert_equal "Hello: www.nextangle.com", @response.body - end - - def test_access_to_logger_in_view - get :accessing_logger_in_template - assert_equal "ActiveSupport::Logger", @response.body - end - - # :ported: - def test_access_to_action_name_in_view - get :accessing_action_name_in_template - assert_equal "accessing_action_name_in_template", @response.body - end - - # :ported: - def test_access_to_controller_name_in_view - get :accessing_controller_name_in_template - assert_equal "test", @response.body # name is explicitly set in the controller. - end - - # :ported: - def test_render_xml - get :render_xml_hello - assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body - assert_equal "application/xml", @response.content_type - end - - # :ported: - def test_render_xml_as_string_template - get :render_xml_hello_as_string_template - assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body - assert_equal "application/xml", @response.content_type - end - - # :ported: - def test_render_xml_with_default - get :greeting - assert_equal "<p>This is grand!</p>\n", @response.body - end - - # :move: test in AV - def test_render_xml_with_partial - get :builder_partial_test - assert_equal "<test>\n <hello/>\n</test>\n", @response.body - end - - # :ported: - def test_layout_rendering - get :layout_test - assert_equal "<html>Hello world!</html>", @response.body - end - - def test_render_xml_with_layouts - get :builder_layout_test - assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body - end - - def test_partials_list - get :partials_list - assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body - end - - def test_render_to_string - get :hello_in_a_string - assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body - end - - def test_render_to_string_resets_assigns - get :render_to_string_test - assert_equal "The value of foo is: ::this is a test::\n", @response.body - end - - def test_render_to_string_inline - get :render_to_string_with_inline_and_render - assert_template "test/hello_world" - end - - # :ported: - def test_nested_rendering - @controller = Fun::GamesController.new - get :hello_world - assert_equal "Living in a nested world", @response.body - end - - def test_accessing_params_in_template - get :accessing_params_in_template, :name => "David" - assert_equal "Hello: David", @response.body - end - - def test_accessing_local_assigns_in_inline_template - get :accessing_local_assigns_in_inline_template, :local_name => "Local David" - assert_equal "Goodbye, Local David", @response.body - assert_equal "text/html", @response.content_type - end - - def test_should_implicitly_render_html_template_from_xhr_request - xhr :get, :render_implicit_html_template_from_xhr_request - assert_equal "XHR!\nHello HTML!", @response.body - end - - def test_should_implicitly_render_js_template_without_layout - get :render_implicit_js_template_without_layout, :format => :js - assert_no_match %r{<html>}, @response.body - end - - def test_should_render_formatted_template - get :formatted_html_erb - assert_equal 'formatted html erb', @response.body - end - - def test_should_render_formatted_html_erb_template - get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body - end - - def test_should_render_formatted_html_erb_template_with_bad_accepts_header - @request.env["HTTP_ACCEPT"] = "; a=dsf" - get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body - end - - def test_should_render_formatted_html_erb_template_with_faulty_accepts_header - @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" - get :formatted_xml_erb - assert_equal '<test>passed formatted html erb</test>', @response.body - end - - def test_layout_test_with_different_layout - get :layout_test_with_different_layout - assert_equal "<html>Hello world!</html>", @response.body - end - - def test_layout_test_with_different_layout_and_string_action - get :layout_test_with_different_layout_and_string_action - assert_equal "<html>Hello world!</html>", @response.body - end - - def test_layout_test_with_different_layout_and_symbol_action - get :layout_test_with_different_layout_and_symbol_action - assert_equal "<html>Hello world!</html>", @response.body - end - - def test_rendering_without_layout - get :rendering_without_layout - assert_equal "Hello world!", @response.body - end - - def test_layout_overriding_layout - get :layout_overriding_layout - assert_no_match %r{<title>}, @response.body - end - - def test_rendering_nothing_on_layout - get :rendering_nothing_on_layout - assert_equal " ", @response.body - end - - def test_render_to_string_doesnt_break_assigns - get :render_to_string_with_assigns - assert_equal "i'm before the render", assigns(:before) - assert_equal "i'm after the render", assigns(:after) - end - - def test_bad_render_to_string_still_throws_exception - assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } - end - - def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns - assert_nothing_raised { get :render_to_string_with_caught_exception } - assert_equal "i'm before the render", assigns(:before) - assert_equal "i'm after the render", assigns(:after) - end - - def test_accessing_params_in_template_with_layout - get :accessing_params_in_template_with_layout, :name => "David" - assert_equal "<html>Hello: David</html>", @response.body - end - - def test_render_with_explicit_template - get :render_with_explicit_template - assert_response :success - end - - def test_render_with_explicit_unescaped_template - assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template } - get :render_with_explicit_escaped_template - assert_equal "Hello w*rld!", @response.body - end - - def test_render_with_explicit_string_template - get :render_with_explicit_string_template - assert_equal "<html>Hello world!</html>", @response.body - end - - def test_render_with_filters - get :render_with_filters - assert_equal "<test>passed formatted xml erb</test>", @response.body - end - - # :ported: - def test_double_render - assert_raise(AbstractController::DoubleRenderError) { get :double_render } - end - - def test_double_redirect - assert_raise(AbstractController::DoubleRenderError) { get :double_redirect } - end - - def test_render_and_redirect - assert_raise(AbstractController::DoubleRenderError) { get :render_and_redirect } - end - - # specify the one exception to double render rule - render_to_string followed by render - def test_render_to_string_and_render - get :render_to_string_and_render - assert_equal("Hi web users! here is some cached stuff", @response.body) - end - - def test_rendering_with_conflicting_local_vars - get :rendering_with_conflicting_local_vars - assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body) - end - - def test_action_talk_to_layout - get :action_talk_to_layout - assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body - end - - # :addressed: - def test_render_text_with_assigns - get :render_text_with_assigns - assert_equal "world", assigns["hello"] - end - - # :ported: - def test_template_with_locals - get :render_with_explicit_template_with_locals - assert_equal "The secret is area51\n", @response.body - end - - def test_yield_content_for - get :yield_content_for - assert_equal "<title>Putting stuff in the title!</title>\nGreat stuff!\n", @response.body - end - - def test_overwritting_rendering_relative_file_with_extension - get :hello_world_from_rxml_using_template - assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body - - get :hello_world_from_rxml_using_action - assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body - end - - def test_head_created - post :head_created - assert @response.body.blank? - assert_response :created - end - - def test_head_created_with_application_json_content_type - post :head_created_with_application_json_content_type - assert @response.body.blank? - assert_equal "application/json", @response.header["Content-Type"] - assert_response :created - end - - def test_head_ok_with_image_png_content_type - post :head_ok_with_image_png_content_type - assert @response.body.blank? - assert_equal "image/png", @response.header["Content-Type"] - assert_response :ok - end - - def test_head_with_location_header - get :head_with_location_header - assert @response.body.blank? - assert_equal "/foo", @response.headers["Location"] - assert_response :ok - end - - def test_head_with_location_object - with_routing do |set| - set.draw do - resources :customers - get ':controller/:action' - end - - get :head_with_location_object - assert @response.body.blank? - assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] - assert_response :ok - end - end - - def test_head_with_custom_header - get :head_with_custom_header - assert @response.body.blank? - assert_equal "something", @response.headers["X-Custom-Header"] - assert_response :ok - end - - def test_head_with_www_authenticate_header - get :head_with_www_authenticate_header - assert @response.body.blank? - assert_equal "something", @response.headers["WWW-Authenticate"] - assert_response :ok - end - - def test_head_with_symbolic_status - get :head_with_symbolic_status, :status => "ok" - assert_equal 200, @response.status - assert_response :ok - - get :head_with_symbolic_status, :status => "not_found" - assert_equal 404, @response.status - assert_response :not_found - - get :head_with_symbolic_status, :status => "no_content" - assert_equal 204, @response.status - assert !@response.headers.include?('Content-Length') - assert_response :no_content - - Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| - get :head_with_symbolic_status, :status => status.to_s - assert_equal code, @response.response_code - assert_response status - end - end - - def test_head_with_integer_status - Rack::Utils::HTTP_STATUS_CODES.each do |code, message| - get :head_with_integer_status, :status => code.to_s - assert_equal message, @response.message - end - end - - def test_head_with_string_status - get :head_with_string_status, :status => "404 Eat Dirt" - assert_equal 404, @response.response_code - assert_equal "Not Found", @response.message - assert_response :not_found - end - - def test_head_with_status_code_first - get :head_with_status_code_first - assert_equal 403, @response.response_code - assert_equal "Forbidden", @response.message - assert_equal "something", @response.headers["X-Custom-Header"] - assert_response :forbidden - end - - def test_using_layout_around_block - get :render_using_layout_around_block - assert_equal "Before (David)\nInside from block\nAfter", @response.body - end - - def test_using_layout_around_block_in_main_layout_and_within_content_for_layout - get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout - assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body - end - - def test_partial_only - get :partial_only - assert_equal "only partial", @response.body - assert_equal "text/html", @response.content_type - end - - def test_should_render_html_formatted_partial - get :partial - assert_equal "partial html", @response.body - assert_equal "text/html", @response.content_type - end - - def test_render_html_formatted_partial_even_with_other_mime_time_in_accept - @request.accept = "text/javascript, text/html" - - get :partial_html_erb - - assert_equal "partial.html.erb", @response.body.strip - assert_equal "text/html", @response.content_type - end - - def test_should_render_html_partial_with_formats - get :partial_formats_html - assert_equal "partial html", @response.body - assert_equal "text/html", @response.content_type - end - - def test_render_to_string_partial - get :render_to_string_with_partial - assert_equal "only partial", assigns(:partial_only) - assert_equal "Hello: david", assigns(:partial_with_locals) - assert_equal "text/html", @response.content_type - end - - def test_render_to_string_with_template_and_html_partial - get :render_to_string_with_template_and_html_partial - assert_equal "**only partial**\n", assigns(:text) - assert_equal "<strong>only partial</strong>\n", assigns(:html) - assert_equal "<strong>only html partial</strong>\n", @response.body - assert_equal "text/html", @response.content_type - end - - def test_render_to_string_and_render_with_different_formats - get :render_to_string_and_render_with_different_formats - assert_equal "<strong>only partial</strong>\n", assigns(:html) - assert_equal "**only partial**\n", @response.body - assert_equal "text/plain", @response.content_type - end - - def test_render_template_within_a_template_with_other_format - get :render_template_within_a_template_with_other_format - expected = "only html partial<p>This is grand!</p>" - assert_equal expected, @response.body.strip - assert_equal "text/html", @response.content_type - end - - def test_partial_with_counter - get :partial_with_counter - assert_equal "5", @response.body - end - - def test_partial_with_locals - get :partial_with_locals - assert_equal "Hello: david", @response.body - end - - def test_partial_with_form_builder - get :partial_with_form_builder - assert_match(/<label/, @response.body) - assert_template('test/_form') - end - - def test_partial_with_form_builder_subclass - get :partial_with_form_builder_subclass - assert_match(/<label/, @response.body) - assert_template('test/_labelling_form') - end - - def test_nested_partial_with_form_builder - @controller = Fun::GamesController.new - get :nested_partial_with_form_builder - assert_match(/<label/, @response.body) - assert_template('fun/games/_form') - end - - def test_namespaced_object_partial - @controller = Quiz::QuestionsController.new - get :new - assert_equal "Namespaced Partial", @response.body - end - - def test_partial_collection - get :partial_collection - assert_equal "Hello: davidHello: mary", @response.body - end - - def test_partial_collection_with_as - get :partial_collection_with_as - assert_equal "david david davidmary mary mary", @response.body - end - - def test_partial_collection_with_counter - get :partial_collection_with_counter - assert_equal "david0mary1", @response.body - end - - def test_partial_collection_with_as_and_counter - get :partial_collection_with_as_and_counter - assert_equal "david0mary1", @response.body - end - - def test_partial_collection_with_locals - get :partial_collection_with_locals - assert_equal "Bonjour: davidBonjour: mary", @response.body - end - - def test_locals_option_to_assert_template_is_not_supported - get :partial_collection_with_locals - - warning_buffer = StringIO.new - $stderr = warning_buffer - - assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' } - assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string - ensure - $stderr = STDERR - end - - def test_partial_collection_with_spacer - get :partial_collection_with_spacer - assert_equal "Hello: davidonly partialHello: mary", @response.body - assert_template :partial => '_customer' - end - - def test_partial_collection_with_spacer_which_uses_render - get :partial_collection_with_spacer_which_uses_render - assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body - assert_template :partial => '_customer' - end - - def test_partial_collection_shorthand_with_locals - get :partial_collection_shorthand_with_locals - assert_equal "Bonjour: davidBonjour: mary", @response.body - assert_template :partial => 'customers/_customer', :count => 2 - assert_template :partial => '_completely_fake_and_made_up_template_that_cannot_possibly_be_rendered', :count => 0 - end - - def test_partial_collection_shorthand_with_different_types_of_records - get :partial_collection_shorthand_with_different_types_of_records - assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body - assert_template :partial => 'good_customers/_good_customer', :count => 3 - assert_template :partial => 'bad_customers/_bad_customer', :count => 3 - end - - def test_empty_partial_collection - get :empty_partial_collection - assert_equal " ", @response.body - assert_template :partial => false - end - - def test_partial_with_hash_object - get :partial_with_hash_object - assert_equal "Sam\nmaS\n", @response.body - end - - def test_partial_with_nested_object - get :partial_with_nested_object - assert_equal "first", @response.body - end - - def test_partial_with_nested_object_shorthand - get :partial_with_nested_object_shorthand - assert_equal "first", @response.body - end - - def test_hash_partial_collection - get :partial_hash_collection - assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body - end - - def test_partial_hash_collection_with_locals - get :partial_hash_collection_with_locals - assert_equal "Hola: PratikHola: Amy", @response.body - end - - def test_render_missing_partial_template - assert_raise(ActionView::MissingTemplate) do - get :missing_partial - end - end - - def test_render_call_to_partial_with_layout - get :render_call_to_partial_with_layout - assert_equal "Before (David)\nInside from partial (David)\nAfter", @response.body - end - - def test_render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout - get :render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout - assert_equal "Before (Anthony)\nInside from partial (Anthony)\nAfter\nBefore (David)\nInside from partial (David)\nAfter\nBefore (Ramm)\nInside from partial (Ramm)\nAfter", @response.body - end -end - class ExpiresInRenderTest < ActionController::TestCase tests TestController - def setup - @request.host = "www.nextangle.com" - end - def test_expires_in_header get :conditional_hello_with_expires_in assert_equal "max-age=60, private", @response.headers["Cache-Control"] @@ -1602,7 +305,6 @@ class LastModifiedRenderTest < ActionController::TestCase def setup super - @request.host = "www.nextangle.com" @last_modified = Time.now.utc.beginning_of_day.httpdate end @@ -1687,7 +389,6 @@ class EtagRenderTest < ActionController::TestCase def setup super - @request.host = "www.nextangle.com" end def test_multiple_etags @@ -1715,7 +416,6 @@ class EtagRenderTest < ActionController::TestCase end end - class MetalRenderTest < ActionController::TestCase tests MetalTestController @@ -1724,3 +424,109 @@ class MetalRenderTest < ActionController::TestCase assert_equal "NilClass", @response.body end end + +class HeadRenderTest < ActionController::TestCase + tests TestController + + def setup + @request.host = "www.nextangle.com" + end + + def test_head_created + post :head_created + assert @response.body.blank? + assert_response :created + end + + def test_head_created_with_application_json_content_type + post :head_created_with_application_json_content_type + assert @response.body.blank? + assert_equal "application/json", @response.header["Content-Type"] + assert_response :created + end + + def test_head_ok_with_image_png_content_type + post :head_ok_with_image_png_content_type + assert @response.body.blank? + assert_equal "image/png", @response.header["Content-Type"] + assert_response :ok + end + + def test_head_with_location_header + get :head_with_location_header + assert @response.body.blank? + assert_equal "/foo", @response.headers["Location"] + assert_response :ok + end + + def test_head_with_location_object + with_routing do |set| + set.draw do + resources :customers + get ':controller/:action' + end + + get :head_with_location_object + assert @response.body.blank? + assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] + assert_response :ok + end + end + + def test_head_with_custom_header + get :head_with_custom_header + assert @response.body.blank? + assert_equal "something", @response.headers["X-Custom-Header"] + assert_response :ok + end + + def test_head_with_www_authenticate_header + get :head_with_www_authenticate_header + assert @response.body.blank? + assert_equal "something", @response.headers["WWW-Authenticate"] + assert_response :ok + end + + def test_head_with_symbolic_status + get :head_with_symbolic_status, :status => "ok" + assert_equal 200, @response.status + assert_response :ok + + get :head_with_symbolic_status, :status => "not_found" + assert_equal 404, @response.status + assert_response :not_found + + get :head_with_symbolic_status, :status => "no_content" + assert_equal 204, @response.status + assert !@response.headers.include?('Content-Length') + assert_response :no_content + + Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code| + get :head_with_symbolic_status, :status => status.to_s + assert_equal code, @response.response_code + assert_response status + end + end + + def test_head_with_integer_status + Rack::Utils::HTTP_STATUS_CODES.each do |code, message| + get :head_with_integer_status, :status => code.to_s + assert_equal message, @response.message + end + end + + def test_head_with_string_status + get :head_with_string_status, :status => "404 Eat Dirt" + assert_equal 404, @response.response_code + assert_equal "Not Found", @response.message + assert_response :not_found + end + + def test_head_with_status_code_first + get :head_with_status_code_first + assert_equal 403, @response.response_code + assert_equal "Forbidden", @response.message + assert_equal "something", @response.headers["X-Custom-Header"] + assert_response :forbidden + end +end
\ No newline at end of file diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index ff0baccd76..3045a07ad6 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -128,6 +128,47 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_match(/ActionController::ParameterMissing/, body) end + test "rescue with text error for xhr request" do + @app = DevelopmentApp + xhr_request_env = {'action_dispatch.show_exceptions' => true, 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest'} + + get "/", {}, xhr_request_env + assert_response 500 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/puke/, body) + + get "/not_found", {}, xhr_request_env + assert_response 404 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/#{AbstractController::ActionNotFound.name}/, body) + + get "/method_not_allowed", {}, xhr_request_env + assert_response 405 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/ActionController::MethodNotAllowed/, body) + + get "/unknown_http_method", {}, xhr_request_env + assert_response 405 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/ActionController::UnknownHttpMethod/, body) + + get "/bad_request", {}, xhr_request_env + assert_response 400 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/ActionController::BadRequest/, body) + + get "/parameter_missing", {}, xhr_request_env + assert_response 400 + assert_no_match(/<body>/, body) + assert_equal response.content_type, "text/plain" + assert_match(/ActionController::ParameterMissing/, body) + end + test "does not show filtered parameters" do @app = DevelopmentApp diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 2fbe7358f9..4501ea095c 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -212,6 +212,11 @@ class ResponseTest < ActiveSupport::TestCase ActionDispatch::Response.default_headers = nil end end + + test "respond_to? accepts include_private" do + assert_not @response.respond_to?(:method_missing) + assert @response.respond_to?(:method_missing, true) + end end class ResponseIntegrationTest < ActionDispatch::IntegrationTest diff --git a/actionpack/test/fixtures/helpers/abc_helper.rb b/actionpack/test/fixtures/helpers/abc_helper.rb index 7104ff3730..cf2774bb5f 100644 --- a/actionpack/test/fixtures/helpers/abc_helper.rb +++ b/actionpack/test/fixtures/helpers/abc_helper.rb @@ -1,5 +1,3 @@ module AbcHelper def bare_a() end - def bare_b() end - def bare_c() end end diff --git a/actionpack/test/fixtures/layout_tests/layouts/symlinked b/actionpack/test/fixtures/layout_tests/layouts/symlinked deleted file mode 120000 index 0ddc1154ab..0000000000 --- a/actionpack/test/fixtures/layout_tests/layouts/symlinked +++ /dev/null @@ -1 +0,0 @@ -../../symlink_parent
\ No newline at end of file diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 82f38b5309..38abb16d08 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -28,12 +28,6 @@ class Customer < Struct.new(:name, :id) end end -class BadCustomer < Customer -end - -class GoodCustomer < Customer -end - class ValidatedCustomer < Customer def errors if name =~ /Sikachu/i diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 98b78a7114..53142a835e 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,18 @@ +* Fix `form_for` when both `namespace` and `as` options are present + + `as` option no longer overwrites `namespace` option when generating + html id attribute of the form element + + *Adam Niedzielski* + +* Fix `excerpt` when `:separator` is `nil`. + + *Paul Nikitochkin* + +* Only cache template digests if `config.cache_template_loading` id true. + + *Josh Lauer*, *Justin Ridgewell* + * Added an `extname` hash option for `javascript_include_tag` method. Before: diff --git a/actionview/Rakefile b/actionview/Rakefile index 8e980df7fc..e1cb4b5be0 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -7,14 +7,7 @@ task :default => :test # Run the unit tests desc "Run all unit tests" -task :test => [:test_action_view, :test_active_record_integration] - -Rake::TestTask.new(:test_action_view) do |t| - t.libs << 'test' - t.test_files = Dir.glob('test/template/**/*_test.rb').sort - t.warning = true - t.verbose = true -end +task :test => ["test:template", "test:integration:action_pack", "test:integration:active_record"] namespace :test do task :isolated do @@ -25,16 +18,28 @@ namespace :test do Rake::TestTask.new(:template) do |t| t.libs << 'test' - t.pattern = 'test/template/**/*.rb' + t.test_files = Dir.glob('test/template/**/*_test.rb').sort + t.warning = true + t.verbose = true end -end -desc 'ActiveRecord Integration Tests' -Rake::TestTask.new(:test_active_record_integration) do |t| - t.libs << 'test' - t.test_files = Dir.glob("test/activerecord/*_test.rb") - t.warning = true - t.verbose = true + namespace :integration do + desc 'ActiveRecord Integration Tests' + Rake::TestTask.new(:active_record) do |t| + t.libs << 'test' + t.test_files = Dir.glob("test/activerecord/*_test.rb") + t.warning = true + t.verbose = true + end + + desc 'ActionPack Integration Tests' + Rake::TestTask.new(:action_pack) do |t| + t.libs << 'test' + t.test_files = Dir.glob("test/actionpack/**/*_test.rb") + t.warning = true + t.verbose = true + end + end end spec = eval(File.read('actionview.gemspec')) diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 8def4ba7c5..39c0c6c856 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -34,10 +34,13 @@ module ActionView autoload :Digestor autoload :Helpers autoload :LookupContext + autoload :Layouts autoload :PathSet autoload :RecordIdentifier + autoload :Rendering autoload :RoutingUrlFor autoload :Template + autoload :ViewPaths autoload_under "renderer" do autoload :Renderer @@ -82,6 +85,7 @@ module ActionView def self.eager_load! super ActionView::Template.eager_load! + HTML.eager_load! end end diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index 08253de3f4..caade8f43b 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -2,6 +2,10 @@ require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/ordered_options' require 'action_view/log_subscriber' +require 'action_view/helpers' +require 'action_view/context' +require 'action_view/template' +require 'action_view/lookup_context' module ActionView #:nodoc: # = Action View Base diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 64239c81b2..95c3200c81 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -32,9 +32,13 @@ module ActionView Digestor end - @@cache[cache_key] = digest = klass.new(name, format, finder, options).digest # Store the actual digest - ensure - @@cache.delete_pair(cache_key, false) if pre_stored && !digest # something went wrong, make sure not to corrupt the @@cache + # Store the actual digest if config.cache_template_loading is true + klass.new(name, format, finder, options).digest.tap do |digest| + @@cache[cache_key] = digest if ActionView::Resolver.caching? + end + rescue Exception + @@cache.delete_pair(cache_key, false) if pre_stored # something went wrong, make sure not to corrupt the @@cache + raise end end diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index 0b957adb91..c830ab23e3 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -143,9 +143,9 @@ module ActionView "#{source}#{tail}" end - alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with a asset_path named route + alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route - # Computes the full URL to a asset in the public directory. This + # Computes the full URL to an asset in the public directory. This # will use +asset_path+ internally, so most of their behaviors # will be the same. def asset_url(source, options = {}) diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 2a38e5c446..b3af1d4da4 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -4,7 +4,7 @@ module ActionView module CacheHelper # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful - # caching pieces like menus, lists of newstopics, static HTML + # caching pieces like menus, lists of new topics, static HTML # fragments, and so on. This method takes a block that contains # the content you wish to cache. # diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 8a4830d887..ead7871fc5 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -442,10 +442,11 @@ module ActionView object = convert_to_model(object) as = options[:as] + namespace = options[:namespace] action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] options[:html].reverse_merge!( class: as ? "#{action}_#{as}" : dom_class(object, action), - id: as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, + id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence, method: method ) diff --git a/actionview/lib/action_view/helpers/record_tag_helper.rb b/actionview/lib/action_view/helpers/record_tag_helper.rb index f767957fa9..77c3e6d394 100644 --- a/actionview/lib/action_view/helpers/record_tag_helper.rb +++ b/actionview/lib/action_view/helpers/record_tag_helper.rb @@ -1,3 +1,5 @@ +require 'action_view/record_identifier' + module ActionView # = Action View Record Tag Helpers module Helpers diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 3fc64fa8a5..c23d605c5f 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -150,7 +150,7 @@ module ActionView def excerpt(text, phrase, options = {}) return unless text && phrase - separator = options.fetch(:separator, "") + separator = options[:separator] || '' phrase = Regexp.escape(phrase) regex = /#{phrase}/i @@ -171,7 +171,8 @@ module ActionView prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) - prefix + (first_part + separator + phrase + separator + second_part).strip + postfix + affix = [first_part, separator, phrase, separator, second_part].join.strip + [prefix, affix, postfix].join end # Attempts to pluralize the +singular+ word unless +count+ is 1. If diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionview/lib/action_view/layouts.rb index 8e7bdf620e..d8de1d95df 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -1,6 +1,7 @@ +require "action_view/rendering" require "active_support/core_ext/module/remove_method" -module AbstractController +module ActionView # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: # @@ -200,7 +201,7 @@ module AbstractController module Layouts extend ActiveSupport::Concern - include Rendering + include ActionView::Rendering included do class_attribute :_layout, :_layout_conditions, :instance_accessor => false diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index fd9a543e0a..354a136894 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -1,3 +1,5 @@ +require 'active_support/log_subscriber' + module ActionView # = Action View Log Subscriber # diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index e80e0ed9b0..4113448af0 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -35,5 +35,18 @@ module ActionView end end end + + initializer "action_view.setup_action_pack", before: :add_view_paths do |app| + ActiveSupport.on_load(:action_controller) do + ActionController::Base.superclass.send(:include, ActionView::Layouts) + ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) + end + end + + initializer "action_view.setup_action_mailer", before: :add_view_paths do |app| + ActiveSupport.on_load(:action_mailer) do + ActionMailer::Base.send(:include, ActionView::Layouts) + end + end end end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb new file mode 100644 index 0000000000..18a0788f8e --- /dev/null +++ b/actionview/lib/action_view/rendering.rb @@ -0,0 +1,141 @@ +require "action_view/view_paths" + +module ActionView + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + class I18nProxy < ::I18n::Config #:nodoc: + attr_reader :original_config, :lookup_context + + def initialize(original_config, lookup_context) + original_config = original_config.original_config if original_config.respond_to?(:original_config) + @original_config, @lookup_context = original_config, lookup_context + end + + def locale + @original_config.locale + end + + def locale=(value) + @lookup_context.locale = value + end + end + + module Rendering + extend ActiveSupport::Concern + include ActionView::ViewPaths + + # Overwrite process to setup I18n proxy. + def process(*) #:nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config + end + + module ClassMethods + def view_context_class + @view_context_class ||= begin + routes = respond_to?(:_routes) && _routes + helpers = respond_to?(:_helpers) && _helpers + + Class.new(ActionView::Base) do + if routes + include routes.url_helpers + include routes.mounted_helpers + end + + if helpers + include helpers + end + end + end + end + end + + attr_internal_writer :view_context_class + + def view_context_class + @_view_context_class ||= self.class.view_context_class + end + + # An instance of a view class. The default view class is ActionView::Base + # + # The view class must have the following methods: + # View.new[lookup_context, assigns, controller] + # Create a new ActionView instance for a controller + # View#render[options] + # Returns String with the rendered template + # + # Override this method in a module to change the default behavior. + def view_context + view_context_class.new(view_renderer, view_assigns, self) + end + + # Returns an object that is able to render templates. + # :api: private + def view_renderer + @_view_renderer ||= ActionView::Renderer.new(lookup_context) + end + + # Find and renders a template based on the options given. + # :api: private + def _render_template(options) #:nodoc: + lookup_context.rendered_format = nil if options[:formats] + view_renderer.render(view_context, options) + end + + def render_to_body(options = {}) + _process_options(options) + _render_template(options) + end + + def rendered_format + Mime[lookup_context.rendered_format] + end + + private + + # Assign the rendered format to lookup context. + def _process_format(format) #:nodoc: + super + lookup_context.formats = [format.to_sym] + lookup_context.rendered_format = lookup_context.formats.first + end + + # Normalize args by converting render "foo" to render :action => "foo" and + # render "foo/bar" to render :file => "foo/bar". + # :api: private + def _normalize_args(action=nil, options={}) + options = super(action, options) + case action + when NilClass + when Hash + options = action + when String, Symbol + action = action.to_s + key = action.include?(?/) ? :file : :action + options[key] = action + else + options[:partial] = action + end + + options + end + + # Normalize options. + # :api: private + def _normalize_options(options) + options = super(options) + if options[:partial] == true + options[:partial] = action_name + end + + if (options.keys & [:partial, :file, :template]).empty? + options[:prefixes] ||= _prefixes + end + + options[:template] ||= (options[:action] || action_name).to_s + options + end + end +end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionview/lib/action_view/view_paths.rb index c08b3a0e2a..6c349feb1d 100644 --- a/actionpack/lib/abstract_controller/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -1,6 +1,6 @@ require 'action_view/base' -module AbstractController +module ActionView module ViewPaths extend ActiveSupport::Concern diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 8213997f4e..6623b47e83 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -24,7 +24,6 @@ require 'action_dispatch' require 'active_support/dependencies' require 'active_model' require 'active_record' -require 'action_controller/caching' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late @@ -268,6 +267,10 @@ class Rack::TestCase < ActionDispatch::IntegrationTest end end +# Emulate AV railtie. +ActionController::Base.superclass.send(:include, ActionView::Layouts) +ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) + module ActionController class Base include ActionController::Testing @@ -290,9 +293,6 @@ module ActionController end end -class ::ApplicationController < ActionController::Base -end - module ActionView class TestCase # Must repeat the setup because AV::TestCase is a duplication @@ -330,53 +330,3 @@ module ActionDispatch end end -module ActionDispatch - module RoutingVerbs - def get(uri_or_host, path = nil) - host = uri_or_host.host unless path - path ||= uri_or_host.path - - params = {'PATH_INFO' => path, - 'REQUEST_METHOD' => 'GET', - 'HTTP_HOST' => host} - - routes.call(params)[2].join - end - end -end - -module RoutingTestHelpers - def url_for(set, options, recall = nil) - set.send(:url_for, options.merge(:only_path => true, :_recall => recall)) - end -end - -class ResourcesController < ActionController::Base - def index() render :nothing => true end - alias_method :show, :index -end - -class ThreadsController < ResourcesController; end -class MessagesController < ResourcesController; end -class CommentsController < ResourcesController; end -class ReviewsController < ResourcesController; end -class AuthorsController < ResourcesController; end -class LogosController < ResourcesController; end - -class AccountsController < ResourcesController; end -class AdminController < ResourcesController; end -class ProductsController < ResourcesController; end -class ImagesController < ResourcesController; end -class PreferencesController < ResourcesController; end - -module Backoffice - class ProductsController < ResourcesController; end - class TagsController < ResourcesController; end - class ManufacturersController < ResourcesController; end - class ImagesController < ResourcesController; end - - module Admin - class ProductsController < ResourcesController; end - class ImagesController < ResourcesController; end - end -end diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionview/test/actionpack/abstract/abstract_controller_test.rb index eb9143c8f6..40d3b17131 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionview/test/actionpack/abstract/abstract_controller_test.rb @@ -30,6 +30,7 @@ module AbstractController # ==== class RenderingController < AbstractController::Base include AbstractController::Rendering + include ActionView::Rendering def _prefixes [] @@ -153,7 +154,7 @@ module AbstractController # ==== # self._layout is used when defined class WithLayouts < PrefixedViews - include AbstractController::Layouts + include ActionView::Layouts private def self.layout(formats) diff --git a/actionpack/test/abstract/helper_test.rb b/actionview/test/actionpack/abstract/helper_test.rb index bc3e34684c..4e05245584 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionview/test/actionpack/abstract/helper_test.rb @@ -1,13 +1,14 @@ require 'abstract_unit' -ActionController::Base.helpers_path = File.expand_path('../../fixtures/helpers', __FILE__) +ActionController::Base.helpers_path = File.expand_path('../../../fixtures/helpers', __FILE__) module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include AbstractController::Rendering include AbstractController::Helpers + include AbstractController::Rendering + include ActionView::Rendering def with_module render :inline => "Module <%= included_method %>" @@ -51,7 +52,7 @@ module AbstractController class AbstractInvalidHelpers < AbstractHelpers include ActionController::Helpers - path = File.join(File.expand_path('../../fixtures', __FILE__), "helpers_missing") + path = File.join(File.expand_path('../../../fixtures', __FILE__), "helpers_missing") $:.unshift(path) self.helpers_path = path end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb index 4a05c00f8b..c79cb50fd7 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionview/test/actionpack/abstract/layouts_test.rb @@ -6,7 +6,8 @@ module AbstractControllerTests # Base controller for these tests class Base < AbstractController::Base include AbstractController::Rendering - include AbstractController::Layouts + include ActionView::Rendering + include ActionView::Layouts abstract! diff --git a/actionpack/test/abstract/render_test.rb b/actionview/test/actionpack/abstract/render_test.rb index b9293d1241..f9d8c916d9 100644 --- a/actionpack/test/abstract/render_test.rb +++ b/actionview/test/actionpack/abstract/render_test.rb @@ -5,6 +5,7 @@ module AbstractController class ControllerRenderer < AbstractController::Base include AbstractController::Rendering + include ActionView::Rendering def _prefixes %w[renderer] diff --git a/actionpack/test/abstract/views/abstract_controller/testing/me3/formatted.html.erb b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/formatted.html.erb index 785bf69191..785bf69191 100644 --- a/actionpack/test/abstract/views/abstract_controller/testing/me3/formatted.html.erb +++ b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/formatted.html.erb diff --git a/actionpack/test/abstract/views/abstract_controller/testing/me3/index.erb b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/index.erb index f079ad8204..f079ad8204 100644 --- a/actionpack/test/abstract/views/abstract_controller/testing/me3/index.erb +++ b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me3/index.erb diff --git a/actionpack/test/abstract/views/abstract_controller/testing/me4/index.erb b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me4/index.erb index 89dce12bdc..89dce12bdc 100644 --- a/actionpack/test/abstract/views/abstract_controller/testing/me4/index.erb +++ b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me4/index.erb diff --git a/actionpack/test/abstract/views/abstract_controller/testing/me5/index.erb b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb index 84d0b7417e..84d0b7417e 100644 --- a/actionpack/test/abstract/views/abstract_controller/testing/me5/index.erb +++ b/actionview/test/actionpack/abstract/views/abstract_controller/testing/me5/index.erb diff --git a/actionpack/test/abstract/views/action_with_ivars.erb b/actionview/test/actionpack/abstract/views/action_with_ivars.erb index 8d8ae22fd7..8d8ae22fd7 100644 --- a/actionpack/test/abstract/views/action_with_ivars.erb +++ b/actionview/test/actionpack/abstract/views/action_with_ivars.erb diff --git a/actionpack/test/abstract/views/helper_test.erb b/actionview/test/actionpack/abstract/views/helper_test.erb index 8ae45cc195..8ae45cc195 100644 --- a/actionpack/test/abstract/views/helper_test.erb +++ b/actionview/test/actionpack/abstract/views/helper_test.erb diff --git a/actionpack/test/abstract/views/index.erb b/actionview/test/actionpack/abstract/views/index.erb index cc1a8b8c85..cc1a8b8c85 100644 --- a/actionpack/test/abstract/views/index.erb +++ b/actionview/test/actionpack/abstract/views/index.erb diff --git a/actionpack/test/abstract/views/layouts/abstract_controller/testing/me4.erb b/actionview/test/actionpack/abstract/views/layouts/abstract_controller/testing/me4.erb index 172dd56569..172dd56569 100644 --- a/actionpack/test/abstract/views/layouts/abstract_controller/testing/me4.erb +++ b/actionview/test/actionpack/abstract/views/layouts/abstract_controller/testing/me4.erb diff --git a/actionpack/test/abstract/views/layouts/application.erb b/actionview/test/actionpack/abstract/views/layouts/application.erb index 27317140ad..27317140ad 100644 --- a/actionpack/test/abstract/views/layouts/application.erb +++ b/actionview/test/actionpack/abstract/views/layouts/application.erb diff --git a/actionpack/test/abstract/views/naked_render.erb b/actionview/test/actionpack/abstract/views/naked_render.erb index 1b3d03878b..1b3d03878b 100644 --- a/actionpack/test/abstract/views/naked_render.erb +++ b/actionview/test/actionpack/abstract/views/naked_render.erb diff --git a/actionpack/test/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb index 72263156d9..f8387b27b0 100644 --- a/actionpack/test/controller/capture_test.rb +++ b/actionview/test/actionpack/controller/capture_test.rb @@ -2,6 +2,8 @@ require 'abstract_unit' require 'active_support/logger' class CaptureController < ActionController::Base + self.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack' ] + def self.controller_name; "test"; end def self.controller_path; "test"; end diff --git a/actionpack/test/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 34304cf640..5dd23c4b31 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -9,7 +9,7 @@ old_load_paths = ActionController::Base.view_paths ActionView::Template::register_template_handler :mab, lambda { |template| template.source.inspect } -ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/layout_tests/' ] +ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/' ] class LayoutTest < ActionController::Base def self.controller_path; 'views' end @@ -87,7 +87,7 @@ class StreamingLayoutController < LayoutTest end class AbsolutePathLayoutController < LayoutTest - layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test') + layout File.expand_path(File.expand_path(__FILE__) + '/../../../fixtures/actionpack/layout_tests/layouts/layout_test') end class HasOwnLayoutController < LayoutTest @@ -108,7 +108,7 @@ end class PrependsViewPathController < LayoutTest def hello - prepend_view_path File.dirname(__FILE__) + '/../fixtures/layout_tests/alt/' + prepend_view_path File.dirname(__FILE__) + '/../../fixtures/actionpack/layout_tests/alt/' render :layout => 'alt' end end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb new file mode 100644 index 0000000000..964dccbffb --- /dev/null +++ b/actionview/test/actionpack/controller/render_test.rb @@ -0,0 +1,1337 @@ +require 'abstract_unit' +require "active_model" + +class ApplicationController < ActionController::Base + self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack") +end + +class Customer < Struct.new(:name, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + + undef_method :to_json + + def to_xml(options={}) + if options[:builder] + options[:builder].name name + else + "<name>#{name}</name>" + end + end + + def to_js(options={}) + "name: #{name.inspect}" + end + alias :to_text :to_js + + def errors + [] + end + + def persisted? + id.present? + end +end + +module Quiz + #Models + class Question < Struct.new(:name, :id) + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted? + id.present? + end + end + + class Store < Question; end + + # Controller + class QuestionsController < ApplicationController + def new + render :partial => Quiz::Question.new("Namespaced Partial") + end + end +end + +class BadCustomer < Customer; end +class GoodCustomer < Customer; end + +module Fun + class GamesController < ApplicationController + def hello_world; end + + def nested_partial_with_form_builder + render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) + end + end +end + +class TestController < ApplicationController + protect_from_forgery + + before_action :set_variable_for_layout + + class LabellingFormBuilder < ActionView::Helpers::FormBuilder + end + + layout :determine_layout + + def name + nil + end + + private :name + helper_method :name + + def hello_world + end + + def hello_world_file + render :file => File.expand_path("../../../fixtures/actionpack/hello", __FILE__), :formats => [:html] + end + + # :ported: + def render_hello_world + render :template => "test/hello_world" + end + + def render_hello_world_with_last_modified_set + response.last_modified = Date.new(2008, 10, 10).to_time + render :template => "test/hello_world" + end + + # :ported: compatibility + def render_hello_world_with_forward_slash + render :template => "/test/hello_world" + end + + # :ported: + def render_template_in_top_directory + render :template => 'shared' + end + + # :deprecated: + def render_template_in_top_directory_with_slash + render :template => '/shared' + end + + # :ported: + def render_hello_world_from_variable + @person = "david" + render :text => "hello #{@person}" + end + + # :ported: + def render_action_hello_world + render :action => "hello_world" + end + + def render_action_upcased_hello_world + render :action => "Hello_world" + end + + def render_action_hello_world_as_string + render "hello_world" + end + + def render_action_hello_world_with_symbol + render :action => :hello_world + end + + # :ported: + def render_text_hello_world + render :text => "hello world" + end + + # :ported: + def render_text_hello_world_with_layout + @variable_for_layout = ", I am here!" + render :text => "hello world", :layout => true + end + + def hello_world_with_layout_false + render :layout => false + end + + # :ported: + def render_file_with_instance_variables + @secret = 'in the sauce' + path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar') + render :file => path + end + + # :ported: + def render_file_as_string_with_instance_variables + @secret = 'in the sauce' + path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')) + render path + end + + # :ported: + def render_file_not_using_full_path + @secret = 'in the sauce' + render :file => 'test/render_file_with_ivar' + end + + def render_file_not_using_full_path_with_dot_in_path + @secret = 'in the sauce' + render :file => 'test/dot.directory/render_file_with_ivar' + end + + def render_file_using_pathname + @secret = 'in the sauce' + render :file => Pathname.new(File.dirname(__FILE__)).join('..', '..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar') + end + + def render_file_from_template + @secret = 'in the sauce' + @path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_ivar')) + end + + def render_file_with_locals + path = File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals') + render :file => path, :locals => {:secret => 'in the sauce'} + end + + def render_file_as_string_with_locals + path = File.expand_path(File.join(File.dirname(__FILE__), '../../fixtures/test/render_file_with_locals')) + render path, :locals => {:secret => 'in the sauce'} + end + + def accessing_request_in_template + render :inline => "Hello: <%= request.host %>" + end + + def accessing_logger_in_template + render :inline => "<%= logger.class %>" + end + + def accessing_action_name_in_template + render :inline => "<%= action_name %>" + end + + def accessing_controller_name_in_template + render :inline => "<%= controller_name %>" + end + + # :ported: + def render_custom_code + render :text => "hello world", :status => 404 + end + + # :ported: + def render_text_with_nil + render :text => nil + end + + # :ported: + def render_text_with_false + render :text => false + end + + def render_text_with_resource + render :text => Customer.new("David") + end + + # :ported: + def render_nothing_with_appendix + render :text => "appended" + end + + # This test is testing 3 things: + # render :file in AV :ported: + # render :template in AC :ported: + # setting content type + def render_xml_hello + @name = "David" + render :template => "test/hello" + end + + def render_xml_hello_as_string_template + @name = "David" + render "test/hello" + end + + def render_line_offset + render :inline => '<% raise %>', :locals => {:foo => 'bar'} + end + + def heading + head :ok + end + + def greeting + # let's just rely on the template + end + + # :ported: + def blank_response + render :text => ' ' + end + + # :ported: + def layout_test + render :action => "hello_world" + end + + # :ported: + def builder_layout_test + @name = nil + render :action => "hello", :layout => "layouts/builder" + end + + # :move: test this in Action View + def builder_partial_test + render :action => "hello_world_container" + end + + # :ported: + def partials_list + @test_unchanged = 'hello' + @customers = [ Customer.new("david"), Customer.new("mary") ] + render :action => "list" + end + + def partial_only + render :partial => true + end + + def hello_in_a_string + @customers = [ Customer.new("david"), Customer.new("mary") ] + render :text => "How's there? " + render_to_string(:template => "test/list") + end + + def accessing_params_in_template + render :inline => "Hello: <%= params[:name] %>" + end + + def accessing_local_assigns_in_inline_template + name = params[:local_name] + render :inline => "<%= 'Goodbye, ' + local_name %>", + :locals => { :local_name => name } + end + + def render_implicit_html_template_from_xhr_request + end + + def render_implicit_js_template_without_layout + end + + def formatted_html_erb + end + + def formatted_xml_erb + end + + def render_to_string_test + @foo = render_to_string :inline => "this is a test" + end + + def default_render + @alternate_default_render ||= nil + if @alternate_default_render + @alternate_default_render.call + else + super + end + end + + def render_action_hello_world_as_symbol + render :action => :hello_world + end + + def layout_test_with_different_layout + render :action => "hello_world", :layout => "standard" + end + + def layout_test_with_different_layout_and_string_action + render "hello_world", :layout => "standard" + end + + def layout_test_with_different_layout_and_symbol_action + render :hello_world, :layout => "standard" + end + + def rendering_without_layout + render :action => "hello_world", :layout => false + end + + def layout_overriding_layout + render :action => "hello_world", :layout => "standard" + end + + def rendering_nothing_on_layout + render :nothing => true + end + + def render_to_string_with_assigns + @before = "i'm before the render" + render_to_string :text => "foo" + @after = "i'm after the render" + render :template => "test/hello_world" + end + + def render_to_string_with_exception + render_to_string :file => "exception that will not be caught - this will certainly not work" + end + + def render_to_string_with_caught_exception + @before = "i'm before the render" + begin + render_to_string :file => "exception that will be caught- hope my future instance vars still work!" + rescue + end + @after = "i'm after the render" + render :template => "test/hello_world" + end + + def accessing_params_in_template_with_layout + render :layout => true, :inline => "Hello: <%= params[:name] %>" + end + + # :ported: + def render_with_explicit_template + render :template => "test/hello_world" + end + + def render_with_explicit_unescaped_template + render :template => "test/h*llo_world" + end + + def render_with_explicit_escaped_template + render :template => "test/hello,world" + end + + def render_with_explicit_string_template + render "test/hello_world" + end + + # :ported: + def render_with_explicit_template_with_locals + render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' } + end + + # :ported: + def double_render + render :text => "hello" + render :text => "world" + end + + def double_redirect + redirect_to :action => "double_render" + redirect_to :action => "double_render" + end + + def render_and_redirect + render :text => "hello" + redirect_to :action => "double_render" + end + + def render_to_string_and_render + @stuff = render_to_string :text => "here is some cached stuff" + render :text => "Hi web users! #{@stuff}" + end + + def render_to_string_with_inline_and_render + render_to_string :inline => "<%= 'dlrow olleh'.reverse %>" + render :template => "test/hello_world" + end + + def rendering_with_conflicting_local_vars + @name = "David" + render :action => "potential_conflicts" + end + + def hello_world_from_rxml_using_action + render :action => "hello_world_from_rxml", :handlers => [:builder] + end + + # :deprecated: + def hello_world_from_rxml_using_template + render :template => "test/hello_world_from_rxml", :handlers => [:builder] + end + + def action_talk_to_layout + # Action template sets variable that's picked up by layout + end + + # :addressed: + def render_text_with_assigns + @hello = "world" + render :text => "foo" + end + + def yield_content_for + render :action => "content_for", :layout => "yield" + end + + def render_content_type_from_body + response.content_type = Mime::RSS + render :text => "hello world!" + end + + def render_using_layout_around_block + render :action => "using_layout_around_block" + end + + def render_using_layout_around_block_in_main_layout_and_within_content_for_layout + render :action => "using_layout_around_block", :layout => "layouts/block_with_layout" + end + + def partial_formats_html + render :partial => 'partial', :formats => [:html] + end + + def partial + render :partial => 'partial' + end + + def partial_html_erb + render :partial => 'partial_html_erb' + end + + def render_to_string_with_partial + @partial_only = render_to_string :partial => "partial_only" + @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } + render :template => "test/hello_world" + end + + def render_to_string_with_template_and_html_partial + @text = render_to_string :template => "test/with_partial", :formats => [:text] + @html = render_to_string :template => "test/with_partial", :formats => [:html] + render :template => "test/with_html_partial" + end + + def render_to_string_and_render_with_different_formats + @html = render_to_string :template => "test/with_partial", :formats => [:html] + render :template => "test/with_partial", :formats => [:text] + end + + def render_template_within_a_template_with_other_format + render :template => "test/with_xml_template", + :formats => [:html], + :layout => "with_html_partial" + end + + def partial_with_counter + render :partial => "counter", :locals => { :counter_counter => 5 } + end + + def partial_with_locals + render :partial => "customer", :locals => { :customer => Customer.new("david") } + end + + def partial_with_form_builder + render :partial => ActionView::Helpers::FormBuilder.new(:post, nil, view_context, {}) + end + + def partial_with_form_builder_subclass + render :partial => LabellingFormBuilder.new(:post, nil, view_context, {}) + end + + def partial_collection + render :partial => "customer", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + + def partial_collection_with_as + render :partial => "customer_with_var", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :customer + end + + def partial_collection_with_counter + render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + + def partial_collection_with_as_and_counter + render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client + end + + def partial_collection_with_locals + render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } + end + + def partial_collection_with_spacer + render :partial => "customer", :spacer_template => "partial_only", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + + def partial_collection_with_spacer_which_uses_render + render :partial => "customer", :spacer_template => "partial_with_partial", :collection => [ Customer.new("david"), Customer.new("mary") ] + end + + def partial_collection_shorthand_with_locals + render :partial => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } + end + + def partial_collection_shorthand_with_different_types_of_records + render :partial => [ + BadCustomer.new("mark"), + GoodCustomer.new("craig"), + BadCustomer.new("john"), + GoodCustomer.new("zach"), + GoodCustomer.new("brandon"), + BadCustomer.new("dan") ], + :locals => { :greeting => "Bonjour" } + end + + def empty_partial_collection + render :partial => "customer", :collection => [] + end + + def partial_collection_shorthand_with_different_types_of_records_with_counter + partial_collection_shorthand_with_different_types_of_records + end + + def missing_partial + render :partial => 'thisFileIsntHere' + end + + def partial_with_hash_object + render :partial => "hash_object", :object => {:first_name => "Sam"} + end + + def partial_with_nested_object + render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") + end + + def partial_with_nested_object_shorthand + render Quiz::Question.new("first") + end + + def partial_hash_collection + render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] + end + + def partial_hash_collection_with_locals + render :partial => "hash_greeting", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ], :locals => { :greeting => "Hola" } + end + + def partial_with_implicit_local_assignment + @customer = Customer.new("Marcel") + render :partial => "customer" + end + + def render_call_to_partial_with_layout + render :action => "calling_partial_with_layout" + end + + def render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout + render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" + end + + before_action only: :render_with_filters do + request.format = :xml + end + + # Ensure that the before filter is executed *before* self.formats is set. + def render_with_filters + render :action => :formatted_xml_erb + end + + private + + def set_variable_for_layout + @variable_for_layout = nil + end + + def determine_layout + case action_name + when "hello_world", "layout_test", "rendering_without_layout", + "rendering_nothing_on_layout", "render_text_hello_world", + "render_text_hello_world_with_layout", + "hello_world_with_layout_false", + "partial_only", "accessing_params_in_template", + "accessing_params_in_template_with_layout", + "render_with_explicit_template", + "render_with_explicit_string_template", + "update_page", "update_page_with_instance_variables" + + "layouts/standard" + when "action_talk_to_layout", "layout_overriding_layout" + "layouts/talk_from_action" + when "render_implicit_html_template_from_xhr_request" + (request.xhr? ? 'layouts/xhr' : 'layouts/standard') + end + end +end + +class RenderTest < ActionController::TestCase + tests TestController + + def setup + # 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". + super + @controller.logger = ActiveSupport::Logger.new(nil) + ActionView::Base.logger = ActiveSupport::Logger.new(nil) + + @request.host = "www.nextangle.com" + end + + def teardown + ActionView::Base.logger = nil + end + + # :ported: + def test_simple_show + get :hello_world + assert_response 200 + assert_response :success + assert_template "test/hello_world" + assert_equal "<html>Hello world!</html>", @response.body + end + + # :ported: + def test_renders_default_template_for_missing_action + get :'hyphen-ated' + assert_template 'test/hyphen-ated' + end + + # :ported: + def test_render + get :render_hello_world + assert_template "test/hello_world" + end + + def test_line_offset + get :render_line_offset + flunk "the action should have raised an exception" + rescue StandardError => exc + line = exc.backtrace.first + assert(line =~ %r{:(\d+):}) + assert_equal "1", $1, + "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" + end + + # :ported: compatibility + def test_render_with_forward_slash + get :render_hello_world_with_forward_slash + assert_template "test/hello_world" + end + + # :ported: + def test_render_in_top_directory + get :render_template_in_top_directory + assert_template "shared" + assert_equal "Elastica", @response.body + end + + # :ported: + def test_render_in_top_directory_with_slash + get :render_template_in_top_directory_with_slash + assert_template "shared" + assert_equal "Elastica", @response.body + end + + # :ported: + def test_render_from_variable + get :render_hello_world_from_variable + assert_equal "hello david", @response.body + end + + # :ported: + def test_render_action + get :render_action_hello_world + assert_template "test/hello_world" + end + + def test_render_action_upcased + assert_raise ActionView::MissingTemplate do + get :render_action_upcased_hello_world + end + end + + # :ported: + def test_render_action_hello_world_as_string + get :render_action_hello_world_as_string + assert_equal "Hello world!", @response.body + assert_template "test/hello_world" + end + + # :ported: + def test_render_action_with_symbol + get :render_action_hello_world_with_symbol + assert_template "test/hello_world" + end + + # :ported: + def test_render_text + get :render_text_hello_world + assert_equal "hello world", @response.body + end + + # :ported: + def test_do_with_render_text_and_layout + get :render_text_hello_world_with_layout + assert_equal "<html>hello world, I am here!</html>", @response.body + end + + # :ported: + def test_do_with_render_action_and_layout_false + get :hello_world_with_layout_false + assert_equal 'Hello world!', @response.body + end + + # :ported: + def test_render_file_with_instance_variables + get :render_file_with_instance_variables + assert_equal "The secret is in the sauce\n", @response.body + end + + def test_render_file + get :hello_world_file + assert_equal "Hello world!", @response.body + end + + # :ported: + def test_render_file_as_string_with_instance_variables + get :render_file_as_string_with_instance_variables + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_file_not_using_full_path + get :render_file_not_using_full_path + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_file_not_using_full_path_with_dot_in_path + get :render_file_not_using_full_path_with_dot_in_path + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_file_using_pathname + get :render_file_using_pathname + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_file_with_locals + get :render_file_with_locals + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_file_as_string_with_locals + get :render_file_as_string_with_locals + assert_equal "The secret is in the sauce\n", @response.body + end + + # :assessed: + def test_render_file_from_template + get :render_file_from_template + assert_equal "The secret is in the sauce\n", @response.body + end + + # :ported: + def test_render_custom_code + get :render_custom_code + assert_response 404 + assert_response :missing + assert_equal 'hello world', @response.body + end + + # :ported: + def test_render_text_with_nil + get :render_text_with_nil + assert_response 200 + assert_equal ' ', @response.body + end + + # :ported: + def test_render_text_with_false + get :render_text_with_false + assert_equal 'false', @response.body + end + + # :ported: + def test_render_nothing_with_appendix + get :render_nothing_with_appendix + assert_response 200 + assert_equal 'appended', @response.body + end + + def test_render_text_with_resource + get :render_text_with_resource + assert_equal 'name: "David"', @response.body + end + + # :ported: + def test_attempt_to_access_object_method + assert_raise(AbstractController::ActionNotFound, "No action responded to [clone]") { get :clone } + end + + # :ported: + def test_private_methods + assert_raise(AbstractController::ActionNotFound, "No action responded to [determine_layout]") { get :determine_layout } + end + + # :ported: + def test_access_to_request_in_view + get :accessing_request_in_template + assert_equal "Hello: www.nextangle.com", @response.body + end + + def test_access_to_logger_in_view + get :accessing_logger_in_template + assert_equal "ActiveSupport::Logger", @response.body + end + + # :ported: + def test_access_to_action_name_in_view + get :accessing_action_name_in_template + assert_equal "accessing_action_name_in_template", @response.body + end + + # :ported: + def test_access_to_controller_name_in_view + get :accessing_controller_name_in_template + assert_equal "test", @response.body # name is explicitly set in the controller. + end + + # :ported: + def test_render_xml + get :render_xml_hello + assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body + assert_equal "application/xml", @response.content_type + end + + # :ported: + def test_render_xml_as_string_template + get :render_xml_hello_as_string_template + assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body + assert_equal "application/xml", @response.content_type + end + + # :ported: + def test_render_xml_with_default + get :greeting + assert_equal "<p>This is grand!</p>\n", @response.body + end + + # :move: test in AV + def test_render_xml_with_partial + get :builder_partial_test + assert_equal "<test>\n <hello/>\n</test>\n", @response.body + end + + # :ported: + def test_layout_rendering + get :layout_test + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_render_xml_with_layouts + get :builder_layout_test + assert_equal "<wrapper>\n<html>\n <p>Hello </p>\n<p>This is grand!</p>\n</html>\n</wrapper>\n", @response.body + end + + def test_partials_list + get :partials_list + assert_equal "goodbyeHello: davidHello: marygoodbye\n", @response.body + end + + def test_render_to_string + get :hello_in_a_string + assert_equal "How's there? goodbyeHello: davidHello: marygoodbye\n", @response.body + end + + def test_render_to_string_resets_assigns + get :render_to_string_test + assert_equal "The value of foo is: ::this is a test::\n", @response.body + end + + def test_render_to_string_inline + get :render_to_string_with_inline_and_render + assert_template "test/hello_world" + end + + # :ported: + def test_nested_rendering + @controller = Fun::GamesController.new + get :hello_world + assert_equal "Living in a nested world", @response.body + end + + def test_accessing_params_in_template + get :accessing_params_in_template, :name => "David" + assert_equal "Hello: David", @response.body + end + + def test_accessing_local_assigns_in_inline_template + get :accessing_local_assigns_in_inline_template, :local_name => "Local David" + assert_equal "Goodbye, Local David", @response.body + assert_equal "text/html", @response.content_type + end + + def test_should_implicitly_render_html_template_from_xhr_request + xhr :get, :render_implicit_html_template_from_xhr_request + assert_equal "XHR!\nHello HTML!", @response.body + end + + def test_should_implicitly_render_js_template_without_layout + get :render_implicit_js_template_without_layout, :format => :js + assert_no_match %r{<html>}, @response.body + end + + def test_should_render_formatted_template + get :formatted_html_erb + assert_equal 'formatted html erb', @response.body + end + + def test_should_render_formatted_html_erb_template + get :formatted_xml_erb + assert_equal '<test>passed formatted html erb</test>', @response.body + end + + def test_should_render_formatted_html_erb_template_with_bad_accepts_header + @request.env["HTTP_ACCEPT"] = "; a=dsf" + get :formatted_xml_erb + assert_equal '<test>passed formatted html erb</test>', @response.body + end + + def test_should_render_formatted_html_erb_template_with_faulty_accepts_header + @request.accept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, appliction/x-shockwave-flash, */*" + get :formatted_xml_erb + assert_equal '<test>passed formatted html erb</test>', @response.body + end + + def test_layout_test_with_different_layout + get :layout_test_with_different_layout + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_layout_test_with_different_layout_and_string_action + get :layout_test_with_different_layout_and_string_action + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_layout_test_with_different_layout_and_symbol_action + get :layout_test_with_different_layout_and_symbol_action + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_rendering_without_layout + get :rendering_without_layout + assert_equal "Hello world!", @response.body + end + + def test_layout_overriding_layout + get :layout_overriding_layout + assert_no_match %r{<title>}, @response.body + end + + def test_rendering_nothing_on_layout + get :rendering_nothing_on_layout + assert_equal " ", @response.body + end + + def test_render_to_string_doesnt_break_assigns + get :render_to_string_with_assigns + assert_equal "i'm before the render", assigns(:before) + assert_equal "i'm after the render", assigns(:after) + end + + def test_bad_render_to_string_still_throws_exception + assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } + end + + def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns + assert_nothing_raised { get :render_to_string_with_caught_exception } + assert_equal "i'm before the render", assigns(:before) + assert_equal "i'm after the render", assigns(:after) + end + + def test_accessing_params_in_template_with_layout + get :accessing_params_in_template_with_layout, :name => "David" + assert_equal "<html>Hello: David</html>", @response.body + end + + def test_render_with_explicit_template + get :render_with_explicit_template + assert_response :success + end + + def test_render_with_explicit_unescaped_template + assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template } + get :render_with_explicit_escaped_template + assert_equal "Hello w*rld!", @response.body + end + + def test_render_with_explicit_string_template + get :render_with_explicit_string_template + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_render_with_filters + get :render_with_filters + assert_equal "<test>passed formatted xml erb</test>", @response.body + end + + # :ported: + def test_double_render + assert_raise(AbstractController::DoubleRenderError) { get :double_render } + end + + def test_double_redirect + assert_raise(AbstractController::DoubleRenderError) { get :double_redirect } + end + + def test_render_and_redirect + assert_raise(AbstractController::DoubleRenderError) { get :render_and_redirect } + end + + # specify the one exception to double render rule - render_to_string followed by render + def test_render_to_string_and_render + get :render_to_string_and_render + assert_equal("Hi web users! here is some cached stuff", @response.body) + end + + def test_rendering_with_conflicting_local_vars + get :rendering_with_conflicting_local_vars + assert_equal("First: David\nSecond: Stephan\nThird: David\nFourth: David\nFifth: ", @response.body) + end + + def test_action_talk_to_layout + get :action_talk_to_layout + assert_equal "<title>Talking to the layout</title>\nAction was here!", @response.body + end + + # :addressed: + def test_render_text_with_assigns + get :render_text_with_assigns + assert_equal "world", assigns["hello"] + end + + # :ported: + def test_template_with_locals + get :render_with_explicit_template_with_locals + assert_equal "The secret is area51\n", @response.body + end + + def test_yield_content_for + get :yield_content_for + assert_equal "<title>Putting stuff in the title!</title>\nGreat stuff!\n", @response.body + end + + def test_overwritting_rendering_relative_file_with_extension + get :hello_world_from_rxml_using_template + assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body + + get :hello_world_from_rxml_using_action + assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body + end + + def test_using_layout_around_block + get :render_using_layout_around_block + assert_equal "Before (David)\nInside from block\nAfter", @response.body + end + + def test_using_layout_around_block_in_main_layout_and_within_content_for_layout + get :render_using_layout_around_block_in_main_layout_and_within_content_for_layout + assert_equal "Before (Anthony)\nInside from first block in layout\nAfter\nBefore (David)\nInside from block\nAfter\nBefore (Ramm)\nInside from second block in layout\nAfter\n", @response.body + end + + def test_partial_only + get :partial_only + assert_equal "only partial", @response.body + assert_equal "text/html", @response.content_type + end + + def test_should_render_html_formatted_partial + get :partial + assert_equal "partial html", @response.body + assert_equal "text/html", @response.content_type + end + + def test_render_html_formatted_partial_even_with_other_mime_time_in_accept + @request.accept = "text/javascript, text/html" + + get :partial_html_erb + + assert_equal "partial.html.erb", @response.body.strip + assert_equal "text/html", @response.content_type + end + + def test_should_render_html_partial_with_formats + get :partial_formats_html + assert_equal "partial html", @response.body + assert_equal "text/html", @response.content_type + end + + def test_render_to_string_partial + get :render_to_string_with_partial + assert_equal "only partial", assigns(:partial_only) + assert_equal "Hello: david", assigns(:partial_with_locals) + assert_equal "text/html", @response.content_type + end + + def test_render_to_string_with_template_and_html_partial + get :render_to_string_with_template_and_html_partial + assert_equal "**only partial**\n", assigns(:text) + assert_equal "<strong>only partial</strong>\n", assigns(:html) + assert_equal "<strong>only html partial</strong>\n", @response.body + assert_equal "text/html", @response.content_type + end + + def test_render_to_string_and_render_with_different_formats + get :render_to_string_and_render_with_different_formats + assert_equal "<strong>only partial</strong>\n", assigns(:html) + assert_equal "**only partial**\n", @response.body + assert_equal "text/plain", @response.content_type + end + + def test_render_template_within_a_template_with_other_format + get :render_template_within_a_template_with_other_format + expected = "only html partial<p>This is grand!</p>" + assert_equal expected, @response.body.strip + assert_equal "text/html", @response.content_type + end + + def test_partial_with_counter + get :partial_with_counter + assert_equal "5", @response.body + end + + def test_partial_with_locals + get :partial_with_locals + assert_equal "Hello: david", @response.body + end + + def test_partial_with_form_builder + get :partial_with_form_builder + assert_match(/<label/, @response.body) + assert_template('test/_form') + end + + def test_partial_with_form_builder_subclass + get :partial_with_form_builder_subclass + assert_match(/<label/, @response.body) + assert_template('test/_labelling_form') + end + + def test_nested_partial_with_form_builder + @controller = Fun::GamesController.new + get :nested_partial_with_form_builder + assert_match(/<label/, @response.body) + assert_template('fun/games/_form') + end + + def test_namespaced_object_partial + @controller = Quiz::QuestionsController.new + get :new + assert_equal "Namespaced Partial", @response.body + end + + def test_partial_collection + get :partial_collection + assert_equal "Hello: davidHello: mary", @response.body + end + + def test_partial_collection_with_as + get :partial_collection_with_as + assert_equal "david david davidmary mary mary", @response.body + end + + def test_partial_collection_with_counter + get :partial_collection_with_counter + assert_equal "david0mary1", @response.body + end + + def test_partial_collection_with_as_and_counter + get :partial_collection_with_as_and_counter + assert_equal "david0mary1", @response.body + end + + def test_partial_collection_with_locals + get :partial_collection_with_locals + assert_equal "Bonjour: davidBonjour: mary", @response.body + end + + def test_locals_option_to_assert_template_is_not_supported + get :partial_collection_with_locals + + warning_buffer = StringIO.new + $stderr = warning_buffer + + assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' } + assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string + ensure + $stderr = STDERR + end + + def test_partial_collection_with_spacer + get :partial_collection_with_spacer + assert_equal "Hello: davidonly partialHello: mary", @response.body + assert_template :partial => '_customer' + end + + def test_partial_collection_with_spacer_which_uses_render + get :partial_collection_with_spacer_which_uses_render + assert_equal "Hello: davidpartial html\npartial with partial\nHello: mary", @response.body + assert_template :partial => '_customer' + end + + def test_partial_collection_shorthand_with_locals + get :partial_collection_shorthand_with_locals + assert_equal "Bonjour: davidBonjour: mary", @response.body + assert_template :partial => 'customers/_customer', :count => 2 + assert_template :partial => '_completely_fake_and_made_up_template_that_cannot_possibly_be_rendered', :count => 0 + end + + def test_partial_collection_shorthand_with_different_types_of_records + get :partial_collection_shorthand_with_different_types_of_records + assert_equal "Bonjour bad customer: mark0Bonjour good customer: craig1Bonjour bad customer: john2Bonjour good customer: zach3Bonjour good customer: brandon4Bonjour bad customer: dan5", @response.body + assert_template :partial => 'good_customers/_good_customer', :count => 3 + assert_template :partial => 'bad_customers/_bad_customer', :count => 3 + end + + def test_empty_partial_collection + get :empty_partial_collection + assert_equal " ", @response.body + assert_template :partial => false + end + + def test_partial_with_hash_object + get :partial_with_hash_object + assert_equal "Sam\nmaS\n", @response.body + end + + def test_partial_with_nested_object + get :partial_with_nested_object + assert_equal "first", @response.body + end + + def test_partial_with_nested_object_shorthand + get :partial_with_nested_object_shorthand + assert_equal "first", @response.body + end + + def test_hash_partial_collection + get :partial_hash_collection + assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body + end + + def test_partial_hash_collection_with_locals + get :partial_hash_collection_with_locals + assert_equal "Hola: PratikHola: Amy", @response.body + end + + def test_render_missing_partial_template + assert_raise(ActionView::MissingTemplate) do + get :missing_partial + end + end + + def test_render_call_to_partial_with_layout + get :render_call_to_partial_with_layout + assert_equal "Before (David)\nInside from partial (David)\nAfter", @response.body + end + + def test_render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout + get :render_call_to_partial_with_layout_in_main_layout_and_within_content_for_layout + assert_equal "Before (Anthony)\nInside from partial (Anthony)\nAfter\nBefore (David)\nInside from partial (David)\nAfter\nBefore (Ramm)\nInside from partial (Ramm)\nAfter", @response.body + end +end + diff --git a/actionview/test/fixtures/actionpack/bad_customers/_bad_customer.html.erb b/actionview/test/fixtures/actionpack/bad_customers/_bad_customer.html.erb new file mode 100644 index 0000000000..d22af431ec --- /dev/null +++ b/actionview/test/fixtures/actionpack/bad_customers/_bad_customer.html.erb @@ -0,0 +1 @@ +<%= greeting %> bad customer: <%= bad_customer.name %><%= bad_customer_counter %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/customers/_customer.html.erb b/actionview/test/fixtures/actionpack/customers/_customer.html.erb index 483571e22a..483571e22a 100644 --- a/actionpack/test/fixtures/customers/_customer.html.erb +++ b/actionview/test/fixtures/actionpack/customers/_customer.html.erb diff --git a/actionpack/test/fixtures/fun/games/_form.erb b/actionview/test/fixtures/actionpack/fun/games/_form.erb index 01107f1cb2..01107f1cb2 100644 --- a/actionpack/test/fixtures/fun/games/_form.erb +++ b/actionview/test/fixtures/actionpack/fun/games/_form.erb diff --git a/actionpack/test/fixtures/fun/games/hello_world.erb b/actionview/test/fixtures/actionpack/fun/games/hello_world.erb index 1ebfbe2539..1ebfbe2539 100644 --- a/actionpack/test/fixtures/fun/games/hello_world.erb +++ b/actionview/test/fixtures/actionpack/fun/games/hello_world.erb diff --git a/actionpack/test/fixtures/good_customers/_good_customer.html.erb b/actionview/test/fixtures/actionpack/good_customers/_good_customer.html.erb index a2d97ebc6d..a2d97ebc6d 100644 --- a/actionpack/test/fixtures/good_customers/_good_customer.html.erb +++ b/actionview/test/fixtures/actionpack/good_customers/_good_customer.html.erb diff --git a/actionpack/test/fixtures/hello.html b/actionview/test/fixtures/actionpack/hello.html index 6769dd60bd..6769dd60bd 100644 --- a/actionpack/test/fixtures/hello.html +++ b/actionview/test/fixtures/actionpack/hello.html diff --git a/actionpack/test/fixtures/layout_tests/alt/layouts/alt.erb b/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb index e69de29bb2..e69de29bb2 100644 --- a/actionpack/test/fixtures/layout_tests/alt/layouts/alt.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/alt/layouts/alt.erb diff --git a/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.erb b/actionview/test/fixtures/actionpack/layout_tests/layouts/controller_name_space/nested.erb index 121bc079a1..121bc079a1 100644 --- a/actionpack/test/fixtures/layout_tests/layouts/controller_name_space/nested.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/controller_name_space/nested.erb diff --git a/actionpack/test/fixtures/layout_tests/layouts/item.erb b/actionview/test/fixtures/actionpack/layout_tests/layouts/item.erb index 60f04d77d5..60f04d77d5 100644 --- a/actionpack/test/fixtures/layout_tests/layouts/item.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/item.erb diff --git a/actionpack/test/fixtures/layout_tests/layouts/layout_test.erb b/actionview/test/fixtures/actionpack/layout_tests/layouts/layout_test.erb index b74ac0840d..b74ac0840d 100644 --- a/actionpack/test/fixtures/layout_tests/layouts/layout_test.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/layout_test.erb diff --git a/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb b/actionview/test/fixtures/actionpack/layout_tests/layouts/multiple_extensions.html.erb index 3b65e54f9c..3b65e54f9c 100644 --- a/actionpack/test/fixtures/layout_tests/layouts/multiple_extensions.html.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/multiple_extensions.html.erb diff --git a/actionview/test/fixtures/actionpack/layout_tests/layouts/symlinked/symlinked_layout.erb b/actionview/test/fixtures/actionpack/layout_tests/layouts/symlinked/symlinked_layout.erb new file mode 100644 index 0000000000..bda57d0fae --- /dev/null +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/symlinked/symlinked_layout.erb @@ -0,0 +1,5 @@ +This is my layout + +<%= yield %> + +End. diff --git a/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab b/actionview/test/fixtures/actionpack/layout_tests/layouts/third_party_template_library.mab index fcee620d82..fcee620d82 100644 --- a/actionpack/test/fixtures/layout_tests/layouts/third_party_template_library.mab +++ b/actionview/test/fixtures/actionpack/layout_tests/layouts/third_party_template_library.mab diff --git a/actionpack/test/fixtures/layout_tests/views/goodbye.erb b/actionview/test/fixtures/actionpack/layout_tests/views/goodbye.erb index 4ee911188e..4ee911188e 100644 --- a/actionpack/test/fixtures/layout_tests/views/goodbye.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/views/goodbye.erb diff --git a/actionpack/test/fixtures/layout_tests/views/hello.erb b/actionview/test/fixtures/actionpack/layout_tests/views/hello.erb index 4ee911188e..4ee911188e 100644 --- a/actionpack/test/fixtures/layout_tests/views/hello.erb +++ b/actionview/test/fixtures/actionpack/layout_tests/views/hello.erb diff --git a/actionview/test/fixtures/actionpack/layouts/_column.html.erb b/actionview/test/fixtures/actionpack/layouts/_column.html.erb new file mode 100644 index 0000000000..96db002b8a --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/_column.html.erb @@ -0,0 +1,2 @@ +<div id="column"><%= yield :column %></div> +<div id="content"><%= yield %></div>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/_customers.erb b/actionview/test/fixtures/actionpack/layouts/_customers.erb new file mode 100644 index 0000000000..ae63f13cd3 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/_customers.erb @@ -0,0 +1 @@ +<title><%= yield Struct.new(:name).new("David") %></title>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/_partial_and_yield.erb b/actionview/test/fixtures/actionpack/layouts/_partial_and_yield.erb new file mode 100644 index 0000000000..74cc428ffa --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/_partial_and_yield.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'test/partial' %> +<%= yield %> diff --git a/actionview/test/fixtures/actionpack/layouts/_yield_only.erb b/actionview/test/fixtures/actionpack/layouts/_yield_only.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/_yield_only.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/actionview/test/fixtures/actionpack/layouts/_yield_with_params.erb b/actionview/test/fixtures/actionpack/layouts/_yield_with_params.erb new file mode 100644 index 0000000000..68e6557fb8 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/_yield_with_params.erb @@ -0,0 +1 @@ +<%= yield 'Yield!' %> diff --git a/actionview/test/fixtures/actionpack/layouts/block_with_layout.erb b/actionview/test/fixtures/actionpack/layouts/block_with_layout.erb new file mode 100644 index 0000000000..73ac833e52 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/block_with_layout.erb @@ -0,0 +1,3 @@ +<%= render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% "Return value should be discarded" %><% end %> +<%= yield %> +<%= render(:layout => "layout_for_partial", :locals => { :name => "Ramm" }) do %>Inside from second block in layout<% end %> diff --git a/actionview/test/fixtures/actionpack/layouts/builder.builder b/actionview/test/fixtures/actionpack/layouts/builder.builder new file mode 100644 index 0000000000..7c7d4b2dd1 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/builder.builder @@ -0,0 +1,3 @@ +xml.wrapper do + xml << yield +end
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/partial_with_layout.erb b/actionview/test/fixtures/actionpack/layouts/partial_with_layout.erb new file mode 100644 index 0000000000..a0349d731e --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/partial_with_layout.erb @@ -0,0 +1,3 @@ +<%= render( :layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => {:name => 'Anthony' } ) %> +<%= yield %> +<%= render( :layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => {:name => 'Ramm' } ) %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/standard.html.erb b/actionview/test/fixtures/actionpack/layouts/standard.html.erb new file mode 100644 index 0000000000..5e6c24fe39 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/standard.html.erb @@ -0,0 +1 @@ +<html><%= yield %><%= @variable_for_layout %></html>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/streaming.erb b/actionview/test/fixtures/actionpack/layouts/streaming.erb new file mode 100644 index 0000000000..d3f896a6ca --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/streaming.erb @@ -0,0 +1,4 @@ +<%= yield :header -%> +<%= yield -%> +<%= yield :footer -%> +<%= yield(:unknown).presence || "." -%>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/talk_from_action.erb b/actionview/test/fixtures/actionpack/layouts/talk_from_action.erb new file mode 100644 index 0000000000..bf53fdb785 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/talk_from_action.erb @@ -0,0 +1,2 @@ +<title><%= @title || yield(:title) %></title> +<%= yield -%>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/with_html_partial.html.erb b/actionview/test/fixtures/actionpack/layouts/with_html_partial.html.erb new file mode 100644 index 0000000000..fd2896aeaa --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/with_html_partial.html.erb @@ -0,0 +1 @@ +<%= render :partial => "partial_only_html" %><%= yield %> diff --git a/actionview/test/fixtures/actionpack/layouts/xhr.html.erb b/actionview/test/fixtures/actionpack/layouts/xhr.html.erb new file mode 100644 index 0000000000..85285324ec --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/xhr.html.erb @@ -0,0 +1,2 @@ +XHR! +<%= yield %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/layouts/yield.erb b/actionview/test/fixtures/actionpack/layouts/yield.erb new file mode 100644 index 0000000000..482dc9022e --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/yield.erb @@ -0,0 +1,2 @@ +<title><%= yield :title %></title> +<%= yield %> diff --git a/actionview/test/fixtures/actionpack/layouts/yield_with_render_inline_inside.erb b/actionview/test/fixtures/actionpack/layouts/yield_with_render_inline_inside.erb new file mode 100644 index 0000000000..7298d79690 --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/yield_with_render_inline_inside.erb @@ -0,0 +1,2 @@ +<%= render :inline => 'welcome' %> +<%= yield %> diff --git a/actionview/test/fixtures/actionpack/layouts/yield_with_render_partial_inside.erb b/actionview/test/fixtures/actionpack/layouts/yield_with_render_partial_inside.erb new file mode 100644 index 0000000000..74cc428ffa --- /dev/null +++ b/actionview/test/fixtures/actionpack/layouts/yield_with_render_partial_inside.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'test/partial' %> +<%= yield %> diff --git a/actionpack/test/fixtures/quiz/questions/_question.html.erb b/actionview/test/fixtures/actionpack/quiz/questions/_question.html.erb index fb4dcfee64..fb4dcfee64 100644 --- a/actionpack/test/fixtures/quiz/questions/_question.html.erb +++ b/actionview/test/fixtures/actionpack/quiz/questions/_question.html.erb diff --git a/actionview/test/fixtures/actionpack/shared.html.erb b/actionview/test/fixtures/actionpack/shared.html.erb new file mode 100644 index 0000000000..af262fc9f8 --- /dev/null +++ b/actionview/test/fixtures/actionpack/shared.html.erb @@ -0,0 +1 @@ +Elastica
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_changing_priority.html.erb b/actionview/test/fixtures/actionpack/test/_changing_priority.html.erb new file mode 100644 index 0000000000..3225efc49a --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_changing_priority.html.erb @@ -0,0 +1 @@ +HTML
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_changing_priority.json.erb b/actionview/test/fixtures/actionpack/test/_changing_priority.json.erb new file mode 100644 index 0000000000..7fa41dce66 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_changing_priority.json.erb @@ -0,0 +1 @@ +JSON
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_counter.html.erb b/actionview/test/fixtures/actionpack/test/_counter.html.erb new file mode 100644 index 0000000000..fd245bfc70 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_counter.html.erb @@ -0,0 +1 @@ +<%= counter_counter %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_customer.erb b/actionview/test/fixtures/actionpack/test/_customer.erb new file mode 100644 index 0000000000..d8220afeda --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_customer.erb @@ -0,0 +1 @@ +Hello: <%= customer.name rescue "Anonymous" %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_customer_counter.erb b/actionview/test/fixtures/actionpack/test/_customer_counter.erb new file mode 100644 index 0000000000..3435979dba --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_customer_counter.erb @@ -0,0 +1 @@ +<%= customer_counter.name %><%= customer_counter_counter %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_customer_counter_with_as.erb b/actionview/test/fixtures/actionpack/test/_customer_counter_with_as.erb new file mode 100644 index 0000000000..1241eb604d --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_customer_counter_with_as.erb @@ -0,0 +1 @@ +<%= client.name %><%= client_counter %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_customer_greeting.erb b/actionview/test/fixtures/actionpack/test/_customer_greeting.erb new file mode 100644 index 0000000000..6acbcb20c4 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_customer_greeting.erb @@ -0,0 +1 @@ +<%= greeting %>: <%= customer_greeting.name %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_customer_with_var.erb b/actionview/test/fixtures/actionpack/test/_customer_with_var.erb new file mode 100644 index 0000000000..00047dd20e --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_customer_with_var.erb @@ -0,0 +1 @@ +<%= customer.name %> <%= customer.name %> <%= customer.name %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_directory/_partial_with_locales.html.erb b/actionview/test/fixtures/actionpack/test/_directory/_partial_with_locales.html.erb new file mode 100644 index 0000000000..1cc8d41475 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_directory/_partial_with_locales.html.erb @@ -0,0 +1 @@ +Hello <%= name %> diff --git a/actionview/test/fixtures/actionpack/test/_first_json_partial.json.erb b/actionview/test/fixtures/actionpack/test/_first_json_partial.json.erb new file mode 100644 index 0000000000..790ee896db --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_first_json_partial.json.erb @@ -0,0 +1 @@ +<%= render :partial => "test/second_json_partial" %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_form.erb b/actionview/test/fixtures/actionpack/test/_form.erb new file mode 100644 index 0000000000..01107f1cb2 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_form.erb @@ -0,0 +1 @@ +<%= form.label :title %> diff --git a/actionview/test/fixtures/actionpack/test/_hash_greeting.erb b/actionview/test/fixtures/actionpack/test/_hash_greeting.erb new file mode 100644 index 0000000000..fc54a36f2a --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_hash_greeting.erb @@ -0,0 +1 @@ +<%= greeting %>: <%= hash_greeting[:first_name] %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_hash_object.erb b/actionview/test/fixtures/actionpack/test/_hash_object.erb new file mode 100644 index 0000000000..34a92c6a56 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_hash_object.erb @@ -0,0 +1,2 @@ +<%= hash_object[:first_name] %> +<%= hash_object[:first_name].reverse %> diff --git a/actionview/test/fixtures/actionpack/test/_hello.builder b/actionview/test/fixtures/actionpack/test/_hello.builder new file mode 100644 index 0000000000..ef52f632d1 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_hello.builder @@ -0,0 +1 @@ +xm.hello
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_json_change_priority.json.erb b/actionview/test/fixtures/actionpack/test/_json_change_priority.json.erb new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_json_change_priority.json.erb diff --git a/actionview/test/fixtures/actionpack/test/_labelling_form.erb b/actionview/test/fixtures/actionpack/test/_labelling_form.erb new file mode 100644 index 0000000000..1b95763165 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_labelling_form.erb @@ -0,0 +1 @@ +<%= labelling_form.label :title %> diff --git a/actionview/test/fixtures/actionpack/test/_layout_for_partial.html.erb b/actionview/test/fixtures/actionpack/test/_layout_for_partial.html.erb new file mode 100644 index 0000000000..666efadbb6 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_layout_for_partial.html.erb @@ -0,0 +1,3 @@ +Before (<%= name %>) +<%= yield %> +After
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial.erb b/actionview/test/fixtures/actionpack/test/_partial.erb new file mode 100644 index 0000000000..e466dcbd8e --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial.erb @@ -0,0 +1 @@ +invalid
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial.html.erb b/actionview/test/fixtures/actionpack/test/_partial.html.erb new file mode 100644 index 0000000000..e39f6c9827 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial.html.erb @@ -0,0 +1 @@ +partial html
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial.js.erb b/actionview/test/fixtures/actionpack/test/_partial.js.erb new file mode 100644 index 0000000000..b350cdd7ef --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial.js.erb @@ -0,0 +1 @@ +partial js
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial_for_use_in_layout.html.erb b/actionview/test/fixtures/actionpack/test/_partial_for_use_in_layout.html.erb new file mode 100644 index 0000000000..3a03a64e31 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_for_use_in_layout.html.erb @@ -0,0 +1 @@ +Inside from partial (<%= name %>)
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial_html_erb.html.erb b/actionview/test/fixtures/actionpack/test/_partial_html_erb.html.erb new file mode 100644 index 0000000000..4b54875782 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_html_erb.html.erb @@ -0,0 +1 @@ +<%= "partial.html.erb" %> diff --git a/actionview/test/fixtures/actionpack/test/_partial_name_local_variable.erb b/actionview/test/fixtures/actionpack/test/_partial_name_local_variable.erb new file mode 100644 index 0000000000..cc3a91c89f --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_name_local_variable.erb @@ -0,0 +1 @@ +<%= partial_name_local_variable %> diff --git a/actionview/test/fixtures/actionpack/test/_partial_only.erb b/actionview/test/fixtures/actionpack/test/_partial_only.erb new file mode 100644 index 0000000000..a44b3eed40 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_only.erb @@ -0,0 +1 @@ +only partial
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial_only_html.html b/actionview/test/fixtures/actionpack/test/_partial_only_html.html new file mode 100644 index 0000000000..d2d630bd40 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_only_html.html @@ -0,0 +1 @@ +only html partial
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_partial_with_partial.erb b/actionview/test/fixtures/actionpack/test/_partial_with_partial.erb new file mode 100644 index 0000000000..ee0d5037b6 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_partial_with_partial.erb @@ -0,0 +1,2 @@ +<%= render 'test/partial' %> +partial with partial diff --git a/actionview/test/fixtures/actionpack/test/_person.erb b/actionview/test/fixtures/actionpack/test/_person.erb new file mode 100644 index 0000000000..b2e5688956 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_person.erb @@ -0,0 +1,2 @@ +Second: <%= name %> +Third: <%= @name %> diff --git a/actionview/test/fixtures/actionpack/test/_raise_indentation.html.erb b/actionview/test/fixtures/actionpack/test/_raise_indentation.html.erb new file mode 100644 index 0000000000..f9a93728fe --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_raise_indentation.html.erb @@ -0,0 +1,13 @@ +<p>First paragraph</p> +<p>Second paragraph</p> +<p>Third paragraph</p> +<p>Fourth paragraph</p> +<p>Fifth paragraph</p> +<p>Sixth paragraph</p> +<p>Seventh paragraph</p> +<p>Eight paragraph</p> +<p>Ninth paragraph</p> +<p>Tenth paragraph</p> +<%= raise "error here!" %> +<p>Eleventh paragraph</p> +<p>Twelfth paragraph</p>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/_second_json_partial.json.erb b/actionview/test/fixtures/actionpack/test/_second_json_partial.json.erb new file mode 100644 index 0000000000..5ebb7f1afd --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/_second_json_partial.json.erb @@ -0,0 +1 @@ +Third level
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/action_talk_to_layout.erb b/actionview/test/fixtures/actionpack/test/action_talk_to_layout.erb new file mode 100644 index 0000000000..36e896daa8 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/action_talk_to_layout.erb @@ -0,0 +1,2 @@ +<% @title = "Talking to the layout" -%> +Action was here!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/calling_partial_with_layout.html.erb b/actionview/test/fixtures/actionpack/test/calling_partial_with_layout.html.erb new file mode 100644 index 0000000000..ac44bc0d81 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/calling_partial_with_layout.html.erb @@ -0,0 +1 @@ +<%= render(:layout => "layout_for_partial", :partial => "partial_for_use_in_layout", :locals => { :name => "David" }) %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/capturing.erb b/actionview/test/fixtures/actionpack/test/capturing.erb new file mode 100644 index 0000000000..1addaa40d9 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/capturing.erb @@ -0,0 +1,4 @@ +<% days = capture do %> + Dreamy days +<% end %> +<%= days %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/change_priority.html.erb b/actionview/test/fixtures/actionpack/test/change_priority.html.erb new file mode 100644 index 0000000000..5618977d05 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/change_priority.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => "test/json_change_priority", formats: :json %> +HTML Template, but <%= render :partial => "test/changing_priority" %> partial
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/content_for.erb b/actionview/test/fixtures/actionpack/test/content_for.erb new file mode 100644 index 0000000000..1fb829f54c --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/content_for.erb @@ -0,0 +1 @@ +<% content_for :title do -%>Putting stuff in the title!<% end -%>Great stuff!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/content_for_concatenated.erb b/actionview/test/fixtures/actionpack/test/content_for_concatenated.erb new file mode 100644 index 0000000000..e65f629574 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/content_for_concatenated.erb @@ -0,0 +1,3 @@ +<% content_for :title, "Putting stuff " + content_for :title, "in the title!" -%> +Great stuff!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/content_for_with_parameter.erb b/actionview/test/fixtures/actionpack/test/content_for_with_parameter.erb new file mode 100644 index 0000000000..aeb6f73ce0 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/content_for_with_parameter.erb @@ -0,0 +1,2 @@ +<% content_for :title, "Putting stuff in the title!" -%> +Great stuff!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/dot.directory/render_file_with_ivar.erb b/actionview/test/fixtures/actionpack/test/dot.directory/render_file_with_ivar.erb new file mode 100644 index 0000000000..8b8a449236 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/dot.directory/render_file_with_ivar.erb @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/actionview/test/fixtures/actionpack/test/formatted_html_erb.html.erb b/actionview/test/fixtures/actionpack/test/formatted_html_erb.html.erb new file mode 100644 index 0000000000..1c64efabd8 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/formatted_html_erb.html.erb @@ -0,0 +1 @@ +formatted html erb
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder new file mode 100644 index 0000000000..14fd3549fb --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.builder @@ -0,0 +1 @@ +xml.test 'failed'
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.html.erb b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.html.erb new file mode 100644 index 0000000000..0c855a604b --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.html.erb @@ -0,0 +1 @@ +<test>passed formatted html erb</test>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/formatted_xml_erb.xml.erb b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.xml.erb new file mode 100644 index 0000000000..6ca09d5304 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/formatted_xml_erb.xml.erb @@ -0,0 +1 @@ +<test>passed formatted xml erb</test>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/greeting.html.erb b/actionview/test/fixtures/actionpack/test/greeting.html.erb new file mode 100644 index 0000000000..62fb0293f0 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/greeting.html.erb @@ -0,0 +1 @@ +<p>This is grand!</p> diff --git a/actionview/test/fixtures/actionpack/test/greeting.xml.erb b/actionview/test/fixtures/actionpack/test/greeting.xml.erb new file mode 100644 index 0000000000..62fb0293f0 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/greeting.xml.erb @@ -0,0 +1 @@ +<p>This is grand!</p> diff --git a/actionview/test/fixtures/actionpack/test/hello,world.erb b/actionview/test/fixtures/actionpack/test/hello,world.erb new file mode 100644 index 0000000000..bc8fa5e0ca --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello,world.erb @@ -0,0 +1 @@ +Hello w*rld!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello.builder b/actionview/test/fixtures/actionpack/test/hello.builder new file mode 100644 index 0000000000..a471553941 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello.builder @@ -0,0 +1,4 @@ +xml.html do + xml.p "Hello #{@name}" + xml << render(:file => "test/greeting") +end
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello/hello.erb b/actionview/test/fixtures/actionpack/test/hello/hello.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello/hello.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello_world.erb b/actionview/test/fixtures/actionpack/test/hello_world.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_world.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello_world_container.builder b/actionview/test/fixtures/actionpack/test/hello_world_container.builder new file mode 100644 index 0000000000..e48d75c405 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_world_container.builder @@ -0,0 +1,3 @@ +xml.test do + render :partial => 'hello', :locals => { :xm => xml } +end
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello_world_from_rxml.builder b/actionview/test/fixtures/actionpack/test/hello_world_from_rxml.builder new file mode 100644 index 0000000000..619a97ba96 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_world_from_rxml.builder @@ -0,0 +1,3 @@ +xml.html do + xml.p "Hello" +end diff --git a/actionview/test/fixtures/actionpack/test/hello_world_with_layout_false.erb b/actionview/test/fixtures/actionpack/test/hello_world_with_layout_false.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_world_with_layout_false.erb @@ -0,0 +1 @@ +Hello world!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hello_world_with_partial.html.erb b/actionview/test/fixtures/actionpack/test/hello_world_with_partial.html.erb new file mode 100644 index 0000000000..ec31545356 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_world_with_partial.html.erb @@ -0,0 +1,2 @@ +Hello world! +<%= render '/test/partial' %> diff --git a/actionview/test/fixtures/actionpack/test/hello_xml_world.builder b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder new file mode 100644 index 0000000000..e7081b89fe --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hello_xml_world.builder @@ -0,0 +1,11 @@ +xml.html do + xml.head do + xml.title "Hello World" + end + + xml.body do + xml.p "abes" + xml.p "monks" + xml.p "wiseguys" + end +end
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/html_template.html.erb b/actionview/test/fixtures/actionpack/test/html_template.html.erb new file mode 100644 index 0000000000..1bbc2b7f09 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/html_template.html.erb @@ -0,0 +1 @@ +<%= render :partial => "test/first_json_partial", formats: :json %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/hyphen-ated.erb b/actionview/test/fixtures/actionpack/test/hyphen-ated.erb new file mode 100644 index 0000000000..cd0875583a --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/hyphen-ated.erb @@ -0,0 +1 @@ +Hello world! diff --git a/actionview/test/fixtures/actionpack/test/implicit_content_type.atom.builder b/actionview/test/fixtures/actionpack/test/implicit_content_type.atom.builder new file mode 100644 index 0000000000..2fcb32d247 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/implicit_content_type.atom.builder @@ -0,0 +1,2 @@ +xml.atom do +end diff --git a/actionview/test/fixtures/actionpack/test/list.erb b/actionview/test/fixtures/actionpack/test/list.erb new file mode 100644 index 0000000000..0a4bda58ee --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/list.erb @@ -0,0 +1 @@ +<%= @test_unchanged = 'goodbye' %><%= render :partial => 'customer', :collection => @customers %><%= @test_unchanged %> diff --git a/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder new file mode 100644 index 0000000000..d539a425a4 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/non_erb_block_content_for.builder @@ -0,0 +1,4 @@ +content_for :title do + 'Putting stuff in the title!' +end +xml << "Great stuff!" diff --git a/actionview/test/fixtures/actionpack/test/potential_conflicts.erb b/actionview/test/fixtures/actionpack/test/potential_conflicts.erb new file mode 100644 index 0000000000..a5e964e359 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/potential_conflicts.erb @@ -0,0 +1,4 @@ +First: <%= @name %> +<%= render :partial => "person", :locals => { :name => "Stephan" } -%> +Fourth: <%= @name %> +Fifth: <%= name %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/proper_block_detection.erb b/actionview/test/fixtures/actionpack/test/proper_block_detection.erb new file mode 100644 index 0000000000..b55efbb25d --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/proper_block_detection.erb @@ -0,0 +1 @@ +<%= @todo %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/render_file_from_template.html.erb b/actionview/test/fixtures/actionpack/test/render_file_from_template.html.erb new file mode 100644 index 0000000000..fde9f4bb64 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_file_from_template.html.erb @@ -0,0 +1 @@ +<%= render :file => @path %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/render_file_with_ivar.erb b/actionview/test/fixtures/actionpack/test/render_file_with_ivar.erb new file mode 100644 index 0000000000..8b8a449236 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_file_with_ivar.erb @@ -0,0 +1 @@ +The secret is <%= @secret %> diff --git a/actionview/test/fixtures/actionpack/test/render_file_with_locals.erb b/actionview/test/fixtures/actionpack/test/render_file_with_locals.erb new file mode 100644 index 0000000000..ebe09faee6 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_file_with_locals.erb @@ -0,0 +1 @@ +The secret is <%= secret %> diff --git a/actionview/test/fixtures/actionpack/test/render_file_with_locals_and_default.erb b/actionview/test/fixtures/actionpack/test/render_file_with_locals_and_default.erb new file mode 100644 index 0000000000..9b4900acc5 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_file_with_locals_and_default.erb @@ -0,0 +1 @@ +<%= secret ||= 'one' %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.da.html.erb b/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.da.html.erb new file mode 100644 index 0000000000..0740b2d07c --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.da.html.erb @@ -0,0 +1 @@ +Hey HTML! diff --git a/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.html.erb b/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.html.erb new file mode 100644 index 0000000000..4a11845cfe --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_implicit_html_template_from_xhr_request.html.erb @@ -0,0 +1 @@ +Hello HTML!
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/render_implicit_js_template_without_layout.js.erb b/actionview/test/fixtures/actionpack/test/render_implicit_js_template_without_layout.js.erb new file mode 100644 index 0000000000..892ae5eca2 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_implicit_js_template_without_layout.js.erb @@ -0,0 +1 @@ +alert('hello');
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/render_partial_inside_directory.html.erb b/actionview/test/fixtures/actionpack/test/render_partial_inside_directory.html.erb new file mode 100644 index 0000000000..1461b95186 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_partial_inside_directory.html.erb @@ -0,0 +1 @@ +<%= render partial: 'test/_directory/partial_with_locales', locals: {'name' => 'Jane'} %> diff --git a/actionview/test/fixtures/actionpack/test/render_to_string_test.erb b/actionview/test/fixtures/actionpack/test/render_to_string_test.erb new file mode 100644 index 0000000000..6e267e8634 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_to_string_test.erb @@ -0,0 +1 @@ +The value of foo is: ::<%= @foo %>:: diff --git a/actionview/test/fixtures/actionpack/test/render_two_partials.html.erb b/actionview/test/fixtures/actionpack/test/render_two_partials.html.erb new file mode 100644 index 0000000000..3db6025860 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/render_two_partials.html.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'partial', :locals => {'first' => '1'} %> +<%= render :partial => 'partial', :locals => {'second' => '2'} %> diff --git a/actionview/test/fixtures/actionpack/test/using_layout_around_block.html.erb b/actionview/test/fixtures/actionpack/test/using_layout_around_block.html.erb new file mode 100644 index 0000000000..3d6661df9a --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/using_layout_around_block.html.erb @@ -0,0 +1 @@ +<%= render(:layout => "layout_for_partial", :locals => { :name => "David" }) do %>Inside from block<% end %>
\ No newline at end of file diff --git a/actionview/test/fixtures/actionpack/test/with_html_partial.html.erb b/actionview/test/fixtures/actionpack/test/with_html_partial.html.erb new file mode 100644 index 0000000000..d84d909d64 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/with_html_partial.html.erb @@ -0,0 +1 @@ +<strong><%= render :partial => "partial_only_html" %></strong> diff --git a/actionview/test/fixtures/actionpack/test/with_partial.html.erb b/actionview/test/fixtures/actionpack/test/with_partial.html.erb new file mode 100644 index 0000000000..7502364cf5 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/with_partial.html.erb @@ -0,0 +1 @@ +<strong><%= render :partial => "partial_only" %></strong> diff --git a/actionview/test/fixtures/actionpack/test/with_partial.text.erb b/actionview/test/fixtures/actionpack/test/with_partial.text.erb new file mode 100644 index 0000000000..5f068ebf27 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/with_partial.text.erb @@ -0,0 +1 @@ +**<%= render :partial => "partial_only" %>** diff --git a/actionview/test/fixtures/actionpack/test/with_xml_template.html.erb b/actionview/test/fixtures/actionpack/test/with_xml_template.html.erb new file mode 100644 index 0000000000..e54a7cd001 --- /dev/null +++ b/actionview/test/fixtures/actionpack/test/with_xml_template.html.erb @@ -0,0 +1 @@ +<%= render :template => "test/greeting", :formats => :xml %> diff --git a/actionview/test/fixtures/helpers/abc_helper.rb b/actionview/test/fixtures/helpers/abc_helper.rb new file mode 100644 index 0000000000..cf2774bb5f --- /dev/null +++ b/actionview/test/fixtures/helpers/abc_helper.rb @@ -0,0 +1,3 @@ +module AbcHelper + def bare_a() end +end diff --git a/actionview/test/fixtures/helpers/fun/games_helper.rb b/actionview/test/fixtures/helpers/fun/games_helper.rb new file mode 100644 index 0000000000..3b7adce086 --- /dev/null +++ b/actionview/test/fixtures/helpers/fun/games_helper.rb @@ -0,0 +1,5 @@ +module Fun + module GamesHelper + def stratego() "Iz guuut!" end + end +end
\ No newline at end of file diff --git a/actionview/test/fixtures/helpers/fun/pdf_helper.rb b/actionview/test/fixtures/helpers/fun/pdf_helper.rb new file mode 100644 index 0000000000..0171be8500 --- /dev/null +++ b/actionview/test/fixtures/helpers/fun/pdf_helper.rb @@ -0,0 +1,5 @@ +module Fun + module PdfHelper + def foobar() 'baz' end + end +end diff --git a/actionview/test/fixtures/helpers/helpery_test_helper.rb b/actionview/test/fixtures/helpers/helpery_test_helper.rb new file mode 100644 index 0000000000..a4f2951efa --- /dev/null +++ b/actionview/test/fixtures/helpers/helpery_test_helper.rb @@ -0,0 +1,5 @@ +module HelperyTestHelper + def helpery_test + "Default" + end +end diff --git a/actionview/test/fixtures/helpers/just_me_helper.rb b/actionview/test/fixtures/helpers/just_me_helper.rb new file mode 100644 index 0000000000..b140a7b9b4 --- /dev/null +++ b/actionview/test/fixtures/helpers/just_me_helper.rb @@ -0,0 +1,3 @@ +module JustMeHelper + def me() "mine!" end +end
\ No newline at end of file diff --git a/actionview/test/fixtures/helpers/me_too_helper.rb b/actionview/test/fixtures/helpers/me_too_helper.rb new file mode 100644 index 0000000000..ce56042143 --- /dev/null +++ b/actionview/test/fixtures/helpers/me_too_helper.rb @@ -0,0 +1,3 @@ +module MeTooHelper + def me() "me too!" end +end
\ No newline at end of file diff --git a/actionpack/test/fixtures/helpers_missing/invalid_require_helper.rb b/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb index d8801e54d5..d8801e54d5 100644 --- a/actionpack/test/fixtures/helpers_missing/invalid_require_helper.rb +++ b/actionview/test/fixtures/helpers_missing/invalid_require_helper.rb diff --git a/actionpack/test/fixtures/override/test/hello_world.erb b/actionview/test/fixtures/override/test/hello_world.erb index 3e308d3d86..3e308d3d86 100644 --- a/actionpack/test/fixtures/override/test/hello_world.erb +++ b/actionview/test/fixtures/override/test/hello_world.erb diff --git a/actionpack/test/fixtures/override2/layouts/test/sub.erb b/actionview/test/fixtures/override2/layouts/test/sub.erb index 3863d5a8ef..3863d5a8ef 100644 --- a/actionpack/test/fixtures/override2/layouts/test/sub.erb +++ b/actionview/test/fixtures/override2/layouts/test/sub.erb diff --git a/actionpack/test/controller/view_paths_test.rb b/actionview/test/lib/controller/view_paths_test.rb index c6e7a523b9..c6e7a523b9 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionview/test/lib/controller/view_paths_test.rb diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index 67e3775f28..c6608e214a 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -184,6 +184,15 @@ class TemplateDigestorTest < ActionView::TestCase assert_not_equal digest_phone, digest_fridge_phone end + def test_cache_template_loading + resolver_before = ActionView::Resolver.caching + ActionView::Resolver.caching = false + assert_digest_difference("messages/edit", true) do + change_template("comments/_comment") + end + ActionView::Resolver.caching = resolver_before + end + private def assert_logged(message) old_logger = ActionView::Base.logger @@ -200,9 +209,9 @@ class TemplateDigestorTest < ActionView::TestCase end end - def assert_digest_difference(template_name) + def assert_digest_difference(template_name, persistent = false) previous_digest = digest(template_name) - ActionView::Digestor.cache.clear + ActionView::Digestor.cache.clear unless persistent yield diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index bc9c21dfd3..b56847396d 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -17,14 +17,14 @@ class FormCollectionsHelperTest < ActionView::TestCase end # COLLECTION RADIO BUTTONS - test 'collection radio accepts a collection and generate inputs from value method' do + test 'collection radio accepts a collection and generates inputs from value method' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s assert_select 'input[type=radio][value=true]#user_active_true' assert_select 'input[type=radio][value=false]#user_active_false' end - test 'collection radio accepts a collection and generate inputs from label method' do + test 'collection radio accepts a collection and generates inputs from label method' do with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s assert_select 'label[for=user_active_true]', 'true' @@ -38,7 +38,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=user_active_no]', 'No' end - test 'colection radio should sanitize collection values for labels correctly' do + test 'collection radio should sanitize collection values for labels correctly' do with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s assert_select 'label[for=user_name_099]', '$0.99' assert_select 'label[for=user_name_199]', '$1.99' @@ -194,7 +194,7 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=user_active_no]', 'No' end - test 'colection check box should sanitize collection values for labels correctly' do + test 'collection check box should sanitize collection values for labels correctly' do with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s assert_select 'label[for=user_name_099]', '$0.99' assert_select 'label[for=user_name_199]', '$1.99' diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 8cca43d7ca..944884c9dd 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1681,6 +1681,18 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_namespace_and_as_option + form_for(@post, namespace: 'namespace', as: 'custom_name') do |f| + concat f.text_field(:title) + end + + expected = whole_form('/posts/123', 'namespace_edit_custom_name', 'edit_custom_name', method: 'patch') do + "<input id='namespace_custom_name_title' name='custom_name[title]' type='text' value='Hello World' />" + end + + assert_dom_equal expected, output_buffer + end + def test_two_form_for_with_namespace form_for(@post, namespace: 'namespace_1') do |f| concat f.label(:title) diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index 073bd14783..203ad6d910 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -68,7 +68,7 @@ class LookupContextTest < ActiveSupport::TestCase test "delegates changing the locale to the I18n configuration object if it contains a lookup_context object" do begin - I18n.config = AbstractController::I18nProxy.new(I18n.config, @lookup_context) + I18n.config = ActionView::I18nProxy.new(I18n.config, @lookup_context) @lookup_context.locale = :pt assert_equal :pt, I18n.locale assert_equal :pt, @lookup_context.locale diff --git a/actionview/test/template/resolver_patterns_test.rb b/actionview/test/template/resolver_patterns_test.rb index 97b1bad055..575eb9bd28 100644 --- a/actionview/test/template/resolver_patterns_test.rb +++ b/actionview/test/template/resolver_patterns_test.rb @@ -20,7 +20,7 @@ class ResolverPatternsTest < ActiveSupport::TestCase assert_equal [:html], templates.first.formats end - def test_should_return_all_templates_when_ambigous_pattern + def test_should_return_all_templates_when_ambiguous_pattern templates = @resolver.find_all("another", "custom_pattern", false, {:locale => [], :formats => [:html], :handlers => [:erb]}) assert_equal 2, templates.size, "expected two templates" assert_equal "Another template!", templates[0].source diff --git a/actionview/test/template/text_helper_test.rb b/actionview/test/template/text_helper_test.rb index 1b2234f4e2..c2999fcb85 100644 --- a/actionview/test/template/text_helper_test.rb +++ b/actionview/test/template/text_helper_test.rb @@ -314,6 +314,9 @@ class TextHelperTest < ActionView::TestCase options = { :separator => "\n", :radius => 1 } assert_equal("...very\nvery long\nstring", excerpt("my very\nvery\nvery long\nstring", 'long', options)) + + assert_equal excerpt('This is a beautiful morning', 'a'), + excerpt('This is a beautiful morning', 'a', separator: nil) end def test_word_wrap diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index d512fa9913..e3440915a4 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'abstract_unit' +require 'minitest/mock' class UrlHelperTest < ActiveSupport::TestCase diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index cc9483e67b..8b9ac97bbb 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -2,7 +2,9 @@ module ActiveModel module SecurePassword extend ActiveSupport::Concern - class << self; attr_accessor :min_cost; end + class << self + attr_accessor :min_cost # :nodoc: + end self.min_cost = false module ClassMethods diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index f0347081ee..bc185c737f 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -155,12 +155,6 @@ class JsonSerializationTest < ActiveModel::TestCase end end - test "as_json should keep the default order in the hash" do - json = @contact.as_json - - assert_equal %w(name age created_at awesome preferences), json.keys - end - test "from_json should work without a root (class attribute)" do json = @contact.to_json result = Contact.new.from_json(json) @@ -204,7 +198,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{"preferences":}, json end - test "custom as_json options should be extendible" do + test "custom as_json options should be extensible" do def @contact.as_json(options = {}); super(options.merge(only: [:name])); end json = @contact.to_json diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index c4cfb0c255..11ee17bb27 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -53,8 +53,7 @@ class XmlSerializationTest < ActiveModel::TestCase @contact.address.city = "Springfield" @contact.address.apt_number = 35 @contact.friends = [Contact.new, Contact.new] - @related_contact = SerializableContact.new - @contact.contact = @related_contact + @contact.contact = SerializableContact.new end test "should serialize default root" do diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index 41a4c33727..5049d6dd61 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -23,7 +23,7 @@ class ConditionalValidationTest < ActiveModel::TestCase Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_method_false @@ -31,7 +31,7 @@ class ConditionalValidationTest < ActiveModel::TestCase Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true_but_its_not) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_method_false @@ -57,7 +57,7 @@ class ConditionalValidationTest < ActiveModel::TestCase Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_string_false @@ -65,7 +65,7 @@ class ConditionalValidationTest < ActiveModel::TestCase Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_string_false @@ -93,7 +93,7 @@ class ConditionalValidationTest < ActiveModel::TestCase unless: Proc.new { |r| r.content.size > 4 }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_block_false @@ -102,7 +102,7 @@ class ConditionalValidationTest < ActiveModel::TestCase if: Proc.new { |r| r.title != "uhohuhoh"}) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_block_false @@ -124,7 +124,7 @@ class ConditionalValidationTest < ActiveModel::TestCase t = Topic.new assert t.invalid?, "A topic without a title should not be valid" - assert t.errors[:author_name].empty?, "A topic without an 'important' title should not require an author" + assert_empty t.errors[: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" diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 487352fce6..05eb2f8750 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,102 @@ +* Allow Relation#from to accept other relations with bind values. + + *Ryan Wallace* + +* Fix inserts with prepared statements disabled. + + Fixes #12023. + + *Rafael Mendonça França* + +* Setting a has_one association on a new record no longer causes an empty + transaction. + + *Dylan Thacker-Smith* + +* Fix `AR::Relation#merge` sometimes failing to preserve `readonly(false)` flag. + + *thedarkone* + +* Re-use `order` argument pre-processing for `reorder`. + + *Paul Nikitochkin* + +* Fix PredicateBuilder so polymorphic association keys in `where` clause can + accept objects other than direct descendants of `ActiveRecord::Base` (decorated + models, for example). + + *Mikhail Dieterle* + +* PostgreSQL adapter recognizes negative money values formatted with + parentheses (eg. `($1.25) # => -1.25`)). + Fixes #11899. + + *Yves Senn* + +* Stop interpreting SQL 'string' columns as :string type because there is no + common STRING datatype in SQL. + + *Ben Woosley* + +* `ActiveRecord::FinderMethods#exists?` returns `true`/`false` in all cases. + + *Xavier Noria* + +* Assign inet/cidr attribute with `nil` value for invalid address. + + Example: + + record = User.new + record.logged_in_from_ip # is type of an inet or a cidr + + # Before: + record.logged_in_from_ip = 'bad ip address' # raise exception + + # After: + record.logged_in_from_ip = 'bad ip address' # do not raise exception + record.logged_in_from_ip # => nil + record.logged_in_from_ip_before_type_cast # => 'bad ip address' + + *Paul Nikitochkin* + +* `add_to_target` now accepts a second optional `skip_callbacks` argument + + If truthy, it will skip the :before_add and :after_add callbacks. + + *Ben Woosley* + +* Fix interactions between `:before_add` callbacks and nested attributes + assignment of `has_many` associations, when the association was not + yet loaded: + + - A `:before_add` callback was being called when a nested attributes + assignment assigned to an existing record. + + - Nested Attributes assignment did not affect the record in the + association target when a `:before_add` callback triggered the + loading of the association + + *Jörg Schray* + +* Allow enable_extension migration method to be revertible. + + *Eric Tipton* + +* Type cast hstore values on write, so that the value is consistent + with reading from the database. + + Example: + + x = Hstore.new tags: {"bool" => true, "number" => 5} + + # Before: + x.tags # => {"bool" => true, "number" => 5} + + # After: + x.tags # => {"bool" => "true", "number" => "5"} + + *Yves Senn* , *Severin Schoepke* + * Fix multidimensional PG arrays containing non-string items. *Yves Senn* diff --git a/activerecord/RUNNING_UNIT_TESTS.rdoc b/activerecord/RUNNING_UNIT_TESTS.rdoc index c3ee34da55..ca1f2fd665 100644 --- a/activerecord/RUNNING_UNIT_TESTS.rdoc +++ b/activerecord/RUNNING_UNIT_TESTS.rdoc @@ -24,6 +24,7 @@ Simply executing <tt>bundle exec rake test</tt> is equivalent to the following: $ bundle exec rake test_mysql2 $ bundle exec rake test_postgresql $ bundle exec rake test_sqlite3 + $ bundle exec rake test_sqlite3_mem There should be tests available for each database backend listed in the {Config File}[rdoc-label:label-Config+File]. (the exact set of available tests is diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index d075edc159..0d5313956b 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -224,7 +224,7 @@ module ActiveRecord writer_method(name, class_name, mapping, allow_nil, converter) reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) - Reflection.add_reflection self, part_id, reflection + Reflection.add_aggregate_reflection self, part_id, reflection end private diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5ceda933f2..33cbafc6aa 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -164,7 +164,7 @@ module ActiveRecord private # Returns the specified association instance if it responds to :loaded?, nil otherwise. def association_instance_get(name) - @association_cache[name.to_sym] + @association_cache[name] end # Set the specified association instance. diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 8eec4f56af..e1fa5225b5 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -50,8 +50,8 @@ module ActiveRecord # Checks whether record is different to the current target, without loading it def different_target?(record) - if record.nil? - owner[reflection.foreign_key] + if record.nil? + owner[reflection.foreign_key] else record.id != owner[reflection.foreign_key] end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 34de1a1f32..1059fc032d 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -17,7 +17,7 @@ module ActiveRecord::Associations::Builder end self.extensions = [] - VALID_OPTIONS = [:class_name, :foreign_key, :validate] + VALID_OPTIONS = [:class_name, :class, :foreign_key, :validate] attr_reader :name, :scope, :options diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 8ce02afef8..8744a57355 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -290,7 +290,7 @@ module ActiveRecord # Returns true if the collection is empty. # - # If the collection has been loaded + # If the collection has been loaded # it is equivalent to <tt>collection.size.zero?</tt>. If the # collection has not been loaded, it is equivalent to # <tt>collection.exists?</tt>. If the collection has not already been @@ -366,8 +366,8 @@ module ActiveRecord target end - def add_to_target(record) - callback(:before_add, record) + def add_to_target(record, skip_callbacks = false) + callback(:before_add, record) unless skip_callbacks yield(record) if block_given? if association_scope.distinct_value && index = @target.index(record) @@ -376,7 +376,7 @@ module ActiveRecord @target << record end - callback(:after_add, record) + callback(:after_add, record) unless skip_callbacks set_inverse_instance(record) record @@ -542,7 +542,8 @@ module ActiveRecord def include_in_memory?(record) if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) - owner.send(reflection.through_reflection.name).any? { |source| + assoc = owner.association(reflection.through_reflection.name) + assoc.reader.any? { |source| target = source.send(reflection.source_reflection.name) target.respond_to?(:include?) ? target.include?(record) : target == record } || target.include?(record) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 6dc2da56d1..ea7f768a68 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -848,8 +848,6 @@ module ActiveRecord def scope @association.scope end - - # :nodoc: alias spawn scope # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 607ed0da46..a3fcca8a27 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -125,7 +125,11 @@ module ActiveRecord end def foreign_key_present? - owner.attribute_present?(reflection.association_primary_key) + if reflection.klass.primary_key + owner.attribute_present?(reflection.association_primary_key) + else + false + end end end end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index a74dd1cdab..56331bbb0b 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -140,7 +140,21 @@ module ActiveRecord case method when :destroy - count = scope.destroy_all.length + if scope.klass.primary_key + count = scope.destroy_all.length + else + scope.to_a.each do |record| + record.run_callbacks :destroy + end + + arel = scope.arel + + stmt = Arel::DeleteManager.new arel.engine + stmt.from scope.klass.arel_table + stmt.wheres = arel.constraints + + count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) + end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) else diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 3ab1ea1ff4..0008600418 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -27,6 +27,8 @@ module ActiveRecord return self.target if !(target || record) if (target != record) || record.changed? + save &&= owner.persisted? + transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? @@ -34,7 +36,7 @@ module ActiveRecord set_owner_attributes(record) set_inverse_instance(record) - if owner.persisted? && save && !record.save + if save && !record.save nullify_owner_attributes(record) set_owner_attributes(target) if target raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb index b534569063..8024105472 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb @@ -62,7 +62,20 @@ module ActiveRecord end def extract_record(row) - Hash[column_names_with_alias.map{|cn, an| [cn, row[an]]}] + # This code is performance critical as it is called per row. + # see: https://github.com/rails/rails/pull/12185 + hash = {} + + index = 0 + length = column_names_with_alias.length + + while index < length + column_name, alias_name = column_names_with_alias[index] + hash[column_name] = row[alias_name] + index += 1 + end + + hash end def record_id(row) diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 2317e34bc0..6cc9d6c079 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -85,9 +85,11 @@ module ActiveRecord def initialize(records, associations, preload_scope = nil) @records = Array.wrap(records).compact.uniq @associations = Array.wrap(associations) - @preload_scope = preload_scope || Relation.create(nil, nil) + @preload_scope = preload_scope || NULL_RELATION end + NULL_RELATION = Struct.new(:values).new({}) + def run unless records.empty? associations.each { |association| preload(association) } diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 0cc836f991..928da71eed 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -29,6 +29,10 @@ module ActiveRecord end def records_for(ids) + query_scope(ids) + end + + def query_scope(ids) scope.where(association_key.in(ids)) end @@ -52,12 +56,9 @@ module ActiveRecord raise NotImplementedError end - # We're converting to a string here because postgres will return the aliased association - # key in a habtm as a string (for whatever reason) def owners_by_key @owners_by_key ||= owners.group_by do |owner| - key = owner[owner_key_name] - key && key.to_s + owner[owner_key_name] end end @@ -71,27 +72,34 @@ module ActiveRecord owners_map = owners_by_key owner_keys = owners_map.keys.compact - if klass.nil? || owner_keys.empty? - records = [] - else + # Each record may have multiple owners, and vice-versa + records_by_owner = Hash[owners.map { |owner| [owner, []] }] + + if klass && owner_keys.any? # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) # Make several smaller queries if necessary or make one query if the adapter supports it sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) - records = sliced.flat_map { |slice| records_for(slice).to_a } + sliced.each { |slice| + records = records_for(slice) + caster = type_caster(records, association_key_name) + records.each do |record| + owner_key = caster.call record[association_key_name] + + owners_map[owner_key].each do |owner| + records_by_owner[owner] << record + end + end + } end - # Each record may have multiple owners, and vice-versa - records_by_owner = Hash[owners.map { |owner| [owner, []] }] - records.each do |record| - owner_key = record[association_key_name].to_s - - owners_map[owner_key].each do |owner| - records_by_owner[owner] << record - end - end records_by_owner end + IDENTITY = lambda { |value| value } + def type_caster(results, name) + IDENTITY + end + def reflection_scope @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped end diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb index 9a3fada380..c042a44b21 100644 --- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb @@ -12,7 +12,7 @@ module ActiveRecord # Unlike the other associations, we want to get a raw array of rows so that we can # access the aliased column on the join table def records_for(ids) - scope = super + scope = query_scope ids klass.connection.select_all(scope.arel, 'SQL', scope.bind_values) end @@ -40,6 +40,11 @@ module ActiveRecord end end + def type_caster(results, name) + caster = results.column_types.fetch(name, results.identity_type) + lambda { |value| caster.type_cast value } + end + def build_scope super.joins(join).select(join_select) end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index de06931845..2c625cec04 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -27,17 +27,16 @@ module ActiveRecord def through_records_by_owner Preloader.new(owners, through_reflection.name, through_scope).run - Hash[owners.map do |owner| - through_records = Array.wrap(owner.send(through_reflection.name)) + should_reset = (through_scope != through_reflection.klass.unscoped) || + (reflection.options[:source_type] && through_reflection.collection?) - # Dont cache the association - we would only be caching a subset - if (through_scope != through_reflection.klass.unscoped) || - (reflection.options[:source_type] && through_reflection.collection?) - owner.association(through_reflection.name).reset - end + owners.each_with_object({}) do |owner, h| + association = owner.association through_reflection.name + h[owner] = Array(association.reader) - [owner, through_records] - end] + # Dont cache the association - we would only be caching a subset + association.reset if should_reset + end end def through_scope diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 10238555f0..02dc464536 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -42,7 +42,6 @@ module ActiveRecord scope.first.tap { |record| set_inverse_instance(record) } end - # Implemented by subclasses def replace(record) raise NotImplementedError, "Subclasses must implement a replace(record) method" end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 1cf3aba41c..c152a246b5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -109,13 +109,14 @@ module ActiveRecord # We use #[] first as a perf optimization for non-nil values. See https://gist.github.com/jonleighton/3552829. name = attr_name.to_s @attributes_cache[name] || @attributes_cache.fetch(name) { - column = @columns_hash.fetch(name) { - return @attributes.fetch(name) { - if name == 'id' && self.class.primary_key != name - read_attribute(self.class.primary_key) - end - } - } + column = @column_types_override[name] if @column_types_override + column ||= @column_types[name] + + return @attributes.fetch(name) { + if name == 'id' && self.class.primary_key != name + read_attribute(self.class.primary_key) + end + } unless column value = @attributes.fetch(name) { return block_given? ? yield(name) : nil diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 41b5a6e926..f168282ea3 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -51,7 +51,7 @@ module ActiveRecord def create_time_zone_conversion_attribute?(name, column) time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && - [:datetime, :timestamp].include?(column.type) + (:datetime == column.type || :timestamp == column.type) end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b06add096f..04e3dd49e7 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -18,6 +18,7 @@ require 'arel' require 'active_record/errors' require 'active_record/log_subscriber' require 'active_record/explain_subscriber' +require 'active_record/relation/delegation' module ActiveRecord #:nodoc: # = Active Record @@ -290,6 +291,7 @@ module ActiveRecord #:nodoc: extend Translation extend DynamicMatchers extend Explain + extend Delegation::DelegateCache include Persistence include ReadonlyAttributes diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 97e1bc5379..e1f29ea03a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -39,8 +39,8 @@ module ActiveRecord # 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(arel, name = nil) - result = select_rows(to_sql(arel, []), name) - result.map { |v| v[0] } + select_rows(to_sql(arel, []), name) + .map { |v| v[0] } end # Returns an array of arrays containing the field values. @@ -377,7 +377,7 @@ module ActiveRecord def sql_for_insert(sql, pk, id_value, sequence_name, binds) [sql, binds] end - + def last_inserted_id(result) row = result.rows.first row && row.first diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index e6b3c8ec9f..7aae297cdc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -81,14 +81,7 @@ module ActiveRecord else @query_cache[sql][binds] = yield end - - # FIXME: we should guarantee that all cached items are Result - # objects. Then we can avoid this conditional - if ActiveRecord::Result === result - result.dup - else - result.collect { |row| row.dup } - end + result.dup end # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index d18b9c991f..552a22d28a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -15,7 +15,6 @@ module ActiveRecord return "'#{quote_string(value)}'" unless column case column.type - when :binary then "'#{quote_string(column.string_to_binary(value))}'" when :integer then value.to_i.to_s when :float then value.to_f.to_s else @@ -52,7 +51,6 @@ module ActiveRecord return value unless column case column.type - when :binary then value when :integer then value.to_i when :float then value.to_f else diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 0be4b5cb19..063b19871a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -16,9 +16,6 @@ module ActiveRecord # +columns+ attribute of said TableDefinition object, in order to be used # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :primary_key) #:nodoc: - def string_to_binary(value) - value - end def primary_key? primary_key || type.to_sym == :primary_key diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ba1cb05d2c..dde45b0ef3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -97,6 +97,7 @@ module ActiveRecord @pool = pool @schema_cache = SchemaCache.new self @visitor = nil + @prepared_statements = false end def valid_type?(type) @@ -208,10 +209,11 @@ module ActiveRecord end def unprepared_statement - old, @visitor = @visitor, unprepared_visitor + old_prepared_statements, @prepared_statements = @prepared_statements, false + old_visitor, @visitor = @visitor, unprepared_visitor yield ensure - @visitor = old + @visitor, @prepared_statements = old_visitor, old_prepared_statements end # Returns the human-readable name of the adapter. Use mixed case - one @@ -440,6 +442,10 @@ module ActiveRecord # override in derived class ActiveRecord::StatementInvalid.new(message, exception) end + + def without_prepared_statement?(binds) + !@prepared_statements || binds.empty? + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 5b25b26164..d502daf230 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -174,6 +174,7 @@ module ActiveRecord @quoted_column_names, @quoted_table_names = {}, {} if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) + @prepared_statements = true @visitor = Arel::Visitors::MySQL.new self else @visitor = unprepared_visitor @@ -246,8 +247,8 @@ module ActiveRecord # 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] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" elsif value.kind_of?(BigDecimal) value.to_s("F") @@ -469,7 +470,8 @@ module ActiveRecord sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" execute_and_free(sql, 'SCHEMA') do |result| each_hash(result).map do |field| - new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra]) + field_name = set_field_encoding(field[:Field]) + new_column(field_name, field[:Default], field[:Type], field[:Null] == "YES", field[:Collation], field[:Extra]) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index cc02b313e1..fb53090edc 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -119,17 +119,7 @@ module ActiveRecord type_cast(default) end - # Used to convert from Strings to BLOBs - def string_to_binary(value) - self.class.string_to_binary(value) - 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 @@ -282,7 +272,7 @@ module ActiveRecord :text when /blob/i, /binary/i :binary - when /char/i, /string/i + when /char/i :string when /boolean/i :boolean diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 28c7cff1cc..e790f731ea 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -229,7 +229,7 @@ module ActiveRecord alias exec_without_stmt exec_query - # Returns an ActiveRecord::Result instance. + # Returns an ActiveRecord::Result instance. def select(sql, name = nil, binds = []) exec_query(sql, name) end @@ -269,6 +269,10 @@ module ActiveRecord def version @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end + + def set_field_encoding field_name + field_name + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index fbe6ecf5f1..41a47183e0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -279,11 +279,7 @@ module ActiveRecord end def exec_query(sql, name = 'SQL', binds = []) - # If the configuration sets prepared_statements:false, binds will - # always be empty, since the bind variables will have been already - # substituted and removed from binds by BindVisitor, so this will - # effectively disable prepared statement usage completely. - if binds.empty? + if without_prepared_statement?(binds) result_set, affected_rows = exec_without_stmt(sql, name) else result_set, affected_rows = exec_stmt(sql, name, binds) @@ -559,6 +555,14 @@ module ActiveRecord def version @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end + + def set_field_encoding field_name + field_name.force_encoding(client_encoding) + if internal_enc = Encoding.default_internal + field_name = field_name.encoding(internal_enc) + end + field_name + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index 56dc9ea813..ea44e818e5 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -17,8 +17,8 @@ module ActiveRecord return string unless String === string case string - when 'infinity'; 1.0 / 0.0 - when '-infinity'; -1.0 / 0.0 + when 'infinity'; Float::INFINITY + when '-infinity'; -Float::INFINITY when / BC$/ super("-" + string.sub(/ BC$/, "")) else @@ -100,7 +100,11 @@ module ActiveRecord if string.nil? nil elsif String === string - IPAddr.new(string) + begin + IPAddr.new(string) + rescue ArgumentError + nil + end else string end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 751655e61c..86b96a77fb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -135,11 +135,12 @@ module ActiveRecord def exec_query(sql, name = 'SQL', binds = []) log(sql, name, binds) do - result = binds.empty? ? exec_no_cache(sql, binds) : - exec_cache(sql, binds) + result = without_prepared_statement?(binds) ? exec_no_cache(sql, binds) : + exec_cache(sql, binds) types = {} - result.fields.each_with_index do |fname, i| + fields = result.fields + fields.each_with_index do |fname, i| ftype = result.ftype i fmod = result.fmod i types[fname] = OID::TYPE_MAP.fetch(ftype, fmod) { |oid, mod| @@ -148,7 +149,7 @@ module ActiveRecord } end - ret = ActiveRecord::Result.new(result.fields, result.values, types) + ret = ActiveRecord::Result.new(fields, result.values, types) result.clear return ret end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 1be116ce10..dab876af14 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -6,10 +6,6 @@ module ActiveRecord module OID class Type def type; end - - def type_cast_for_write(value) - value - end end class Identity < Type @@ -38,12 +34,17 @@ module ActiveRecord class Money < Type def type_cast(value) return if value.nil? + return value unless String === value # 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 + # Negative values are represented as follows: + # (3) -$2.55 + # (4) ($2.55) + value.sub!(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) value.gsub!(/[^-\d.]/, '') @@ -224,6 +225,10 @@ module ActiveRecord end class Hstore < Type + def type_cast_for_write(value) + ConnectionAdapters::PostgreSQLColumn.hstore_to_string value + end + def type_cast(value) return if value.nil? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 342d1b1433..13978fd113 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -129,6 +129,14 @@ module ActiveRecord end end + def type_cast_for_write(value) + if @oid_type.respond_to?(:type_cast_for_write) + @oid_type.type_cast_for_write(value) + else + super + end + end + def type_cast(value) return if value.nil? return super if encoded? @@ -523,6 +531,7 @@ module ActiveRecord super(connection, logger) if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) + @prepared_statements = true @visitor = Arel::Visitors::PostgreSQL.new self else @visitor = unprepared_visitor diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index e1475416eb..136094dcc9 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -113,6 +113,7 @@ module ActiveRecord @config = config if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) + @prepared_statements = true @visitor = Arel::Visitors::SQLite.new self else @visitor = unprepared_visitor @@ -226,8 +227,8 @@ module ActiveRecord # 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] + if value.kind_of?(String) && column && column.type == :binary + s = value.unpack("H*")[0] "x'#{s}'" else super @@ -293,8 +294,8 @@ module ActiveRecord def exec_query(sql, name = nil, binds = []) log(sql, name, binds) do - # Don't cache statements without bind values - if binds.empty? + # Don't cache statements if they are not prepared + if without_prepared_statement?(binds) stmt = @connection.prepare(sql) cols = stmt.columns records = stmt.to_a diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 2b1e997ef4..b4d6474caa 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -168,7 +168,8 @@ module ActiveRecord defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? } @attributes = self.class.initialize_attributes(defaults) - @columns_hash = self.class.column_types.dup + @column_types_override = nil + @column_types = self.class.column_types init_internals init_changed_attributes @@ -193,7 +194,8 @@ module ActiveRecord # post.title # => 'hello world' def init_with(coder) @attributes = self.class.initialize_attributes(coder['attributes']) - @columns_hash = self.class.column_types.merge(coder['column_types'] || {}) + @column_types_override = coder['column_types'] + @column_types = self.class.column_types init_internals diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb index 3bac31c6aa..e650ebcf64 100644 --- a/activerecord/lib/active_record/dynamic_matchers.rb +++ b/activerecord/lib/active_record/dynamic_matchers.rb @@ -35,7 +35,7 @@ module ActiveRecord end def pattern - /^#{prefix}_([_a-zA-Z]\w*)#{suffix}$/ + @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ end def prefix diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index b2a81a184a..9a26e5df3f 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -379,16 +379,16 @@ module ActiveRecord @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} } - def self.default_fixture_model_name(fixture_set_name) # :nodoc: - ActiveRecord::Base.pluralize_table_names ? + def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + config.pluralize_table_names ? fixture_set_name.singularize.camelize : fixture_set_name.camelize end - def self.default_fixture_table_name(fixture_set_name) # :nodoc: - "#{ ActiveRecord::Base.table_name_prefix }"\ + def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + "#{ config.table_name_prefix }"\ "#{ fixture_set_name.tr('/', '_') }"\ - "#{ ActiveRecord::Base.table_name_suffix }".to_sym + "#{ config.table_name_suffix }".to_sym end def self.reset_cache @@ -436,9 +436,47 @@ module ActiveRecord cattr_accessor :all_loaded_fixtures self.all_loaded_fixtures = {} - def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}) + class ClassCache + def initialize(class_names, config) + @class_names = class_names.stringify_keys + @config = config + + # Remove string values that aren't constants or subclasses of AR + @class_names.delete_if { |k,klass| + unless klass.is_a? Class + klass = klass.safe_constantize + ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.") + end + !insert_class(@class_names, k, klass) + } + end + + def [](fs_name) + @class_names.fetch(fs_name) { + klass = default_fixture_model(fs_name, @config).safe_constantize + insert_class(@class_names, fs_name, klass) + } + end + + private + + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end + end + + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end + end + + def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) fixture_set_names = Array(fixture_set_names).map(&:to_s) - class_names = class_names.stringify_keys + class_names = ClassCache.new class_names, config # FIXME: Apparently JK uses this. connection = block_given? ? yield : ActiveRecord::Base.connection @@ -452,10 +490,12 @@ module ActiveRecord fixtures_map = {} fixture_sets = files_to_read.map do |fs_name| + klass = class_names[fs_name] + conn = klass ? klass.connection : connection fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new - connection, + conn, fs_name, - class_names[fs_name] || default_fixture_model_name(fs_name), + klass, ::File.join(fixtures_directory, fs_name)) end @@ -497,27 +537,31 @@ module ActiveRecord Zlib.crc32(label.to_s) % MAX_ID end - attr_reader :table_name, :name, :fixtures, :model_class + attr_reader :table_name, :name, :fixtures, :model_class, :config - def initialize(connection, name, class_name, path) - @fixtures = {} # Ordered hash + def initialize(connection, name, class_name, path, config = ActiveRecord::Base) @name = name @path = path + @config = config + @model_class = nil + + if class_name.is_a?(String) + ActiveSupport::Deprecation.warn("The ability to pass in strings as a class name will be removed in Rails 4.2, consider using the class itself instead.") + end if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? @model_class = class_name else - @model_class = class_name.constantize rescue nil + @model_class = class_name.safe_constantize if class_name end - @connection = ( model_class.respond_to?(:connection) ? - model_class.connection : connection ) + @connection = connection @table_name = ( model_class.respond_to?(:table_name) ? model_class.table_name : - self.class.default_fixture_table_name(name) ) + self.class.default_fixture_table_name(name, config) ) - read_fixture_files + @fixtures = read_fixture_files path, @model_class end def [](x) @@ -539,7 +583,7 @@ module ActiveRecord # Return a hash of rows to be inserted. The key is the table, the value is # a list of rows to insert to that table. def table_rows - now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now + now = config.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 @@ -551,7 +595,7 @@ module ActiveRecord rows[table_name] = fixtures.map do |label, fixture| row = fixture.to_hash - if model_class && model_class < ActiveRecord::Base + if model_class # 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 |c_name| @@ -591,8 +635,12 @@ module ActiveRecord row[fk_name] = ActiveRecord::FixtureSet.identify(value) end + when :has_many + if association.options[:through] + add_join_records(rows, row, HasManyThroughProxy.new(association)) + end when :has_and_belongs_to_many - handle_habtm(rows, row, association) + add_join_records(rows, row, HABTMProxy.new(association)) end end end @@ -602,18 +650,56 @@ module ActiveRecord rows end + class ReflectionProxy # :nodoc: + def initialize(association) + @association = association + end + + def join_table + @association.join_table + end + + def name + @association.name + end + end + + class HasManyThroughProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.foreign_key + end + + def lhs_key + @association.through_reflection.foreign_key + end + end + + class HABTMProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.association_foreign_key + end + + def lhs_key + @association.foreign_key + end + end + private def primary_key_name @primary_key_name ||= model_class && model_class.primary_key end - def handle_habtm(rows, row, association) + def add_join_records(rows, row, association) + # This is the case when the join table has no fixtures file if (targets = row.delete(association.name.to_s)) - targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) table_name = association.join_table + lhs_key = association.lhs_key + rhs_key = association.rhs_key + + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) rows[table_name].concat targets.map { |target| - { association.foreign_key => row[primary_key_name], - association.association_foreign_key => ActiveRecord::FixtureSet.identify(target) } + { lhs_key => row[primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target) } } end end @@ -636,12 +722,12 @@ module ActiveRecord @column_names ||= @connection.columns(@table_name).collect { |c| c.name } end - def read_fixture_files - yaml_files = Dir["#{@path}/{**,*}/*.yml"].select { |f| + def read_fixture_files(path, model_class) + yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f| ::File.file?(f) - } + [yaml_file_path] + } + [yaml_file_path(path)] - yaml_files.each do |file| + yaml_files.each_with_object({}) do |file, fixtures| FixtureSet::File.open(file) do |fh| fh.each do |fixture_name, row| fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) @@ -650,8 +736,8 @@ module ActiveRecord end end - def yaml_file_path - "#{@path}.yml" + def yaml_file_path(path) + "#{path}.yml" end end @@ -723,14 +809,16 @@ module ActiveRecord class_attribute :use_transactional_fixtures class_attribute :use_instantiated_fixtures # true, false, or :no_instances class_attribute :pre_loaded_fixtures + class_attribute :config self.fixture_table_names = [] self.use_transactional_fixtures = true self.use_instantiated_fixtures = false self.pre_loaded_fixtures = false + self.config = ActiveRecord::Base self.fixture_class_names = Hash.new do |h, fixture_set_name| - h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name) + h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config) end end @@ -743,13 +831,6 @@ module ActiveRecord # 'namespaced/fixture' => Another::Model # # The keys must be the fixture names, that coincide with the short paths to the fixture files. - #-- - # It is also possible to pass the class name instead of the class: - # set_fixture_class 'some_fixture' => 'SomeModel' - # I think this option is redundant, i propose to deprecate it. - # Isn't it easier to always pass the class itself? - # (2011-12-20 alexeymuranov) - #++ def set_fixture_class(class_names = {}) self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys) end @@ -763,7 +844,7 @@ module ActiveRecord end self.fixture_table_names |= fixture_set_names - require_fixture_classes(fixture_set_names) + require_fixture_classes(fixture_set_names, self.config) setup_fixture_accessors(fixture_set_names) end @@ -778,7 +859,7 @@ module ActiveRecord end end - def require_fixture_classes(fixture_set_names = nil) + def require_fixture_classes(fixture_set_names = nil, config = ActiveRecord::Base) if fixture_set_names fixture_set_names = fixture_set_names.map { |n| n.to_s } else @@ -786,7 +867,7 @@ module ActiveRecord end fixture_set_names.each do |file_name| - file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names + file_name = file_name.singularize if config.pluralize_table_names try_to_load_dependency(file_name) end end @@ -838,7 +919,7 @@ module ActiveRecord !self.class.uses_transaction?(method_name) end - def setup_fixtures + def setup_fixtures(config = ActiveRecord::Base) if pre_loaded_fixtures && !use_transactional_fixtures raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures' end @@ -852,7 +933,7 @@ module ActiveRecord if @@already_loaded_fixtures[self.class] @loaded_fixtures = @@already_loaded_fixtures[self.class] else - @loaded_fixtures = load_fixtures + @loaded_fixtures = load_fixtures(config) @@already_loaded_fixtures[self.class] = @loaded_fixtures end @fixture_connections = enlist_fixture_connections @@ -863,11 +944,11 @@ module ActiveRecord else ActiveRecord::FixtureSet.reset_cache @@already_loaded_fixtures[self.class] = nil - @loaded_fixtures = load_fixtures + @loaded_fixtures = load_fixtures(config) end # Instantiate fixtures for every test if requested. - instantiate_fixtures if use_instantiated_fixtures + instantiate_fixtures(config) if use_instantiated_fixtures end def teardown_fixtures @@ -889,19 +970,19 @@ module ActiveRecord end private - def load_fixtures - fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) + def load_fixtures(config) + fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) Hash[fixtures.map { |f| [f.name, f] }] end # for pre_loaded_fixtures, only require the classes once. huge speed improvement @@required_fixture_classes = false - def instantiate_fixtures + def instantiate_fixtures(config) if pre_loaded_fixtures raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? unless @@required_fixture_classes - self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys + self.class.require_fixture_classes ActiveRecord::FixtureSet.all_loaded_fixtures.keys, config @@required_fixture_classes = true end ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 19c6f8148b..a1ad4f6255 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -373,23 +373,23 @@ module ActiveRecord class << self attr_accessor :delegate # :nodoc: attr_accessor :disable_ddl_transaction # :nodoc: - end - def self.check_pending! - raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration? - end + def check_pending! + raise ActiveRecord::PendingMigrationError if ActiveRecord::Migrator.needs_migration? + end - def self.method_missing(name, *args, &block) # :nodoc: - (delegate || superclass.delegate).send(name, *args, &block) - end + def method_missing(name, *args, &block) # :nodoc: + (delegate || superclass.delegate).send(name, *args, &block) + end - def self.migrate(direction) - new.migrate direction - end + def migrate(direction) + new.migrate direction + end - # Disable DDL transactions for this migration. - def self.disable_ddl_transaction! - @disable_ddl_transaction = true + # Disable DDL transactions for this migration. + def disable_ddl_transaction! + @disable_ddl_transaction = true + end end def disable_ddl_transaction # :nodoc: @@ -617,8 +617,8 @@ module ActiveRecord say_with_time "#{method}(#{arg_list})" do unless @connection.respond_to? :revert unless arguments.empty? || method == :execute - arguments[0] = Migrator.proper_table_name(arguments.first) - arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table + arguments[0] = proper_table_name(arguments.first, table_name_options) + arguments[1] = proper_table_name(arguments.second, table_name_options) if method == :rename_table end end return super unless connection.respond_to?(method) @@ -671,6 +671,17 @@ module ActiveRecord copied end + # Finds the correct table name given an Active Record object. + # Uses the Active Record object's own table_name, or pre/suffix from the + # options passed in. + def proper_table_name(name, options = {}) + if name.respond_to? :table_name + name.table_name + else + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" + end + end + # Determines the version number of the next migration. def next_migration_number(number) if ActiveRecord::Base.timestamped_migrations @@ -680,6 +691,13 @@ module ActiveRecord end end + def table_name_options(config = ActiveRecord::Base) + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + private def execute_block if connection.respond_to? :execute_block @@ -809,12 +827,16 @@ module ActiveRecord migrations(migrations_paths).last || NullMigration.new 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 + def proper_table_name(name, options = {}) + ActiveSupport::Deprecation.warn "ActiveRecord::Migrator.proper_table_name is deprecated and will be removed in Rails 4.2. Use the proper_table_name instance method on ActiveRecord::Migration instead" + options = { + table_name_prefix: ActiveRecord::Base.table_name_prefix, + table_name_suffix: ActiveRecord::Base.table_name_suffix + }.merge(options) if name.respond_to? :table_name name.table_name else - "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" end end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 9782a48055..01c73be849 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -73,7 +73,7 @@ module ActiveRecord [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column_default, :add_reference, :remove_reference, :transaction, - :drop_join_table, :drop_table, :execute_block, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :change_column, :execute, :remove_columns, # irreversible methods need to be here too ].each do |method| class_eval <<-EOV, __FILE__, __LINE__ + 1 @@ -100,6 +100,7 @@ module ActiveRecord add_column: :remove_column, add_timestamps: :remove_timestamps, add_reference: :remove_reference, + enable_extension: :disable_extension }.each do |cmd, inv| [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| class_eval <<-EOV, __FILE__, __LINE__ + 1 diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 23541d1d27..75c0c1bda8 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -224,13 +224,20 @@ module ActiveRecord def decorate_columns(columns_hash) # :nodoc: return if columns_hash.empty? - columns_hash.each do |name, col| - if serialized_attributes.key?(name) - columns_hash[name] = AttributeMethods::Serialization::Type.new(col) - end - if create_time_zone_conversion_attribute?(name, col) - columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(col) - end + @serialized_column_names ||= self.columns_hash.keys.find_all do |name| + serialized_attributes.key?(name) + end + + @serialized_column_names.each do |name| + columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name]) + end + + @time_zone_column_names ||= self.columns_hash.find_all do |name, col| + create_time_zone_conversion_attribute?(name, col) + end.map!(&:first) + + @time_zone_column_names.each do |name| + columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name]) end columns_hash @@ -284,16 +291,19 @@ module ActiveRecord undefine_attribute_methods connection.schema_cache.clear_table_cache!(table_name) if table_exists? - @arel_engine = nil - @column_defaults = nil - @column_names = nil - @columns = nil - @columns_hash = nil - @column_types = nil - @content_columns = nil - @dynamic_methods_hash = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column - @relation = nil + @arel_engine = nil + @column_defaults = nil + @column_names = nil + @columns = nil + @columns_hash = nil + @column_types = nil + @content_columns = nil + @dynamic_methods_hash = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @relation = nil + @serialized_column_names = nil + @time_zone_column_names = nil + @cached_time_zone = nil end # This is a hook for use by modules that need to do extra stuff to diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e53e8553ad..df28451bb7 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -465,19 +465,17 @@ module ActiveRecord association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } - unless association.loaded? || call_reject_if(association_name, attributes) + unless call_reject_if(association_name, attributes) # Make sure we are operating on the actual object which is in the association's # proxy_target array (either by finding it, or adding it if not found) - target_record = association.target.detect { |record| record == existing_record } - + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes['id'].to_s } if target_record existing_record = target_record else - association.add_to_target(existing_record) + association.add_to_target(existing_record, :skip_callbacks) end - end - if !call_reject_if(association_name, attributes) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end else diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 582006ea7d..d630f31f5f 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -383,9 +383,10 @@ module ActiveRecord end @attributes.update(fresh_object.instance_variable_get('@attributes')) - @columns_hash = fresh_object.instance_variable_get('@columns_hash') - @attributes_cache = {} + @column_types = self.class.column_types + @column_types_override = fresh_object.instance_variable_get('@column_types_override') + @attributes_cache = {} self end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 8a311039d5..daccab762f 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -320,11 +320,14 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent schema.rb file" task :load_schema => 'db:test:purge' do begin + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) ActiveRecord::Schema.verbose = false db_namespace["schema:load"].invoke ensure - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 73d154e03e..f47282b7fd 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -24,11 +24,11 @@ module ActiveRecord end def self.add_reflection(ar, name, reflection) - if reflection.class == AggregateReflection - ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) - else - ar.reflections = ar.reflections.merge(name => reflection) - end + ar.reflections = ar.reflections.merge(name => reflection) + end + + def self.add_aggregate_reflection(ar, name, reflection) + ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection) end # \Reflection enables to interrogate Active Record classes and objects @@ -121,6 +121,7 @@ module ActiveRecord @scope = scope @options = options @active_record = active_record + @klass = options[:class] @plural_name = active_record.pluralize_table_names ? name.to_s.pluralize : name.to_s end @@ -394,7 +395,7 @@ module ActiveRecord # returns either nil or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) - inverse_name = active_record.name.downcase.to_sym + inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym begin reflection = klass.reflect_on_association(inverse_name) @@ -413,7 +414,7 @@ module ActiveRecord end # Checks if the inverse reflection that is returned from the - # +set_automatic_inverse_of+ method is a valid reflection. We must + # +automatic_inverse_of+ method is a valid reflection. We must # make sure that the reflection's active_record name matches up # with the current reflection's klass name. # @@ -422,7 +423,6 @@ module ActiveRecord def valid_inverse_reflection?(reflection) reflection && klass.name == reflection.active_record.name && - klass.primary_key == reflection.active_record_primary_key && can_find_inverse_of_automatically?(reflection) end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 52a538e5b5..27c04b0952 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -161,8 +161,7 @@ module ActiveRecord result = result.map do |attributes| values = klass.initialize_attributes(attributes).values - iter = columns.each - values.map { |value| iter.next.type_cast value } + columns.zip(values).map { |column, value| column.type_cast value } end columns.one? ? result.map!(&:first) : result end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index b6f80ac5c7..e28938f9d0 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,8 +1,33 @@ -require 'thread' -require 'thread_safe' +require 'active_support/concern' module ActiveRecord module Delegation # :nodoc: + module DelegateCache + def relation_delegate_class(klass) # :nodoc: + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache # :nodoc: + @relation_delegate_cache = cache = {} + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation + ].each do |klass| + delegate = Class.new(klass) { + include ClassSpecificRelation + } + const_set klass.name.gsub('::', '_'), delegate + cache[klass] = delegate + end + end + + def inherited(child_class) + child_class.initialize_relation_delegate_cache + super + end + end + extend ActiveSupport::Concern # This module creates compiled delegation methods dynamically at runtime, which makes @@ -71,32 +96,14 @@ module ActiveRecord end module ClassMethods # :nodoc: - @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) - def create(klass, *args) relation_class_for(klass).new(klass, *args) end private - # Cache the constants in @@subclasses because looking them up via const_get - # make instantiation significantly slower. + def relation_class_for(klass) - if klass && (klass_name = klass.name) - my_cache = @@subclasses.compute_if_absent(self) { ThreadSafe::Cache.new } - # This hash is keyed by klass.name to avoid memory leaks in development mode - my_cache.compute_if_absent(klass_name) do - # Cache#compute_if_absent guarantees that the block will only executed once for the given klass_name - subclass_name = "#{name.gsub('::', '_')}_#{klass_name.gsub('::', '_')}" - - if const_defined?(subclass_name) - const_get(subclass_name) - else - const_set(subclass_name, Class.new(self) { include ClassSpecificRelation }) - end - end - else - ActiveRecord::Relation - end + klass.relation_delegate_class(self) end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2d3bd563ac..0132a02f83 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -171,21 +171,21 @@ module ActiveRecord last or raise RecordNotFound end - # Returns truthy if a record exists in the table that matches the +id+ or - # conditions given, or falsy otherwise. The argument can take six forms: + # Returns +true+ if a record exists in the table that matches the +id+ or + # conditions given, or +false+ otherwise. The argument can take six forms: # # * Integer - Finds the record with this primary key. # * String - Finds the record with a primary key corresponding to this # string (such as <tt>'5'</tt>). # * Array - Finds the record that matches these +find+-style conditions - # (such as <tt>['color = ?', 'red']</tt>). + # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>). # * Hash - Finds the record that matches these +find+-style conditions - # (such as <tt>{color: 'red'}</tt>). + # (such as <tt>{name: 'David'}</tt>). # * +false+ - Returns always +false+. # * No args - Returns +false+ if the table is empty, +true+ otherwise. # - # For more information about specifying conditions as a Hash or Array, - # see the Conditions section in the introduction to ActiveRecord::Base. + # For more information about specifying conditions as a hash or array, + # see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>. # # Note: You can't pass in a condition as a string (like <tt>name = # 'Jamie'</tt>), since it would be sanitized and then queried against @@ -213,7 +213,7 @@ module ActiveRecord relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none end - connection.select_value(relation.arel, "#{name} Exists", relation.bind_values) + connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false end # This method is called whenever no records are found with either a single diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index c08158d38b..c05632e688 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -58,7 +58,11 @@ module ActiveRecord def merge normal_values.each do |name| value = values[name] - relation.send("#{name}!", *value) unless value.blank? + # The unless clause is here mostly for performance reasons (since the `send` call might be moderately + # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that + # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values + # don't fall through the cracks. + relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value) end merge_multi_values @@ -107,11 +111,11 @@ module ActiveRecord bind_values = filter_binds(lhs_binds, removed) + rhs_binds conn = relation.klass.connection - bviter = bind_values.each.with_index + bv_index = 0 where_values.map! do |node| if Arel::Nodes::Equality === node && Arel::Nodes::BindParam === node.right - (column, _), i = bviter.next - substitute = conn.substitute_at column, i + substitute = conn.substitute_at(bind_values[bv_index].first, bv_index) + bv_index += 1 Arel::Nodes::Equality.new(node.left, substitute) else node diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 8948f2bba5..c60cd27a83 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -55,7 +55,7 @@ module ActiveRecord # # For polymorphic relationships, find the foreign key and type: # PriceEstimate.where(estimate_of: treasure) - if klass && value.class < Base && reflection = klass.reflect_on_association(column.to_sym) + if klass && value.is_a?(Base) && reflection = klass.reflect_on_association(column.to_sym) if reflection.polymorphic? queries << build(table[reflection.foreign_type], value.class.base_class) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9f2a039d94..9916c597ee 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -289,17 +289,7 @@ module ActiveRecord end def order!(*args) # :nodoc: - args.flatten! - validate_order_args(args) - - references = args.grep(String) - references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! - references!(references) if references.any? - - # if a symbol is given we prepend the quoted table name - args.map! do |arg| - arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg - end + preprocess_order_args(args) self.order_values += args self @@ -320,8 +310,7 @@ module ActiveRecord end def reorder!(*args) # :nodoc: - args.flatten! - validate_order_args(args) + preprocess_order_args(args) self.reordering_value = true self.order_values = args @@ -926,6 +915,7 @@ module ActiveRecord case opts when Relation name ||= 'subquery' + self.bind_values = opts.bind_values + self.bind_values opts.arel.as(name.to_s) else opts @@ -1036,6 +1026,20 @@ module ActiveRecord end end + def preprocess_order_args(order_args) + order_args.flatten! + validate_order_args(order_args) + + references = order_args.grep(String) + references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! + references!(references) if references.any? + + # if a symbol is given we prepend the quoted table name + order_args.map! do |arg| + arg.is_a?(Symbol) ? Arel::Nodes::Ascending.new(klass.arel_table[arg]) : arg + end + end + # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 253368ae5b..d0f1cb5b75 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -93,7 +93,21 @@ module ActiveRecord # used as keys in ActiveRecord::Base's @attributes hash columns = @columns.map { |c| c.dup.freeze } @rows.map { |row| - Hash[columns.zip(row)] + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + length = columns.length + + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + hash } end end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index a5d6aad3f0..01fec31544 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -100,11 +100,7 @@ module ActiveRecord elsif default_scopes.any? evaluate_default_scope do default_scopes.inject(relation) do |default_scope, scope| - if !scope.is_a?(Relation) && scope.respond_to?(:call) - default_scope.merge(unscoped { scope.call }) - else - default_scope.merge(scope) - end + default_scope.merge(unscoped { scope.call }) end end end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 489efac932..b8dd35c4c5 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase def test_write_value data = "\u001F" record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) end @@ -74,14 +74,14 @@ class PostgresqlByteaTest < ActiveRecord::TestCase data = File.read(File.join(File.dirname(__FILE__), '..', '..', '..', 'assets', 'example.log')) assert(data.size > 1) record = ByteaDataType.create(payload: data) - refute record.new_record? + assert_not record.new_record? assert_equal(data, record.payload) assert_equal(data, ByteaDataType.where(id: record.id).first.payload) end def test_write_nil record = ByteaDataType.create(payload: nil) - refute record.new_record? + assert_not record.new_record? assert_equal(nil, record.payload) assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index e946b8bb22..3dbab08a99 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -298,6 +298,14 @@ _SQL assert_equal(-567.89, @second_money.wealth) end + def test_money_type_cast + column = PostgresqlMoney.columns.find { |c| c.name == 'wealth' } + assert_equal(12345678.12, column.type_cast("$12,345,678.12")) + assert_equal(12345678.12, column.type_cast("$12.345.678,12")) + assert_equal(-1.15, column.type_cast("-$1.15")) + assert_equal(-2.25, column.type_cast("($2.25)")) + end + def test_create_tstzrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? tstzrange = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2011-02-02 14:30:00 CDT') @@ -311,11 +319,11 @@ _SQL def test_update_tstzrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_tstzrange = Time.parse('2010-01-01 14:30:00 CDT')...Time.parse('2011-02-02 14:30:00 CET') - assert @first_range.tstz_range = new_tstzrange + @first_range.tstz_range = new_tstzrange assert @first_range.save assert @first_range.reload assert_equal new_tstzrange, @first_range.tstz_range - assert @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') + @first_range.tstz_range = Time.parse('2010-01-01 14:30:00 +0100')...Time.parse('2010-01-01 13:30:00 +0000') assert @first_range.save assert @first_range.reload assert_nil @first_range.tstz_range @@ -335,11 +343,11 @@ _SQL skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? tz = ::ActiveRecord::Base.default_timezone new_tsrange = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 2, 2, 14, 30, 0) - assert @first_range.ts_range = new_tsrange + @first_range.ts_range = new_tsrange assert @first_range.save assert @first_range.reload assert_equal new_tsrange, @first_range.ts_range - assert @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) + @first_range.ts_range = Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2010, 1, 1, 14, 30, 0) assert @first_range.save assert @first_range.reload assert_nil @first_range.ts_range @@ -357,11 +365,11 @@ _SQL def test_update_numrange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_numrange = BigDecimal.new('0.5')...BigDecimal.new('1') - assert @first_range.num_range = new_numrange + @first_range.num_range = new_numrange assert @first_range.save assert @first_range.reload assert_equal new_numrange, @first_range.num_range - assert @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') + @first_range.num_range = BigDecimal.new('0.5')...BigDecimal.new('0.5') assert @first_range.save assert @first_range.reload assert_nil @first_range.num_range @@ -379,11 +387,11 @@ _SQL def test_update_daterange skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_daterange = Date.new(2012, 2, 3)...Date.new(2012, 2, 10) - assert @first_range.date_range = new_daterange + @first_range.date_range = new_daterange assert @first_range.save assert @first_range.reload assert_equal new_daterange, @first_range.date_range - assert @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) + @first_range.date_range = Date.new(2012, 2, 3)...Date.new(2012, 2, 3) assert @first_range.save assert @first_range.reload assert_nil @first_range.date_range @@ -401,11 +409,11 @@ _SQL def test_update_int4range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int4range = 6...10 - assert @first_range.int4_range = new_int4range + @first_range.int4_range = new_int4range assert @first_range.save assert @first_range.reload assert_equal new_int4range, @first_range.int4_range - assert @first_range.int4_range = 3...3 + @first_range.int4_range = 3...3 assert @first_range.save assert @first_range.reload assert_nil @first_range.int4_range @@ -423,11 +431,11 @@ _SQL def test_update_int8range skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? new_int8range = 60000...10000000 - assert @first_range.int8_range = new_int8range + @first_range.int8_range = new_int8range assert @first_range.save assert @first_range.reload assert_equal new_int8range, @first_range.int8_range - assert @first_range.int8_range = 39999...39999 + @first_range.int8_range = 39999...39999 assert @first_range.save assert @first_range.reload assert_nil @first_range.int8_range @@ -435,10 +443,10 @@ _SQL def test_update_tsvector new_text_vector = "'new' 'text' 'vector'" - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload - assert @first_tsvector.text_vector = new_text_vector + @first_tsvector.text_vector = new_text_vector assert @first_tsvector.save assert @first_tsvector.reload assert_equal new_text_vector, @first_tsvector.text_vector @@ -479,11 +487,11 @@ _SQL def test_update_integer_array new_value = [32800,95000,29350,17000] - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter - assert @first_array.commission_by_quarter = new_value + @first_array.commission_by_quarter = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.commission_by_quarter @@ -491,11 +499,11 @@ _SQL def test_update_text_array new_value = ['robby','robert','rob','robbie'] - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames - assert @first_array.nicknames = new_value + @first_array.nicknames = new_value assert @first_array.save assert @first_array.reload assert_equal new_value, @first_array.nicknames @@ -503,7 +511,7 @@ _SQL def test_update_money new_value = BigDecimal.new('123.45') - assert @first_money.wealth = new_value + @first_money.wealth = new_value assert @first_money.save assert @first_money.reload assert_equal new_value, @first_money.wealth @@ -512,8 +520,8 @@ _SQL def test_update_number new_single = 789.012 new_double = 789012.345 - assert @first_number.single = new_single - assert @first_number.double = new_double + @first_number.single = new_single + @first_number.double = new_double assert @first_number.save assert @first_number.reload assert_equal new_single, @first_number.single @@ -521,7 +529,7 @@ _SQL end def test_update_time - assert @first_time.time_interval = '2 years 3 minutes' + @first_time.time_interval = '2 years 3 minutes' assert @first_time.save assert @first_time.reload assert_equal '2 years 00:03:00', @first_time.time_interval @@ -531,9 +539,9 @@ _SQL new_inet_address = '10.1.2.3/32' new_cidr_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 + @first_network_address.cidr_address = new_cidr_address + @first_network_address.inet_address = new_inet_address + @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 @@ -544,8 +552,8 @@ _SQL def test_update_bit_string new_bit_string = '11111111' new_bit_string_varying = '0xFF' - assert @first_bit_string.bit_string = new_bit_string - assert @first_bit_string.bit_string_varying = new_bit_string_varying + @first_bit_string.bit_string = new_bit_string + @first_bit_string.bit_string_varying = new_bit_string_varying assert @first_bit_string.save assert @first_bit_string.reload assert_equal new_bit_string, @first_bit_string.bit_string @@ -558,9 +566,23 @@ _SQL assert_raise(ActiveRecord::StatementInvalid) { assert @first_bit_string.save } end + def test_invalid_network_address + @first_network_address.cidr_address = 'invalid addr' + assert_nil @first_network_address.cidr_address + assert_equal 'invalid addr', @first_network_address.cidr_address_before_type_cast + assert @first_network_address.save + + @first_network_address.reload + + @first_network_address.inet_address = 'invalid addr' + assert_nil @first_network_address.inet_address + assert_equal 'invalid addr', @first_network_address.inet_address_before_type_cast + assert @first_network_address.save + end + def test_update_oid new_value = 567890 - assert @first_oid.obj_id = new_value + @first_oid.obj_id = new_value assert @first_oid.save assert @first_oid.reload assert_equal new_value, @first_oid.obj_id diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index e434b4861c..f61f196c71 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -70,6 +70,13 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase Hstore.reset_column_information end + def test_cast_value_on_write + x = Hstore.new tags: {"bool" => true, "number" => 5} + assert_equal({"bool" => "true", "number" => "5"}, x.tags) + x.save + assert_equal({"bool" => "true", "number" => "5"}, x.reload.tags) + end + def test_type_cast_hstore assert @column diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index b3429648ee..1122f8b9a1 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -47,11 +47,16 @@ module ActiveRecord def test_quote_cast_numeric fixnum = 666 - c = Column.new(nil, nil, 'string') + c = Column.new(nil, nil, 'varchar') assert_equal "'666'", @conn.quote(fixnum, c) c = Column.new(nil, nil, 'text') assert_equal "'666'", @conn.quote(fixnum, c) end + + def test_quote_time_usec + assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0)) + assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime) + end end end end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index e693d34f99..811d91f849 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -52,12 +52,10 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase def test_cascaded_eager_association_loading_with_join_for_count categories = Category.joins(:categorizations).includes([{:posts=>:comments}, :authors]) - assert_nothing_raised do - assert_equal 4, categories.count - assert_equal 4, categories.to_a.count - assert_equal 3, categories.distinct.count - assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes - end + assert_equal 4, categories.count + assert_equal 4, categories.to_a.count + assert_equal 3, categories.distinct.count + assert_equal 3, categories.to_a.uniq.size # Must uniq since instantiating with inner joins will get dupes end def test_cascaded_eager_association_loading_with_duplicated_includes diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 28bf48f4fd..8d3d6962fc 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1172,8 +1172,11 @@ class EagerAssociationTest < ActiveRecord::TestCase } end - test "works in combination with order(:symbol)" do - author = Author.includes(:posts).references(:posts).order(:name).where('posts.title IS NOT NULL').first + test "works in combination with order(:symbol) and reorder(:symbol)" do + author = Author.includes(:posts).references(:posts).order(:name).find_by('posts.title IS NOT NULL') + assert_equal authors(:bob), author + + author = Author.includes(:posts).references(:posts).reorder(:name).find_by('posts.title IS NOT NULL') assert_equal authors(:bob), author end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 712a770133..f77066e6ab 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -605,6 +605,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_table_alias + # FIXME: `references` has no impact on the aliases generated for the join + # query. The fact that we pass `:developers_projects_join` to `references` + # and that the SQL string contains `developers_projects_join` is merely a + # coincidence. assert_equal( 3, Developer.references(:developers_projects_join).merge( @@ -615,6 +619,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_join_with_group + # FIXME: `references` has no impact on the aliases generated for the join + # query. The fact that we pass `:developers_projects_join` to `references` + # and that the SQL string contains `developers_projects_join` is merely a + # coincidence. group = Developer.columns.inject([]) do |g, c| g << "developers.#{c.name}" g << "developers_projects_2.#{c.name}" @@ -643,7 +651,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_find_scoped_grouped_having - assert_equal 2, projects(:active_record).well_payed_salary_groups.size + assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 } end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ce51853bf3..4c0fa88917 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -45,6 +45,24 @@ class HasManyAssociationsTest < ActiveRecord::TestCase Client.destroyed_client_ids.clear end + def test_anonymous_has_many + developer = Class.new(ActiveRecord::Base) { + self.table_name = 'developers' + dev = self + + developer_project = Class.new(ActiveRecord::Base) { + self.table_name = 'developers_projects' + belongs_to :developer, :class => dev + } + has_many :developer_projects, :class => developer_project, :foreign_key => 'developer_id' + } + dev = developer.first + named = Developer.find(dev.id) + assert_operator dev.developer_projects.count, :>, 0 + assert_equal named.projects.map(&:id).sort, + dev.developer_projects.map(&:project_id).sort + end + def test_create_from_association_should_respect_default_scope car = Car.create(:name => 'honda') assert_equal 'honda', car.name diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 724d1cbbf8..dd8b426b25 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -28,7 +28,8 @@ require 'models/club' class HasManyThroughAssociationsTest < ActiveRecord::TestCase fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, :owners, :pets, :toys, :jobs, :references, :companies, :members, :author_addresses, - :subscribers, :books, :subscriptions, :developers, :categorizations, :essays + :subscribers, :books, :subscriptions, :developers, :categorizations, :essays, + :categories_posts # Dummies to force column loads so query counts are clean. def setup @@ -36,6 +37,101 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase Reader.create :person_id => 0, :post_id => 0 end + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def test_singleton_has_many_through + book = make_model "Book" + subscription = make_model "Subscription" + subscriber = make_model "Subscriber" + + subscriber.primary_key = 'nick' + subscription.belongs_to :book, class: book + subscription.belongs_to :subscriber, class: subscriber + + book.has_many :subscriptions, class: subscription + book.has_many :subscribers, through: :subscriptions, class: subscriber + + anonbook = book.first + namebook = Book.find anonbook.id + + assert_operator anonbook.subscribers.count, :>, 0 + anonbook.subscribers.each do |s| + assert_instance_of subscriber, s + end + assert_equal namebook.subscribers.map(&:id).sort, + anonbook.subscribers.map(&:id).sort + end + + def test_no_pk_join_table_append + lesson, _, student = make_no_pk_hm_t + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + sicp.students << ben + assert sicp.save! + end + + def test_no_pk_join_table_delete + lesson, lesson_student, student = make_no_pk_hm_t + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + louis = student.new(:name => "Louis Reasoner") + sicp.students << ben + sicp.students << louis + assert sicp.save! + + sicp.students.reload + assert_operator lesson_student.count, :>=, 2 + assert_no_difference('student.count') do + assert_difference('lesson_student.count', -2) do + sicp.students.destroy(*student.all.to_a) + end + end + end + + def test_no_pk_join_model_callbacks + lesson, lesson_student, student = make_no_pk_hm_t + + after_destroy_called = false + lesson_student.after_destroy do + after_destroy_called = true + end + + sicp = lesson.new(:name => "SICP") + ben = student.new(:name => "Ben Bitdiddle") + sicp.students << ben + assert sicp.save! + + sicp.students.reload + sicp.students.destroy(*student.all.to_a) + assert after_destroy_called, "after destroy should be called" + end + + def make_no_pk_hm_t + lesson = make_model 'Lesson' + student = make_model 'Student' + + lesson_student = make_model 'LessonStudent' + lesson_student.table_name = 'lessons_students' + + lesson_student.belongs_to :lesson, :class => lesson + lesson_student.belongs_to :student, :class => student + lesson.has_many :lesson_students, :class => lesson_student + lesson.has_many :students, :through => :lesson_students, :class => student + [lesson, lesson_student, student] + end + + def test_pk_is_not_required_for_join + post = Post.includes(:scategories).first + post2 = Post.includes(:categories).first + + assert_operator post.categories.length, :>, 0 + assert_equal post2.categories, post.categories + end + def test_include? person = Person.new post = Post.new @@ -654,7 +750,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase sarah = Person.create!(:first_name => 'Sarah', :primary_contact_id => people(:susan).id, :gender => 'F', :number1_fan_id => 1) john = Person.create!(:first_name => 'John', :primary_contact_id => sarah.id, :gender => 'M', :number1_fan_id => 1) assert_equal sarah.agents, [john] - assert_equal people(:susan).agents.flat_map(&:agents), people(:susan).agents_of_agents + assert_equal people(:susan).agents.map(&:agents).flatten, people(:susan).agents_of_agents end def test_associate_existing_with_nonstandard_primary_key_on_belongs_to diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 4fdf9a9643..9cd4db8dc9 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -505,6 +505,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_no_queries { company.account = nil } account = Account.find(2) assert_queries { company.account = account } + + assert_no_queries { Firm.new.account = account } end def test_has_one_assignment_triggers_save_on_change diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 71cf1237e8..2477e60e51 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -9,10 +9,24 @@ require 'models/rating' require 'models/comment' require 'models/car' require 'models/bulb' +require 'models/mixed_case_monkey' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars + def test_has_one_and_belongs_to_should_find_inverse_automatically_on_multiple_word_name + monkey_reflection = MixedCaseMonkey.reflect_on_association(:man) + man_reflection = Man.reflect_on_association(:mixed_case_monkey) + + assert_respond_to monkey_reflection, :has_inverse? + assert monkey_reflection.has_inverse?, "The monkey reflection should have an inverse" + assert_equal man_reflection, monkey_reflection.inverse_of, "The monkey reflection's inverse should be the man reflection" + + assert_respond_to man_reflection, :has_inverse? + assert man_reflection.has_inverse?, "The man reflection should have an inverse" + assert_equal monkey_reflection, man_reflection.inverse_of, "The man reflection's inverse should be the monkey reflection" + end + def test_has_one_and_belongs_to_should_find_inverse_automatically car_reflection = Car.reflect_on_association(:bulb) bulb_reflection = Bulb.reflect_on_association(:car) @@ -406,7 +420,7 @@ class InverseHasManyTests < ActiveRecord::TestCase interest = Interest.create!(man: man) man.interests.find(interest.id) - refute man.interests.loaded? + assert_not man.interests.loaded? end def test_raise_record_not_found_error_when_invalid_ids_are_passed diff --git a/activerecord/test/cases/associations/nested_through_associations_test.rb b/activerecord/test/cases/associations/nested_through_associations_test.rb index 9b1abc3b81..cf3c07845c 100644 --- a/activerecord/test/cases/associations/nested_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_through_associations_test.rb @@ -371,7 +371,7 @@ class NestedThroughAssociationsTest < ActiveRecord::TestCase prev_default_scope = Club.default_scopes [:includes, :preload, :joins, :eager_load].each do |q| - Club.default_scopes = [Club.send(q, :category)] + Club.default_scopes = [proc { Club.send(q, :category) }] assert_equal categories(:general), members(:groucho).reload.club_category end ensure diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index cd2b341826..e3b441280b 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -565,6 +565,14 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [topic_2, topic_1].sort, [topic_1, topic_2] end + def test_create_without_prepared_statement + topic = Topic.connection.unprepared_statement do + Topic.create(:title => 'foo') + end + + assert_equal topic, Topic.find(topic.id) + end + def test_comparison_with_different_objects topic = Topic.create category = Category.create(:name => "comparison") @@ -584,12 +592,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "changed", post.body end - unless current_adapter?(:MysqlAdapter) - def test_unicode_column_name - columns = Weird.columns_hash.keys - weird = Weird.create(:なまえ => 'たこ焼き仮面') - assert_equal 'たこ焼き仮面', weird.なまえ - end + def test_unicode_column_name + weird = Weird.create(:なまえ => 'たこ焼き仮面') + assert_equal 'たこ焼き仮面', weird.なまえ end def test_non_valid_identifier_column_name @@ -1327,6 +1332,35 @@ class BasicsTest < ActiveRecord::TestCase assert_equal 1, post.comments.length end + def test_marshal_between_processes + skip "fork isn't supported" unless Process.respond_to?(:fork) + + # Define a new model to ensure there are no caches + if self.class.const_defined?("Post", false) + flunk "there should be no post constant" + end + + self.class.const_set("Post", Class.new(ActiveRecord::Base) { + has_many :comments + }) + + rd, wr = IO.pipe + + ActiveRecord::Base.connection_handler.clear_all_connections! + + fork do + rd.close + post = Post.new + post.comments.build + wr.write Marshal.dump(post) + wr.close + end + + wr.close + assert Marshal.load rd.read + rd.close + end + def test_marshalling_new_record_round_trip_with_associations post = Post.new post.comments.build diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index f73e449610..1e6ccecfab 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -126,7 +126,7 @@ module ActiveRecord def test_dup_with_default_scope prev_default_scopes = Topic.default_scopes - Topic.default_scopes = [Topic.where(:approved => true)] + Topic.default_scopes = [proc { Topic.where(:approved => true) }] topic = Topic.new(:approved => false) assert !topic.dup.approved?, "should not be overridden by default scopes" ensure diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index 6101eb7b68..3ff22f222f 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -21,6 +21,11 @@ class FinderRespondToTest < ActiveRecord::TestCase assert_respond_to Topic, :find_by_title end + def test_should_respond_to_find_by_with_bang + ensure_topic_method_is_not_cached(:find_by_title!) + assert_respond_to Topic, :find_by_title! + end + def test_should_respond_to_find_by_two_attributes ensure_topic_method_is_not_cached(:find_by_title_and_author_name) assert_respond_to Topic, :find_by_title_and_author_name diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 51a8a13d04..1b9ef14ec9 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -45,17 +45,19 @@ class FinderTest < ActiveRecord::TestCase end def test_exists - assert Topic.exists?(1) - assert Topic.exists?("1") - assert Topic.exists?(title: "The First Topic") - assert Topic.exists?(heading: "The First Topic") - assert Topic.exists?(:author_name => "Mary", :approved => true) - assert Topic.exists?(["parent_id = ?", 1]) - assert !Topic.exists?(45) - assert !Topic.exists?(Topic.new) + assert_equal true, Topic.exists?(1) + assert_equal true, Topic.exists?("1") + assert_equal true, Topic.exists?(title: "The First Topic") + assert_equal true, Topic.exists?(heading: "The First Topic") + assert_equal true, Topic.exists?(:author_name => "Mary", :approved => true) + assert_equal true, Topic.exists?(["parent_id = ?", 1]) + assert_equal true, Topic.exists?(id: [1, 9999]) + + assert_equal false, Topic.exists?(45) + assert_equal false, Topic.exists?(Topic.new) begin - assert !Topic.exists?("foo") + assert_equal false, Topic.exists?("foo") rescue ActiveRecord::StatementInvalid # PostgreSQL complains about string comparison with integer field rescue Exception @@ -72,61 +74,62 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_returns_true_with_one_record_and_no_args - assert Topic.exists? + assert_equal true, Topic.exists? end def test_exists_returns_false_with_false_arg - assert !Topic.exists?(false) + assert_equal false, Topic.exists?(false) end # exists? should handle nil for id's that come from URLs and always return false # (example: Topic.exists?(params[:id])) where params[:id] is nil def test_exists_with_nil_arg - assert !Topic.exists?(nil) - assert Topic.exists? - assert !Topic.first.replies.exists?(nil) - assert Topic.first.replies.exists? + assert_equal false, Topic.exists?(nil) + assert_equal true, Topic.exists? + + assert_equal false, Topic.first.replies.exists?(nil) + assert_equal true, Topic.first.replies.exists? end # ensures +exists?+ runs valid SQL by excluding order value def test_exists_with_order - assert Topic.order(:id).distinct.exists? + assert_equal true, Topic.order(:id).distinct.exists? end def test_exists_with_includes_limit_and_empty_result - assert !Topic.includes(:replies).limit(0).exists? - assert !Topic.includes(:replies).limit(1).where('0 = 1').exists? + assert_equal false, Topic.includes(:replies).limit(0).exists? + assert_equal false, Topic.includes(:replies).limit(1).where('0 = 1').exists? end def test_exists_with_distinct_association_includes_and_limit author = Author.first - assert !author.unique_categorized_posts.includes(:special_comments).limit(0).exists? - assert author.unique_categorized_posts.includes(:special_comments).limit(1).exists? + assert_equal false, author.unique_categorized_posts.includes(:special_comments).limit(0).exists? + assert_equal true, author.unique_categorized_posts.includes(:special_comments).limit(1).exists? end def test_exists_with_distinct_association_includes_limit_and_order author = Author.first - assert !author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists? - assert author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists? + assert_equal false, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(0).exists? + assert_equal true, author.unique_categorized_posts.includes(:special_comments).order('comments.taggings_count DESC').limit(1).exists? end def test_exists_with_empty_table_and_no_args_given Topic.delete_all - assert !Topic.exists? + assert_equal false, Topic.exists? end def test_exists_with_aggregate_having_three_mappings existing_address = customers(:david).address - assert Customer.exists?(:address => existing_address) + assert_equal true, 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 => + assert_equal false, Customer.exists?(:address => Address.new(existing_address.street, existing_address.city, existing_address.country + "1")) - assert !Customer.exists?(:address => + assert_equal false, Customer.exists?(:address => Address.new(existing_address.street, existing_address.city + "1", existing_address.country)) - assert !Customer.exists?(:address => + assert_equal false, Customer.exists?(:address => Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index c07c9c3b74..e61deb1080 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -84,6 +84,12 @@ class FixturesTest < ActiveRecord::TestCase assert fixtures.detect { |f| f.name == 'collections' }, "no fixtures named 'collections' in #{fixtures.map(&:name).inspect}" end + def test_create_symbol_fixtures_is_deprecated + assert_deprecated do + ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT, :collections, :collections => 'Course') { Course.connection } + end + end + def test_attributes topics = create_fixtures("topics").first assert_equal("The First Topic", topics["first"]["title"]) @@ -190,11 +196,11 @@ class FixturesTest < ActiveRecord::TestCase end def test_empty_yaml_fixture - assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", 'Account', FIXTURES_ROOT + "/naked/yml/accounts") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "accounts", Account, FIXTURES_ROOT + "/naked/yml/accounts") end def test_empty_yaml_fixture_with_a_comment_in_it - assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', FIXTURES_ROOT + "/naked/yml/companies") + assert_not_nil ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, FIXTURES_ROOT + "/naked/yml/companies") end def test_nonexistent_fixture_file @@ -204,19 +210,19 @@ class FixturesTest < ActiveRecord::TestCase assert Dir[nonexistent_fixture_path+"*"].empty? assert_raise(Errno::ENOENT) do - ActiveRecord::FixtureSet.new( Account.connection, "companies", 'Company', nonexistent_fixture_path) + ActiveRecord::FixtureSet.new( Account.connection, "companies", Company, nonexistent_fixture_path) end end def test_dirty_dirty_yaml_file assert_raise(ActiveRecord::Fixture::FormatError) do - ActiveRecord::FixtureSet.new( Account.connection, "courses", 'Course', FIXTURES_ROOT + "/naked/yml/courses") + ActiveRecord::FixtureSet.new( Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses") end end def test_omap_fixtures assert_nothing_raised do - fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', 'Category', FIXTURES_ROOT + "/categories_ordered") + fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered") fixtures.each.with_index do |(name, fixture), i| assert_equal "fixture_no_#{i}", name @@ -263,6 +269,41 @@ class FixturesTest < ActiveRecord::TestCase end end +class HasManyThroughFixture < ActiveSupport::TestCase + def make_model(name) + Class.new(ActiveRecord::Base) { define_singleton_method(:name) { name } } + end + + def test_has_many_through + pt = make_model "ParrotTreasure" + parrot = make_model "Parrot" + treasure = make_model "Treasure" + + pt.table_name = "parrots_treasures" + pt.belongs_to :parrot, :class => parrot + pt.belongs_to :treasure, :class => treasure + + parrot.has_many :parrot_treasures, :class => pt + parrot.has_many :treasures, :through => :parrot_treasures + + parrots = File.join FIXTURES_ROOT, 'parrots' + + fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots + rows = fs.table_rows + assert_equal load_has_and_belongs_to_many['parrots_treasures'], rows['parrots_treasures'] + end + + def load_has_and_belongs_to_many + parrot = make_model "Parrot" + parrot.has_and_belongs_to_many :treasures + + parrots = File.join FIXTURES_ROOT, 'parrots' + + fs = ActiveRecord::FixtureSet.new parrot.connection, "parrots", parrot, parrots + fs.table_rows + end +end + if Account.connection.respond_to?(:reset_pk_sequence!) class FixturesResetPkSequenceTest < ActiveRecord::TestCase fixtures :accounts @@ -449,7 +490,7 @@ class OverRideFixtureMethodTest < ActiveRecord::TestCase end class CheckSetTableNameFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => 'Joke' + 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 @@ -532,7 +573,7 @@ class InvalidTableNameFixturesTest < ActiveRecord::TestCase end class CheckEscapedYamlFixturesTest < ActiveRecord::TestCase - set_fixture_class :funny_jokes => 'Joke' + 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 @@ -574,7 +615,7 @@ class FixturesBrokenRollbackTest < ActiveRecord::TestCase end private - def load_fixtures + def load_fixtures(config) raise 'argh' end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 2cad8a6d96..1b205d372f 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -242,6 +242,16 @@ module ActiveRecord add = @recorder.inverse_of :remove_belongs_to, [:table, :user] assert_equal [:add_reference, [:table, :user], nil], add end + + def test_invert_enable_extension + disable = @recorder.inverse_of :enable_extension, ['uuid-ossp'] + assert_equal [:disable_extension, ['uuid-ossp'], nil], disable + end + + def test_invert_disable_extension + enable = @recorder.inverse_of :disable_extension, ['uuid-ossp'] + assert_equal [:enable_extension, ['uuid-ossp'], nil], enable + end end end end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index ed080b2995..931caff727 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -321,12 +321,53 @@ class MigrationTest < ActiveRecord::TestCase assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name 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) + def test_proper_table_name_on_migrator + assert_deprecated do + assert_equal "table", ActiveRecord::Migrator.proper_table_name('table') + end + assert_deprecated do + assert_equal "table", ActiveRecord::Migrator.proper_table_name(:table) + end + assert_deprecated do + assert_equal "reminders", ActiveRecord::Migrator.proper_table_name(Reminder) + end + Reminder.reset_table_name + assert_deprecated do + assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + end + + # 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_deprecated do + assert_equal "prefix_reminders_suffix", ActiveRecord::Migrator.proper_table_name(Reminder) + end + 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_deprecated do + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name('table') + end + assert_deprecated do + assert_equal "prefix_table_suffix", ActiveRecord::Migrator.proper_table_name(:table) + end + end + + def test_proper_table_name_on_migration + migration = ActiveRecord::Migration.new + assert_equal "table", migration.proper_table_name('table') + assert_equal "table", migration.proper_table_name(:table) + assert_equal "reminders", migration.proper_table_name(Reminder) Reminder.reset_table_name - assert_equal Reminder.table_name, ActiveRecord::Migrator.proper_table_name(Reminder) + assert_equal Reminder.table_name, migration.proper_table_name(Reminder) # Use the model's own prefix/suffix if a model is given ActiveRecord::Base.table_name_prefix = "ARprefix_" @@ -334,7 +375,7 @@ class MigrationTest < ActiveRecord::TestCase 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) + assert_equal "prefix_reminders_suffix", migration.proper_table_name(Reminder) Reminder.table_name_prefix = '' Reminder.table_name_suffix = '' Reminder.reset_table_name @@ -343,8 +384,8 @@ class MigrationTest < ActiveRecord::TestCase 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) + assert_equal "prefix_table_suffix", migration.proper_table_name('table', migration.table_name_options) + assert_equal "prefix_table_suffix", migration.proper_table_name(:table, migration.table_name_options) end def test_rename_table_with_prefix_and_suffix diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb new file mode 100644 index 0000000000..43a69928b6 --- /dev/null +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -0,0 +1,144 @@ +require "cases/helper" +require "models/pirate" +require "models/bird" + +class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase + Pirate.has_many(:birds_with_add_load, + :class_name => "Bird", + :before_add => proc { |p,b| + @@add_callback_called << b + p.birds_with_add_load.to_a + }) + Pirate.has_many(:birds_with_add, + :class_name => "Bird", + :before_add => proc { |p,b| @@add_callback_called << b }) + + Pirate.accepts_nested_attributes_for(:birds_with_add_load, + :birds_with_add, + :allow_destroy => true) + + def setup + @@add_callback_called = [] + @pirate = Pirate.new.tap do |pirate| + pirate.catchphrase = "Don't call me!" + pirate.birds_attributes = [{:name => 'Bird1'},{:name => 'Bird2'}] + pirate.save! + end + @birds = @pirate.birds.to_a + end + + def bird_to_update + @birds[0] + end + + def bird_to_destroy + @birds[1] + end + + def existing_birds_attributes + @birds.map do |bird| + bird.attributes.slice("id","name") + end + end + + def new_birds + @pirate.birds_with_add.to_a - @birds + end + + def new_bird_attributes + [{'name' => "New Bird"}] + end + + def destroy_bird_attributes + [{'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + def update_new_and_destroy_bird_attributes + [{'id' => @birds[0].id.to_s, 'name' => 'New Name'}, + {'name' => "New Bird"}, + {'id' => bird_to_destroy.id.to_s, "_destroy" => true}] + end + + # Characterizing when :before_add callback is called + test ":before_add called for new bird when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + test ":before_add called for new bird when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = new_bird_attributes + assert_new_bird_with_callback_called + end + + def assert_new_bird_with_callback_called + assert_equal(1, new_birds.size) + assert_equal(new_birds, @@add_callback_called) + end + + test ":before_add not called for identical assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for identical assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = existing_birds_attributes + assert_callbacks_not_called + end + + test ":before_add not called for destroy assignment when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + test ":before_add not called for deletion assignment when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = destroy_bird_attributes + assert_callbacks_not_called + end + + def assert_callbacks_not_called + assert_empty new_birds + assert_empty @@add_callback_called + end + + # Ensuring that the records in the association target are updated, + # whether the association is loaded before or not + test "Assignment updates records in target when not loaded" do + assert_not @pirate.birds_with_add.loaded? + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test "Assignment updates records in target when loaded" do + @pirate.birds_with_add.load_target + @pirate.birds_with_add_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add) + end + + test("Assignment updates records in target when not loaded" + + " and callback loads target") do + assert_not @pirate.birds_with_add_load.loaded? + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + test("Assignment updates records in target when loaded" + + " and callback loads target") do + @pirate.birds_with_add_load.load_target + @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes + assert_assignment_affects_records_in_target(:birds_with_add_load) + end + + def assert_assignment_affects_records_in_target(association_name) + association = @pirate.send(association_name) + assert association.detect {|b| b == bird_to_update }.name_changed?, + 'Update record not updated' + assert association.detect {|b| b == bird_to_destroy }.marked_for_destruction?, + 'Destroy record not marked for destruction' + end +end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 85fbe01416..44b2064110 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -184,25 +184,6 @@ module ActiveRecord assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:binary)) end - def test_quote_binary_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - assert_equal "'foo'", @quoter.quote('lo\l', col) - end - - def test_quote_as_mb_chars_binary_column_with_string_to_binary - col = Class.new(FakeColumn) { - def string_to_binary(value) - 'foo' - end - }.new(:binary) - string = ActiveSupport::Multibyte::Chars.new('lo\l') - assert_equal "'foo'", @quoter.quote(string, col) - end - def test_string_with_crazy_column assert_equal "'lo\\\\l'", @quoter.quote('lo\l', FakeColumn.new(:foo)) end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index d333be3560..3e460fa3d6 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -81,6 +81,31 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_decorated_polymorphic_where + treasure_decorator = Struct.new(:model) do + def self.method_missing(method, *args, &block) + Treasure.send(method, *args, &block) + end + + def is_a?(klass) + model.is_a?(klass) + end + + def method_missing(method, *args, &block) + model.send(method, *args, &block) + end + end + + treasure = Treasure.new + treasure.id = 1 + decorated_treasure = treasure_decorator.new(treasure) + + expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1) + actual = PriceEstimate.where(estimate_of: decorated_treasure) + + assert_equal expected.to_sql, actual.to_sql + end + def test_aliased_attribute expected = Topic.where(heading: 'The First Topic') actual = Topic.where(title: 'The First Topic') diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index f99801c437..e77de19ac3 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -9,6 +9,10 @@ module ActiveRecord fixtures :posts, :comments, :authors class FakeKlass < Struct.new(:table_name, :name) + extend ActiveRecord::Delegation::DelegateCache + + inherited self + def self.connection Post.connection end @@ -76,8 +80,8 @@ module ActiveRecord end def test_table_name_delegates_to_klass - relation = Relation.new FakeKlass.new('foo'), :b - assert_equal 'foo', relation.table_name + relation = Relation.new FakeKlass.new('posts'), :b + assert_equal 'posts', relation.table_name end def test_scope_for_create @@ -177,14 +181,26 @@ module ActiveRecord end test 'merging a hash interpolates conditions' do - klass = stub_everything - klass.stubs(:sanitize_sql).with(['foo = ?', 'bar']).returns('foo = bar') + klass = Class.new(FakeKlass) do + def self.sanitize_sql(args) + raise unless args == ['foo = ?', 'bar'] + 'foo = bar' + end + end relation = Relation.new(klass, :b) relation.merge!(where: ['foo = ?', 'bar']) assert_equal ['foo = bar'], relation.where_values end + def test_merging_readonly_false + relation = Relation.new FakeKlass, :b + readonly_false_relation = relation.readonly(false) + # test merging in both directions + assert_equal false, relation.merge(readonly_false_relation).readonly_value + assert_equal false, readonly_false_relation.merge(relation).readonly_value + end + def test_relation_merging_with_merged_joins_as_symbols special_comments_with_ratings = SpecialComment.joins(:ratings) posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings) @@ -210,6 +226,9 @@ module ActiveRecord class RelationMutationTest < ActiveSupport::TestCase class FakeKlass < Struct.new(:table_name, :name) + extend ActiveRecord::Delegation::DelegateCache + inherited self + def arel_table Post.arel_table end @@ -217,6 +236,10 @@ module ActiveRecord def connection Post.connection end + + def relation_delegate_class(klass) + self.class.relation_delegate_class(klass) + end end def relation @@ -296,6 +319,15 @@ module ActiveRecord assert relation.reordering_value end + test '#reorder! with symbol prepends the table name' do + assert relation.reorder!(:name).equal?(relation) + node = relation.order_values.first + + assert node.ascending? + assert_equal :name, node.expr.name + assert_equal "posts", node.expr.relation.name + end + test 'reverse_order!' do assert relation.reverse_order!.equal?(relation) assert relation.reverse_order_value diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index bf9d395b2d..88a12c61df 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -139,6 +139,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal relation.to_a, Topic.select('a.*').from(relation, :a).to_a end + def test_finding_with_subquery_with_binds + relation = Post.first.comments + assert_equal relation.to_a, Comment.select('*').from(relation).to_a + assert_equal relation.to_a, Comment.select('subquery.*').from(relation).to_a + assert_equal relation.to_a, Comment.select('a.*').from(relation, :a).to_a + end + def test_finding_with_conditions assert_equal ["David"], Author.where(:name => 'David').map(&:name) assert_equal ['Mary'], Author.where(["name = ?", 'Mary']).map(&:name) @@ -170,6 +177,10 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:fourth).title, topics.first.title end + def test_order_with_hash_and_symbol_generates_the_same_sql + assert_equal Topic.order(:id).to_sql, Topic.order(:id => :asc).to_sql + end + def test_raising_exception_on_invalid_hash_params assert_raise(ArgumentError) { Topic.order(:name, "id DESC", :id => :DeSc) } end @@ -1340,6 +1351,24 @@ class RelationTest < ActiveRecord::TestCase assert_equal [], scope.references_values end + def test_automatically_added_reorder_references + scope = Post.reorder('comments.body') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('comments.body', 'yaks.body') + assert_equal %w(comments yaks), scope.references_values + + # Don't infer yaks, let's not go down that road again... + scope = Post.reorder('comments.body, yaks.body') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('comments.body asc') + assert_equal %w(comments), scope.references_values + + scope = Post.reorder('foo(comments.body)') + assert_equal [], scope.references_values + end + def test_presence topics = Topic.all diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index b49c27bc78..7fe065ee88 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -243,10 +243,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase myobj = MyObject.new('value1', 'value2') Topic.create(content: myobj) Topic.create(content: myobj) - - Topic.all.each do |topic| - type = topic.instance_variable_get("@columns_hash")["content"] - assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type) - end + type = Topic.column_types["content"] + assert !type.instance_variable_get("@column").is_a?(ActiveRecord::AttributeMethods::Serialization::Type) end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index abfc90474c..a5cb22aaf6 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -453,11 +453,6 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end end - - ensure - Topic.reset_column_information # reset the column information to get correct reading - Topic.connection.remove_column('topics', 'stuff') if Topic.column_names.include?('stuff') - Topic.reset_column_information # reset the column information again for other tests end def test_transactions_state_from_rollback diff --git a/activerecord/test/fixtures/tasks.yml b/activerecord/test/fixtures/tasks.yml index 402ca85faf..c38b32b0e5 100644 --- a/activerecord/test/fixtures/tasks.yml +++ b/activerecord/test/fixtures/tasks.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html first_task: id: 1 starting: 2005-03-30t06:30:00.00+01:00 diff --git a/activerecord/test/models/man.rb b/activerecord/test/models/man.rb index 4bff92dc98..f4d127730c 100644 --- a/activerecord/test/models/man.rb +++ b/activerecord/test/models/man.rb @@ -6,4 +6,5 @@ class Man < ActiveRecord::Base # These are "broken" inverse_of associations for the purposes of testing has_one :dirty_face, :class_name => 'Face', :inverse_of => :dirty_man has_many :secret_interests, :class_name => 'Interest', :inverse_of => :secret_man + has_one :mixed_case_monkey end diff --git a/activerecord/test/models/mixed_case_monkey.rb b/activerecord/test/models/mixed_case_monkey.rb index 763baefd91..4d37371777 100644 --- a/activerecord/test/models/mixed_case_monkey.rb +++ b/activerecord/test/models/mixed_case_monkey.rb @@ -1,3 +1,5 @@ class MixedCaseMonkey < ActiveRecord::Base self.primary_key = 'monkeyID' + + belongs_to :man end diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 77564ffad4..452d54580a 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -1,4 +1,10 @@ class Post < ActiveRecord::Base + class CategoryPost < ActiveRecord::Base + self.table_name = "categories_posts" + belongs_to :category + belongs_to :post + end + module NamedExtension def author 'lifo' @@ -72,6 +78,8 @@ class Post < ActiveRecord::Base has_many :special_comments_ratings, :through => :special_comments, :source => :ratings has_many :special_comments_ratings_taggings, :through => :special_comments_ratings, :source => :taggings + has_many :category_posts, :class_name => 'CategoryPost' + has_many :scategories, through: :category_posts, source: :category has_and_belongs_to_many :categories has_and_belongs_to_many :special_categories, :join_table => "categories_posts", :association_foreign_key => 'category_id' diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index be97b744c8..9ab81c73c8 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,34 @@ +* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods. + + Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases. + + *Anatoli Makarevich* + +* Fix ActiveSupport::Cache::FileStore#cleanup to no longer rely on missing each_key method. + + *Murray Steele* + +* Ensure that autoloaded constants in all-caps nestings are marked as + autoloaded. + + *Simon Coffey* + +* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '') + + *DHH* + +* Adds a new deprecation behaviour that raises an exception. Throwing this + line into +config/environments/development.rb+ + + ActiveSupport::Deprecation.behavior = :raise + + will cause the application to raise an +ActiveSupport::DeprecationException+ + on deprecations. + + Use this for aggressive deprecation cleanups. + + *Xavier Noria* + * Remove 'cow' => 'kine' irregular inflection from default inflections. *Andrew White* diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 472f23c1c5..10d39463ec 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -33,7 +33,8 @@ module ActiveSupport # Premptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) - each_key(options) do |key| + search_dir(cache_path) do |fname| + key = file_path_key(fname) entry = read_entry(key, options) delete_entry(key, options) if entry && entry.expired? end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index b979521c99..34ac91334a 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -124,7 +124,6 @@ module ActiveSupport protected - # See https://gist.github.com/ssimeonov/6047200 PER_ENTRY_OVERHEAD = 240 def cached_size(key, entry) diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 5c738572a8..c3aac31323 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -574,7 +574,7 @@ module ActiveSupport # # set_callback :save, :before_meth # - # The callback can specified as a symbol naming an instance method; as a + # The callback can be specified as a symbol naming an instance method; as a # proc, lambda, or block; as a string to be instance evaluated; or as an # object that responds to a certain method determined by the <tt>:scope</tt> # argument to +define_callback+. diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index af048d0c85..c60e833441 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -129,7 +129,7 @@ class Date options.fetch(:day, day) ) end - + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. def compare_with_coercion(other) if other.is_a?(Time) diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index fbf2877117..8930376ac8 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -235,6 +235,7 @@ module ActiveSupport value.map! { |i| deep_to_h(i) } value.length > 1 ? value : value.first end + end end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index bca3800344..182e74d9e1 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -192,8 +192,7 @@ class Module _ = #{to} # _ = client _.#{method}(#{definition}) # _.name(*args, &block) rescue NoMethodError => e # rescue NoMethodError => e - location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, '#{method_prefix}#{method}'] # location = "%s:%d:in `%s'" % [__FILE__, __LINE__ - 2, 'customer_name'] - if _.nil? && e.backtrace.first == location # if _.nil? && e.backtrace.first == location + if _.nil? && e.name == :#{method} # if _.nil? && e.name == :name #{exception} # # add helpful message to the exception else # else raise # raise diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index a4cacaf789..49c0df6026 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -20,6 +20,16 @@ class String self end + # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). + def remove(pattern) + gsub pattern, '' + end + + # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). + def remove!(pattern) + gsub! pattern, '' + end + # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: # # 'Once upon a time in a world far far away'.truncate(27) diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb index 5481766f10..e80f442973 100644 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -23,14 +23,14 @@ class Thread # for the fiber local. The fiber is executed in the same thread, so the # thread local values are available. def thread_variable_get(key) - locals[key.to_sym] + _locals[key.to_sym] end # Sets a thread local with +key+ to +value+. Note that these are local to # threads, and not to fibers. Please see Thread#thread_variable_get for # more information. def thread_variable_set(key, value) - locals[key.to_sym] = value + _locals[key.to_sym] = value end # Returns an an array of the names of the thread-local variables (as Symbols). @@ -45,7 +45,7 @@ class Thread # Note that these are not fiber local variables. Please see Thread#thread_variable_get # for more details. def thread_variables - locals.keys + _locals.keys end # Returns <tt>true</tt> if the given string (or symbol) exists as a @@ -59,16 +59,21 @@ class Thread # Note that these are not fiber local variables. Please see Thread#thread_variable_get # for more details. def thread_variable?(key) - locals.has_key?(key.to_sym) + _locals.has_key?(key.to_sym) + end + + def freeze + _locals.freeze + super end private - def locals - if defined?(@locals) - @locals + def _locals + if defined?(@_locals) + @_locals else - LOCK.synchronize { @locals ||= {} } + LOCK.synchronize { @_locals ||= {} } end end end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 73559bfe0e..db9f5d4baa 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -459,7 +459,7 @@ module ActiveSupport #:nodoc: if loaded.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else - require_or_load(expanded) + require_or_load(expanded, qualified_name) raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) return from_mod.const_get(const_name) end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 0281c9222e..ab16977bda 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -32,7 +32,7 @@ module ActiveSupport # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = '4.1', gem_name = 'Rails') + def initialize(deprecation_horizon = '4.2', gem_name = 'Rails') self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 90db180124..328b8c320a 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,14 +1,24 @@ require "active_support/notifications" module ActiveSupport + class DeprecationException < StandardError + end + class Deprecation # Default warning behaviors per Rails.env. DEFAULT_BEHAVIORS = { - :stderr => Proc.new { |message, callstack| + raise: ->(message, callstack) { + e = DeprecationException.new(message) + e.set_backtrace(callstack) + raise e + }, + + stderr: ->(message, callstack) { $stderr.puts(message) $stderr.puts callstack.join("\n ") if debug }, - :log => Proc.new { |message, callstack| + + log: ->(message, callstack) { logger = if defined?(Rails) && Rails.logger Rails.logger @@ -19,11 +29,13 @@ module ActiveSupport logger.warn message logger.debug callstack.join("\n ") if debug }, - :notify => Proc.new { |message, callstack| + + notify: ->(message, callstack) { ActiveSupport::Notifications.instrument("deprecation.rails", :message => message, :callstack => callstack) }, - :silence => Proc.new { |message, callstack| } + + silence: ->(message, callstack) {}, } module Behavior @@ -40,6 +52,7 @@ module ActiveSupport # # Available behaviors: # + # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>. # [+stderr+] Log all deprecation warnings to +$stderr+. # [+log+] Log all deprecation warnings to +Rails.logger+. # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. @@ -52,7 +65,7 @@ module ActiveSupport # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = proc { |message, callstack| + # ActiveSupport::Deprecation.behavior = ->(message, callstack) { # # custom stuff # } def behavior=(behavior) diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 2cb1f408b6..87b6407038 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -70,13 +70,11 @@ module ActiveSupport alias :until :ago def inspect #:nodoc: - consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h } - parts = [:years, :months, :days, :minutes, :seconds].map do |length| - n = consolidated[length] - "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero? - end.compact - parts = ["0 seconds"] if parts.empty? - parts.to_sentence(:locale => :en) + parts. + reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }. + sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}. + map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}. + to_sentence(:locale => :en) end def as_json(options = nil) #:nodoc: diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index d063fcb49c..ffdb7b53c4 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -36,7 +36,7 @@ module ActiveSupport # string. # # If passed an optional +locale+ parameter, the word will be - # pluralized using rules defined for that language. By default, + # singularized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # # 'posts'.singularize # => "post" diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 30833a4cb1..21de09c1cc 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -13,8 +13,8 @@ module ActiveSupport # # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") # => {"team" => "rails", "players" => "36"} - def decode(json, proc = nil, options = {}) - data = ::JSON.load(json, proc, options) + def decode(json, options = {}) + data = ::JSON.parse(json, options.merge(create_additions: false, quirks_mode: true)) if ActiveSupport.parse_json_times convert_dates_from(data) else diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 0c9a729ce5..3a244b34b5 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -54,10 +54,11 @@ module ActiveSupport @transaction_id = transaction_id @end = ending @children = [] + @duration = nil end def duration - 1000.0 * (self.end - time) + @duration ||= 1000.0 * (self.end - time) end def <<(event) diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index e03bb4ca0f..a33e2c58a9 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -41,7 +41,7 @@ module ActiveSupport end # +InheritableOptions+ provides a constructor to build an +OrderedOptions+ - # hash inherited from the another hash. + # hash inherited from another hash. # # Use this if you already have some hash and you want to create a new one based on it. # diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 939bd7bfa8..65de48a7f6 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -24,3 +24,13 @@ Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true + +# Skips the current run on Rubinius using Minitest::Assertions#skip +def rubinius_skip(message = '') + skip message if RUBY_ENGINE == 'rbx' +end + +# Skips the current run on JRuby using Minitest::Assertions#skip +def jruby_skip(message = '') + skip message if RUBY_ENGINE == 'jruby' +end diff --git a/activesupport/test/autoloading_fixtures/html/some_class.rb b/activesupport/test/autoloading_fixtures/html/some_class.rb new file mode 100644 index 0000000000..b43d15d891 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/html/some_class.rb @@ -0,0 +1,4 @@ +module HTML + class SomeClass + end +end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index df570d485a..16d087ab9d 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -709,6 +709,18 @@ class FileStoreTest < ActiveSupport::TestCase @cache.send(:read_entry, "winston", {}) assert @buffer.string.present? end + + def test_cleanup_removes_all_expired_entries + time = Time.now + @cache.write('foo', 'bar', expires_in: 10) + @cache.write('baz', 'qux') + @cache.write('quux', 'corge', expires_in: 20) + Time.stubs(:now).returns(time + 15) + @cache.cleanup + assert_not @cache.exist?('foo') + assert @cache.exist?('baz') + assert @cache.exist?('quux') + end end class MemoryStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index a5987044b9..b386e55d6c 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'bigdecimal' require 'active_support/core_ext/big_decimal' class BigDecimalTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 5e3987265b..ed267cf4b9 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -27,6 +27,7 @@ class DurationTest < ActiveSupport::TestCase def test_equals assert 1.day == 1.day assert 1.day == 1.day.to_i + assert 1.day.to_i == 1.day assert !(1.day == 'foo') end @@ -37,6 +38,8 @@ class DurationTest < ActiveSupport::TestCase assert_equal '6 months and -2 days', (6.months - 2.days).inspect assert_equal '10 seconds', 10.seconds.inspect assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect + assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect + assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect assert_equal '7 days', 1.week.inspect assert_equal '14 days', 1.fortnight.inspect end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 12126fb9ef..3cb66d4eec 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -11,13 +11,6 @@ require 'active_support/core_ext/string/strip' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/indent' -module Ace - module Base - class Case - end - end -end - class StringInflectionsTest < ActiveSupport::TestCase include InflectorTestCases include ConstantizeTestCases @@ -277,6 +270,11 @@ class StringInflectionsTest < ActiveSupport::TestCase def test_truncate_should_not_be_html_safe assert !"Hello World!".truncate(12).html_safe? end + + def test_remove + assert_equal "Summer", "Fast Summer".remove(/Fast /) + assert_equal "Summer", "Fast Summer".remove!(/Fast /) + end def test_constantize run_constantize_tests_on do |string| diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb index 230c1203ad..54d2dcd8dd 100644 --- a/activesupport/test/core_ext/thread_test.rb +++ b/activesupport/test/core_ext/thread_test.rb @@ -63,7 +63,18 @@ class ThreadExt < ActiveSupport::TestCase end end + def test_thread_variable_frozen_after_set + t = Thread.new { }.join + t.thread_variable_set :foo, "bar" + t.freeze + assert_raises(RuntimeError) do + t.thread_variable_set(:baz, "qux") + end + end + def test_thread_variable_security + rubinius_skip "$SAFE is not supported on Rubinius." + t = Thread.new { sleep } assert_raises(SecurityError) do diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 68b6cc6e8c..9b84e75902 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -647,6 +647,14 @@ class DependenciesTest < ActiveSupport::TestCase Object.class_eval { remove_const :E } end + def test_constants_in_capitalized_nesting_marked_as_autoloaded + with_autoloading_fixtures do + ActiveSupport::Dependencies.load_missing_constant(HTML, "SomeClass") + + assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass") + end + end + def test_unloadable with_autoloading_fixtures do Object.const_set :M, Module.new diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 9616e42f44..9674851b9d 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -98,6 +98,22 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/foo=nil/, @b) end + def test_raise_behaviour + ActiveSupport::Deprecation.behavior = :raise + + message = 'Revise this deprecated stuff now!' + callstack = %w(foo bar baz) + + begin + ActiveSupport::Deprecation.behavior.first.call(message, callstack) + rescue ActiveSupport::DeprecationException => e + assert_equal message, e.message + assert_equal callstack, e.backtrace + else + flunk 'the :raise deprecation behaviour should raise the expected exception' + end + end + def test_default_stderr_behavior ActiveSupport::Deprecation.behavior = :stderr behavior = ActiveSupport::Deprecation.behavior.first diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 1bb2d7e920..32739d45a9 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -230,25 +230,35 @@ class InflectorTest < ActiveSupport::TestCase end end +# FIXME: get following tests to pass on jruby, currently skipped +# +# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes +# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby +# causing our tests to error out. +# related bug http://jira.codehaus.org/browse/JRUBY-7194 def test_parameterize + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_and_normalize + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizedAndNormalized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_with_custom_separator + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end def test_parameterize_with_multi_character_separator + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 99c5f2d1ec..e9780b36e4 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -4,6 +4,12 @@ require 'active_support/json' require 'active_support/time' class TestJSONDecoding < ActiveSupport::TestCase + class Foo + def self.json_create(object) + "Foo" + end + end + TESTS = { %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}}, %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}}, @@ -52,7 +58,17 @@ class TestJSONDecoding < ActiveSupport::TestCase # tests escaping of "\n" char with Yaml backend %q({"a":"\n"}) => {"a"=>"\n"}, %q({"a":"\u000a"}) => {"a"=>"\n"}, - %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"} + %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}, + # prevent json unmarshalling + %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"}, + # json "fragments" - these are invalid JSON, but ActionPack relies on this + %q("a string") => "a string", + %q(1.1) => 1.1, + %q(1) => 1, + %q(-1) => -1, + %q(true) => true, + %q(false) => false, + %q(null) => nil } TESTS.each_with_index do |(json, expected), index| @@ -76,7 +92,16 @@ class TestJSONDecoding < ActiveSupport::TestCase end def test_failed_json_decoding + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } + end + + def test_cannot_force_json_unmarshalling + encodeded = %q({"json_class":"TestJSONDecoding::Foo"}) + decodeded = {"json_class"=>"TestJSONDecoding::Foo"} + assert_equal decodeded, ActiveSupport::JSON.decode(encodeded, create_additions: true) end end diff --git a/guides/assets/images/akshaysurve.jpg b/guides/assets/images/akshaysurve.jpg Binary files differnew file mode 100644 index 0000000000..cfc3333958 --- /dev/null +++ b/guides/assets/images/akshaysurve.jpg diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb new file mode 100644 index 0000000000..693bc320b3 --- /dev/null +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -0,0 +1,42 @@ +# Activate the gem you are reporting the issue against. +gem 'rails', '4.0.0' + +require 'rails' +require 'action_controller/railtie' + +class TestApp < Rails::Application + config.root = File.dirname(__FILE__) + config.session_store :cookie_store, key: 'cookie_store_key' + config.secret_token = 'secret_token' + config.secret_key_base = 'secret_key_base' + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + routes.draw do + get '/' => 'test#index' + end +end + +class TestController < ActionController::Base + def index + render text: 'Home' + end +end + +require 'minitest/autorun' +require 'rack/test' + +class BugTest < MiniTest::Unit::TestCase + include Rack::Test::Methods + + def test_returns_success + get '/' + assert last_response.ok? + end + + private + def app + Rails.application + end +end
\ No newline at end of file diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb new file mode 100644 index 0000000000..375ed9bc5d --- /dev/null +++ b/guides/bug_report_templates/action_controller_master.rb @@ -0,0 +1,51 @@ +unless File.exists?('Gemfile') + File.write('Gemfile', <<-GEMFILE) + source 'https://rubygems.org' + gem 'rails', github: 'rails/rails' + GEMFILE + + system 'bundle' +end + +require 'bundler' +Bundler.setup(:default) + +require 'rails' +require 'action_controller/railtie' + +class TestApp < Rails::Application + config.root = File.dirname(__FILE__) + config.session_store :cookie_store, key: 'cookie_store_key' + config.secret_token = 'secret_token' + config.secret_key_base = 'secret_key_base' + + config.logger = Logger.new($stdout) + Rails.logger = config.logger + + routes.draw do + get '/' => 'test#index' + end +end + +class TestController < ActionController::Base + def index + render text: 'Home' + end +end + +require 'minitest/autorun' +require 'rack/test' + +class BugTest < Minitest::Test + include Rack::Test::Methods + + def test_returns_success + get '/' + assert last_response.ok? + end + + private + def app + Rails.application + end +end
\ No newline at end of file diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 68069cdd8d..29dedd88d0 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -36,7 +36,7 @@ class Comment < ActiveRecord::Base belongs_to :post end -class BugTest < MiniTest::Unit::TestCase +class BugTest < Minitest::Test def test_association_stuff post = Post.create! post.comments << Comment.create! diff --git a/guides/code/getting_started/test/fixtures/comments.yml b/guides/code/getting_started/test/fixtures/comments.yml index 0cd36069e4..9e409d8a61 100644 --- a/guides/code/getting_started/test/fixtures/comments.yml +++ b/guides/code/getting_started/test/fixtures/comments.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: commenter: MyString diff --git a/guides/code/getting_started/test/fixtures/posts.yml b/guides/code/getting_started/test/fixtures/posts.yml index 617a24b858..46b01c3bb4 100644 --- a/guides/code/getting_started/test/fixtures/posts.yml +++ b/guides/code/getting_started/test/fixtures/posts.yml @@ -1,4 +1,4 @@ -# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html one: title: MyString diff --git a/guides/rails_guides.rb b/guides/rails_guides.rb index ce409868ca..33975718c9 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -39,6 +39,25 @@ ERROR exit 1 end +begin + require 'nokogiri' +rescue LoadError + # This can happen if doc:guides is executed in an application. + $stderr.puts('Generating guides requires Nokogiri.') + $stderr.puts(<<ERROR) if bundler? +Please add + + gem 'nokogiri' + +to the Gemfile, run + + bundle install + +and try again. +ERROR + exit 1 +end + require 'rails_guides/markdown' require "rails_guides/generator" RailsGuides::Generator.new.generate diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 0f05cc6b85..c2002fb8fa 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -237,7 +237,7 @@ If you're one of the people who has always been bothered by the special-case nam ### HTTP Digest Authentication Support -Rails now has built-in support for HTTP digest authentication. To use it, you call `authenticate_or_request_with_http_digest` with a block that returns the user’s password (which is then hashed and compared against the transmitted credentials): +Rails now has built-in support for HTTP digest authentication. To use it, you call `authenticate_or_request_with_http_digest` with a block that returns the user's password (which is then hashed and compared against the transmitted credentials): ```ruby class PostsController < ApplicationController @@ -451,11 +451,11 @@ select(:post, :category, Post::CATEGORIES, :disabled => 'private') returns ```html -<select name=“post[category]“> +<select name="post[category]"> <option>story</option> <option>joke</option> <option>poem</option> -<option disabled=“disabled“>private</option> +<option disabled="disabled">private</option> </select> ``` @@ -606,7 +606,7 @@ A few pieces of older code are deprecated in this release: * If you're one of the (fairly rare) Rails developers who deploys in a fashion that depends on the inspector, reaper, and spawner scripts, you'll need to know that those scripts are no longer included in core Rails. If you need them, you'll be able to pick up copies via the [irs_process_scripts](http://github.com/rails/irs_process_scripts/tree) plugin. * `render_component` goes from "deprecated" to "nonexistent" in Rails 2.3. If you still need it, you can install the [render_component plugin](http://github.com/rails/render_component/tree/master.) * Support for Rails components has been removed. -* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There’s a new request_profiler plugin that you can install to get the exact same functionality back. +* If you were one of the people who got used to running `script/performance/request` to look at performance based on integration tests, you need to learn a new trick: that script has been removed from core Rails now. There's a new request_profiler plugin that you can install to get the exact same functionality back. * `ActionController::Base#session_enabled?` is deprecated because sessions are lazy-loaded now. * The `:digest` and `:secret` options to `protect_from_forgery` are deprecated and have no effect. * Some integration test helpers have been removed. `response.headers["Status"]` and `headers["Status"]` will no longer return anything. Rack does not allow "Status" in its return headers. However you can still use the `status` and `status_message` helpers. `response.headers["cookie"]` and `headers["cookie"]` will no longer return any CGI cookies. You can inspect `headers["Set-Cookie"]` to see the raw cookie header or use the `cookies` helper to get a hash of the cookies sent to the client. diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 8be7a86d20..c4ca1e921f 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -116,7 +116,7 @@ Documentation Railties -------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railties/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/railties/CHANGELOG.md) for detailed changes. ### Notable changes @@ -139,7 +139,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt Action Mailer ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionmailer/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionmailer/CHANGELOG.md) for detailed changes. ### Notable changes @@ -148,7 +148,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio Active Model ------------ -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activemodel/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for detailed changes. ### Notable changes @@ -161,7 +161,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ Active Support -------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activesupport/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activesupport/CHANGELOG.md) for detailed changes. ### Notable changes @@ -173,24 +173,37 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * `Object#try` will now return nil instead of raise a NoMethodError if the receiving object does not implement the method, but you can still get the old behavior by using the new `Object#try!`. +* `String#to_date` now raises `Argument Error: invalid date` instead of `NoMethodError: undefined method 'div' for nil:NilClass` + when given an invalid date. It is now the same as `Date.parse`, and it accepts more invalid dates than 3.x, such as: + + ``` + # ActiveSupport 3.x + "asdf".to_date # => NoMethodError: undefined method `div' for nil:NilClass + "333".to_date # => NoMethodError: undefined method `div' for nil:NilClass + + # ActiveSupport 4 + "asdf".to_date # => ArgumentError: invalid date + "333".to_date # => Fri, 29 Nov 2013 + ``` + ### Deprecations * Deprecate `ActiveSupport::TestCase#pending` method, use `skip` from MiniTest instead. -* ActiveSupport::Benchmarkable#silence has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. +* `ActiveSupport::Benchmarkable#silence` has been deprecated due to its lack of thread safety. It will be removed without replacement in Rails 4.1. * `ActiveSupport::JSON::Variable` is deprecated. Define your own `#as_json` and `#encode_json` methods for custom JSON string literals. -* Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). +* Deprecates the compatibility method `Module#local_constant_names`, use `Module#local_constants` instead (which returns symbols). -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. +* `BufferedLogger` is deprecated. Use `ActiveSupport::Logger`, or the logger from Ruby standard library. * Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` Action Pack ----------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actionpack/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/actionpack/CHANGELOG.md) for detailed changes. ### Notable changes @@ -202,7 +215,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/actio Active Record ------------- -Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activerecord/CHANGELOG.md) for detailed changes. +Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/activerecord/CHANGELOG.md) for detailed changes. ### Notable changes @@ -253,9 +266,9 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * `find_all_by_...` can be rewritten using `where(...)`. * `find_last_by_...` can be rewritten using `where(...).last`. * `scoped_by_...` can be rewritten using `where(...)`. - * `find_or_initialize_by_...` can be rewritten using `where(...).first_or_initialize`. - * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)` or `where(...).first_or_create`. - * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)` or `where(...).first_or_create!`. + * `find_or_initialize_by_...` can be rewritten using `find_or_initialize_by(...)`. + * `find_or_create_by_...` can be rewritten using `find_or_create_by(...)`. + * `find_or_create_by_...!` can be rewritten using `find_or_create_by!(...)`. Credits ------- diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index ecaee02cce..8dfecd0190 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -348,7 +348,7 @@ All session stores use a cookie to store a unique ID for each session (you must For most stores, this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store - the CookieStore - which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof. And it is also encrypted so anyone with access to it can't read its contents. (Rails will not accept it if it has been edited). -The CookieStore can store around 4kB of data — much less than the others — but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. +The CookieStore can store around 4kB of data - much less than the others - but this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error. If your user sessions don't store critical data or don't need to be around for long periods (for instance if you just use the flash for messaging), you can consider using ActionDispatch::Session::CacheStore. This will store sessions using the cache implementation you have configured for your application. The advantage of this is that you can use your existing cache infrastructure for storing sessions without requiring any additional setup or administration. The downside, of course, is that the sessions will be ephemeral and could disappear at any time. @@ -538,7 +538,7 @@ end Cookies ------- -Your application can store small amounts of data on the client — called cookies — that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which — much like the `session` — works like a hash: +Your application can store small amounts of data on the client - called cookies - that will be persisted across requests and even sessions. Rails provides easy access to cookies via the `cookies` method, which - much like the `session` - works like a hash: ```ruby class CommentsController < ApplicationController @@ -808,11 +808,11 @@ class AdminsController < ApplicationController private - def authenticate - authenticate_or_request_with_http_digest do |username| - USERS[username] + def authenticate + authenticate_or_request_with_http_digest do |username| + USERS[username] + end end - end end ``` @@ -839,13 +839,13 @@ class ClientsController < ApplicationController private - def generate_pdf(client) - Prawn::Document.new do - text client.name, align: :center - text "Address: #{client.address}" - text "Email: #{client.email}" - end.render - end + def generate_pdf(client) + Prawn::Document.new do + text client.name, align: :center + text "Address: #{client.address}" + text "Email: #{client.email}" + end.render + end end ``` @@ -1048,9 +1048,9 @@ class ApplicationController < ActionController::Base private - def record_not_found - render text: "404 Not Found", status: 404 - end + def record_not_found + render text: "404 Not Found", status: 404 + end end ``` @@ -1062,10 +1062,10 @@ class ApplicationController < ActionController::Base private - def user_not_authorized - flash[:error] = "You don't have access to this section." - redirect_to :back - end + def user_not_authorized + flash[:error] = "You don't have access to this section." + redirect_to :back + end end class ClientsController < ApplicationController @@ -1079,10 +1079,10 @@ class ClientsController < ApplicationController private - # If the user is not authorized, just throw the exception. - def check_authorization - raise User::NotAuthorized unless current_user.admin? - end + # If the user is not authorized, just throw the exception. + def check_authorization + raise User::NotAuthorized unless current_user.admin? + end end ``` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index bf34799eb3..93a2b89ede 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -569,25 +569,25 @@ class UserMailer < ActionMailer::Base private - def set_delivery_options - # You have access to the mail instance, - # @business and @user instance variables here - if @business && @business.has_smtp_settings? - mail.delivery_method.settings.merge!(@business.smtp_settings) + def set_delivery_options + # You have access to the mail instance, + # @business and @user instance variables here + if @business && @business.has_smtp_settings? + mail.delivery_method.settings.merge!(@business.smtp_settings) + end end - end - def prevent_delivery_to_guests - if @user && @user.guest? - mail.perform_deliveries = false + def prevent_delivery_to_guests + if @user && @user.guest? + mail.perform_deliveries = false + end end - end - def set_business_headers - if @business - headers["X-SMTPAPI-CATEGORY"] = @business.code + def set_business_headers + if @business + headers["X-SMTPAPI-CATEGORY"] = @business.code + end end - end end ``` diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 6fce5a1dc2..5cda104138 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -152,7 +152,7 @@ By default, Rails will compile each template to a method in order to render it. ### Partials -Partial templates – usually just called "partials" – are another device for breaking the rendering process into more manageable chunks. With partials, you can extract pieces of code from your templates to separate files and also reuse them throughout your templates. +Partial templates - usually just called "partials" - are another device for breaking the rendering process into more manageable chunks. With partials, you can extract pieces of code from your templates to separate files and also reuse them throughout your templates. #### Naming Partials @@ -1520,6 +1520,72 @@ number_with_precision(111.2345) # => 111.235 number_with_precision(111.2345, 2) # => 111.23 ``` +### SanitizeHelper + +The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. + +#### sanitize + +This sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed. + +```ruby +sanitize @article.body +``` + +If either the :attributes or :tags options are passed, only the mentioned tags and attributes are allowed and nothing else. + +```ruby +sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style) +``` + +To change defaults for multiple uses, for example adding table tags to the default: + +```ruby +class Application < Rails::Application + config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' +end +``` + +#### sanitize_css(style) + +Sanitizes a block of CSS code. + +#### strip_links(html) +Strips all link tags from text leaving just the link text. + +```ruby +strip_links("<a href="http://rubyonrails.org">Ruby on Rails</a>") +# => Ruby on Rails +``` + +```ruby +strip_links("emails to <a href="mailto:me@email.com">me@email.com</a>.") +# => emails to me@email.com. +``` + +```ruby +strip_links('Blog: <a href="http://myblog.com/">Visit</a>.') +# => Blog: Visit. +``` + +#### strip_tags(html) + +Strips all HTML tags from the html, including comments. +This uses the html-scanner tokenizer and so its HTML parsing ability is limited by that of html-scanner. + +```ruby +strip_tags("Strip <i>these</i> tags!") +# => Strip these tags! +``` + +```ruby +strip_tags("<b>Bold</b> no more! <a href='more.html'>See more</a>") +# => Bold no more! See more +``` + +NB: The output may still contain unescaped '<', '>', '&' characters and confuse browsers. + + Localized Views --------------- diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index 556c2544ff..ad08cb01f7 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -86,7 +86,7 @@ by underscores. Examples: | ------------- | -------------- | | `Post` | `posts` | | `LineItem` | `line_items` | -| `Deer` | `deer` | +| `Deer` | `deers` | | `Mouse` | `mice` | | `Person` | `people` | @@ -181,18 +181,18 @@ definition: ```ruby class FunnyJoke < ActiveSupport::TestCase - set_fixture_class funny_jokes: 'Joke' + set_fixture_class funny_jokes: Joke fixtures :funny_jokes ... end ``` It's also possible to override the column that should be used as the table's -primary key using the `ActiveRecord::Base.set_primary_key` method: +primary key using the `ActiveRecord::Base.primary_key=` method: ```ruby class Product < ActiveRecord::Base - set_primary_key "product_id" + self.primary_key = "product_id" end ``` diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index df1dd22971..aa2ce99f6d 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -35,11 +35,11 @@ class User < ActiveRecord::Base before_validation :ensure_login_has_a_value protected - def ensure_login_has_a_value - if login.nil? - self.login = email unless email.blank? + def ensure_login_has_a_value + if login.nil? + self.login = email unless email.blank? + end end - end end ``` @@ -65,13 +65,13 @@ class User < ActiveRecord::Base after_validation :set_location, on: [ :create, :update ] protected - def normalize_name - self.name = self.name.downcase.titleize - end + def normalize_name + self.name = self.name.downcase.titleize + end - def set_location - self.location = LocationService.query(self) - end + def set_location + self.location = LocationService.query(self) + end end ``` @@ -180,7 +180,7 @@ NOTE: The `find_by_*` and `find_by_*!` methods are dynamic finders generated aut Skipping Callbacks ------------------ -Just as with validations, it is also possible to skip callbacks. These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. +Just as with validations, it is also possible to skip callbacks by using the following methods: * `decrement` * `decrement_counter` @@ -195,6 +195,8 @@ Just as with validations, it is also possible to skip callbacks. These methods s * `update_all` * `update_counters` +These methods should be used with caution, however, because important business rules and application logic may be kept in callbacks. Bypassing them without understanding the potential implications may lead to invalid data. + Halting Execution ----------------- diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 7fe9b8b4af..28013beeae 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -514,13 +514,7 @@ SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5)) Post.where.not(author: author) ``` -In other words, this query can be generated by calling `where` with no argument, -then immediately chain with `not` passing `where` conditions. This will generate -SQL code like this: - -```sql -SELECT * FROM posts WHERE (author_id != 1) -``` +In other words, this query can be generated by calling `where` with no argument, then immediately chain with `not` passing `where` conditions. Ordering -------- @@ -530,12 +524,18 @@ To retrieve records from the database in a specific order, you can use the `orde For example, if you're getting a set of records and want to order them in ascending order by the `created_at` field in your table: ```ruby +Client.order(:created_at) +# OR Client.order("created_at") ``` You could specify `ASC` or `DESC` as well: ```ruby +Client.order(created_at: :desc) +# OR +Client.order(created_at: :asc) +# OR Client.order("created_at DESC") # OR Client.order("created_at ASC") @@ -544,6 +544,10 @@ Client.order("created_at ASC") Or ordering by multiple fields: ```ruby +Client.order(orders_count: :asc, created_at: :desc) +# OR +Client.order(:orders_count, created_at: :desc) +# OR Client.order("orders_count ASC, created_at DESC") # OR Client.order("orders_count ASC", "created_at DESC") @@ -939,7 +943,7 @@ WARNING: This method only works with `INNER JOIN`. Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method. -For example, consider the following `Category`, `Post`, `Comments` and `Guest` models: +For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models: ```ruby class Category < ActiveRecord::Base @@ -1462,7 +1466,7 @@ Client.pluck(:id, :name) # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] ``` -`pluck` makes it possible to replace code like +`pluck` makes it possible to replace code like: ```ruby Client.select(:id).map { |c| c.id } @@ -1472,7 +1476,7 @@ Client.select(:id).map(&:id) Client.select(:id, :name).map { |c| [c.id, c.name] } ``` -with +with: ```ruby Client.pluck(:id) @@ -1480,6 +1484,37 @@ Client.pluck(:id) Client.pluck(:id, :name) ``` +Unlike `select`, `pluck` directly converts a database result into a Ruby `Array`, +without constructing `ActiveRecord` objects. This can mean better performance for +a large or often-running query. However, any model method overrides will +not be available. For example: + +```ruby +class Client < ActiveRecord::Base + def name + "I am #{super}" + end +end + +Client.select(:name).map &:name +# => ["I am David", "I am Jeremy", "I am Jose"] + +Client.pluck(:name) +# => ["David", "Jeremy", "Jose"] +``` + +Furthermore, unlike `select` and other `Relation` scopes, `pluck` triggers an immediate +query, and thus cannot be chained with any further scopes, although it can work with +scopes already constructed earlier: + +```ruby +Client.pluck(:name).limit(1) +# => NoMethodError: undefined method `limit' for #<Array:0x007ff34d3ad6d8> + +Client.limit(1).pluck(:name) +# => ["David"] +``` + ### `ids` `ids` can be used to pluck all the IDs for the relation using the table's primary key. @@ -1501,18 +1536,21 @@ Person.ids Existence of Objects -------------------- -If you simply want to check for the existence of the object there's a method called `exists?`. This method will query the database using the same query as `find`, but instead of returning an object or collection of objects it will return either `true` or `false`. +If you simply want to check for the existence of the object there's a method called `exists?`. +This method will query the database using the same query as `find`, but instead of returning an +object or collection of objects it will return either `true` or `false`. ```ruby Client.exists?(1) ``` -The `exists?` method also takes multiple ids, but the catch is that it will return true if any one of those records exists. +The `exists?` method also takes multiple values, but the catch is that it will return `true` if any +one of those records exists. ```ruby -Client.exists?(1,2,3) +Client.exists?(id: [1,2,3]) # or -Client.exists?([1,2,3]) +Client.exists?(name: ['John', 'Sergei']) ``` It's even possible to use `exists?` without any arguments on a model or a relation. @@ -1521,7 +1559,8 @@ It's even possible to use `exists?` without any arguments on a model or a relati Client.where(first_name: 'Ryan').exists? ``` -The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` otherwise. +The above returns `true` if there is at least one client with the `first_name` 'Ryan' and `false` +otherwise. ```ruby Client.exists? diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 8154d4e1cc..0b2f0a47fa 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -684,7 +684,7 @@ class GoodnessValidator end end - # … + # ... end ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 1915252122..e6b849e4c9 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -96,13 +96,12 @@ INFO: The predicate for strings uses the Unicode-aware character class `[:space: WARNING: Note that numbers are not mentioned. In particular, 0 and 0.0 are **not** blank. -For example, this method from `ActionController::HttpAuthentication::Token::ControllerMethods` uses `blank?` for checking whether a token is present: +For example, this method from `ActionDispatch::Session::AbstractStore` uses `blank?` for checking whether a session key is present: ```ruby -def authenticate(controller, &login_procedure) - token, options = token_and_options(controller.request) - unless token.blank? - login_procedure.call(token, options) +def ensure_session_key! + if @key.blank? + raise ArgumentError, 'A key is required...' end end ``` @@ -421,9 +420,9 @@ NOTE: Defined in `active_support/core_ext/object/with_options.rb`. ### JSON support -Active Support provides a better implemention of `to_json` than the `json` gem ordinarily provides for Ruby objects. This is because some classes, like `Hash` and `OrderedHash` needs special handling in order to provide a proper JSON representation. +Active Support provides a better implementation of `to_json` than the +json+ gem ordinarily provides for Ruby objects. This is because some classes, like +Hash+ and +OrderedHash+ needs special handling in order to provide a proper JSON representation. -Active Support also provides an implementation of `as_json` for the `Process::Status` class. +Active Support also provides an implementation of `as_json` for the <tt>Process::Status</tt> class. NOTE: Defined in `active_support/core_ext/object/to_json.rb`. @@ -1249,6 +1248,18 @@ Calling `to_s` on a safe string returns a safe string, but coercion with `to_str Calling `dup` or `clone` on safe strings yields safe strings. +### `remove` + +The method `remove` will remove all occurrences of the pattern: + +```ruby +"Hello World".remove(/Hello /) => "World" +``` + +There's also the destructive version `String#remove!`. + +NOTE: Defined in `active_support/core_ext/string/filters.rb`. + ### `squish` The method `squish` strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -1988,7 +1999,7 @@ Produce a string representation of a number in human-readable words: 1234567890123456.to_s(:human) # => "1.23 Quadrillion" ``` -NOTE: Defined in `active_support/core_ext/numeric/conversions.rb`. +NOTE: Defined in `active_support/core_ext/numeric/formatting.rb`. Extensions to `Integer` ----------------------- @@ -2046,7 +2057,7 @@ BigDecimal.new(5.00, 6).to_s # => "5.0" ### `to_formatted_s` -The method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: +Te method `to_formatted_s` provides a default specifier of "F". This means that a simple call to `to_formatted_s` or `to_s` will result in floating point representation instead of engineering notation: ```ruby BigDecimal.new(5.00, 6).to_formatted_s # => "5.0" @@ -2433,7 +2444,7 @@ dup[1][2] = 4 array[1][2] == nil # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/array/deep_dup.rb`. ### Grouping @@ -2659,7 +2670,45 @@ hash[:b][:e] == nil # => true hash[:b][:d] == [3, 4] # => true ``` -NOTE: Defined in `active_support/core_ext/object/deep_dup.rb`. +NOTE: Defined in `active_support/core_ext/hash/deep_dup.rb`. + +### Diffing + +The method `diff` returns a hash that represents a diff of the receiver and the argument with the following logic: + +* Pairs `key`, `value` that exist in both hashes do not belong to the diff hash. + +* If both hashes have `key`, but with different values, the pair in the receiver wins. + +* The rest is just merged. + +```ruby +{a: 1}.diff(a: 1) +# => {}, first rule + +{a: 1}.diff(a: 2) +# => {:a=>1}, second rule + +{a: 1}.diff(b: 2) +# => {:a=>1, :b=>2}, third rule + +{a: 1, b: 2, c: 3}.diff(b: 1, c: 3, d: 4) +# => {:a=>1, :b=>2, :d=>4}, all rules + +{}.diff({}) # => {} +{a: 1}.diff({}) # => {:a=>1} +{}.diff(a: 1) # => {:a=>1} +``` + +An important property of this diff hash is that you can retrieve the original hash by applying `diff` twice: + +```ruby +hash.diff(hash2).diff(hash2) == hash +``` + +Diffing hashes may be useful for error messages related to expected option hashes for example. + +NOTE: Defined in `active_support/core_ext/hash/diff.rb`. ### Working with Keys @@ -2687,14 +2736,14 @@ NOTE: Defined in `active_support/core_ext/hash/except.rb`. The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: ```ruby -{nil => nil, 1 => 1, a: :a}.transform_keys{ |key| key.to_s.upcase } +{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } # => {"" => nil, "A" => :a, "1" => 1} ``` The result in case of collision is undefined: ```ruby -{"a" => 1, a: 2}.transform_keys{ |key| key.to_s.upcase } +{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } # => {"A" => 2}, in my test, can't rely on this result though ``` @@ -2702,11 +2751,11 @@ This method may be useful for example to build specialized conversions. For inst ```ruby def stringify_keys - transform_keys{ |key| key.to_s } + transform_keys { |key| key.to_s } end ... def symbolize_keys - transform_keys{ |key| key.to_sym rescue key } + transform_keys { |key| key.to_sym rescue key } end ``` @@ -2715,7 +2764,7 @@ There's also the bang variant `transform_keys!` that applies the block operation Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is: ```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys{ |key| key.to_s.upcase } +{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase } # => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} ``` @@ -3794,13 +3843,13 @@ def default_helper_module! module_path = module_name.underscore helper module_path rescue MissingSourceFile => e - raise e unless e.is_missing? "helpers/#{module_path}_helper" + raise e unless e.is_missing? "#{module_path}_helper" rescue NameError => e raise e unless e.missing_name? "#{module_name}Helper" end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/name_error.rb`. Extensions to `LoadError` ------------------------- @@ -3823,4 +3872,4 @@ rescue NameError => e end ``` -NOTE: Defined in `actionpack/lib/abstract_controller/helpers.rb`. +NOTE: Defined in `active_support/core_ext/load_error.rb`. diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index 7e056d970c..98ead9570f 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -172,7 +172,7 @@ In lists of options, parameters, etc. use a hyphen between the item and its desc # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. ``` -The description starts in upper case and ends with a full stop—it's standard English. +The description starts in upper case and ends with a full stop-it's standard English. Dynamically Generated Methods ----------------------------- diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 639a00817b..72aff1e0dd 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -151,7 +151,7 @@ environments. You can enable or disable it in your configuration through the More reading: * [Optimize caching](http://code.google.com/speed/page-speed/docs/caching.html) -* [Revving Filenames: don’t use +* [Revving Filenames: don't use * querystring](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/) @@ -235,6 +235,11 @@ scope of the application or those libraries which are shared across applications * `vendor/assets` is for assets that are owned by outside entities, such as code for JavaScript plugins and CSS frameworks. +WARNING: If you are upgrading from Rails 3, please take into account that assets +under `lib/assets` or `vendor/assets` are available for inclusion via the +application manifests but no longer part of the precompile array. See +[Precompiling Assets](#precompiling-assets) for guidance. + #### Search Paths When a file is referenced from a manifest or a helper, Sprockets searches the @@ -378,8 +383,8 @@ it would make sense to have an image in one of the asset load paths, such as already available in `public/assets` as a fingerprinted file, then that path is referenced. -If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) — -a method of embedding the image data directly into the CSS file — you can use +If you want to use a [data URI](http://en.wikipedia.org/wiki/Data_URI_scheme) - +a method of embedding the image data directly into the CSS file - you can use the `asset_data_uri` helper. ```css @@ -428,7 +433,7 @@ $('#logo').attr src: "<%= asset_path('logo.png') %>" ### Manifest Files and Directives Sprockets uses manifest files to determine which assets to include and serve. -These manifest files contain _directives_ — instructions that tell Sprockets +These manifest files contain _directives_ - instructions that tell Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets loads the files specified, processes them if necessary, concatenates them into one single file and then compresses them (if @@ -536,7 +541,7 @@ Additional layers of preprocessing can be requested by adding other extensions, where each extension is processed in a right-to-left manner. These should be used in the order the processing should be applied. For example, a stylesheet called `app/assets/stylesheets/projects.css.scss.erb` is first processed as ERB, -then SCSS, and finally served as CSS. The same applies to a JavaScript file — +then SCSS, and finally served as CSS. The same applies to a JavaScript file - `app/assets/javascripts/projects.js.coffee.erb` is processed as ERB, then CoffeeScript, and served as JavaScript. @@ -589,7 +594,7 @@ generate instead: Assets are compiled and cached on the first request after the server is started. Sprockets sets a `must-revalidate` Cache-Control HTTP header to reduce request -overhead on subsequent requests — on these the browser gets a 304 (Not Modified) +overhead on subsequent requests - on these the browser gets a 304 (Not Modified) response. If any of the files in the manifest have changed between requests, the server @@ -760,8 +765,8 @@ headers. For Apache: ```apache -# The Expires* directives requires the Apache module `mod_expires` to be -# enabled. +# The Expires* directives requires the Apache module +# `mod_expires` to be enabled. <Location /assets/> # Use of ETag is discouraged when Last-Modified is present Header unset ETag FileETag None @@ -905,7 +910,7 @@ Customizing the Pipeline ### CSS Compression There is currently one option for compressing CSS, YUI. The [YUI CSS -compressor]((http://yui.github.io/yuicompressor/css.html) provides +compressor](http://yui.github.io/yuicompressor/css.html) provides minification. The following line enables YUI compression, and requires the `yui-compressor` diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 9b80a65a44..c58dd2e90a 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -40,7 +40,7 @@ end @customer.destroy ``` -With Active Record associations, we can streamline these — and other — operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: +With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders: ```ruby class Customer < ActiveRecord::Base @@ -69,7 +69,7 @@ To learn more about the different types of associations, read the next section o The Types of Associations ------------------------- -In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key–Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations: +In Rails, an _association_ is a connection between two Active Record models. Associations are implemented using macro-style calls, so that you can declaratively add features to your models. For example, by declaring that one model `belongs_to` another, you instruct Rails to maintain Primary Key-Foreign Key information between instances of the two models, and you also get a number of utility methods added to your model. Rails supports six types of associations: * `belongs_to` * `has_one` @@ -1137,6 +1137,12 @@ Controls what happens to the associated object when its owner is destroyed: * `:restrict_with_exception` causes an exception to be raised if there is an associated record * `:restrict_with_error` causes an error to be added to the owner if there is an associated object +It's necessary not to set or leave `:nullify` option for those associations +that have `NOT NULL` database constraints. If you don't set `dependent` to +destroy such associations you won't be able to change the associated object +because initial associated object foreign key will be set to unallowed `NULL` +value. + ##### `:foreign_key` By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly: diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 1e196b0e42..d3d25f4d4e 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -225,7 +225,7 @@ This is the default cache store implementation. ### ActiveSupport::Cache::MemCacheStore -This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very a high performance and redundancy. +This cache store uses Danga's `memcached` server to provide a centralized cache for your application. Rails uses the bundled `dalli` gem by default. This is currently the most popular cache store for production websites. It can be used to provide a single, shared cache cluster with very high performance and redundancy. When initializing the cache, you need to specify the addresses for all memcached servers in your cluster. If none is specified, it will assume memcached is running on the local host on the default port, but this is not an ideal set up for larger sites. @@ -301,7 +301,7 @@ Conditional GET support Conditional GETs are a feature of the HTTP specification that provide a way for web servers to tell browsers that the response to a GET request hasn't changed since the last request and can be safely pulled from the browser cache. -They work by using the `HTTP_IF_NONE_MATCH` and `HTTP_IF_MODIFIED_SINCE` headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server’s version then the server only needs to send back an empty response with a not modified status. +They work by using the `HTTP_IF_NONE_MATCH` and `HTTP_IF_MODIFIED_SINCE` headers to pass back and forth both a unique content identifier and the timestamp of when the content was last changed. If the browser makes a request where the content identifier (etag) or last modified since timestamp matches the server's version then the server only needs to send back an empty response with a not modified status. It is the server's (i.e. our) responsibility to look for a last modified timestamp and the if-none-match header and determine whether or not to send back the full response. With conditional-get support in Rails this is a pretty easy task: @@ -338,7 +338,7 @@ class ProductsController < ApplicationController end ``` -If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you’ve got an easy helper in fresh_when: +If you don't have any special response processing and are using the default rendering mechanism (i.e. you're not using respond_to or calling render yourself) then you've got an easy helper in fresh_when: ```ruby class ProductsController < ApplicationController diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 639476eeeb..6f792be8ae 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -471,7 +471,7 @@ spec/models/user_spec.rb: INFO: A good description of unit testing in Rails is given in [A Guide to Testing Rails Applications](testing.html) -Rails comes with a test suite called `Test::Unit`. Rails owes its stability to the use of tests. The tasks available in the `test:` namespace helps in running the different tests you will hopefully write. +Rails comes with a test suite called Minitest. Rails owes its stability to the use of tests. The tasks available in the `test:` namespace helps in running the different tests you will hopefully write. ### `tmp` @@ -493,7 +493,9 @@ The `tmp:` namespaced tasks will help you clear and create the `Rails.root/tmp` ### Custom Rake Tasks -Custom rake tasks have a `.rake` extension and are placed in `Rails.root/lib/tasks`. +Custom rake tasks have a `.rake` extension and are placed in +`Rails.root/lib/tasks`. You can create these custom rake tasks with the +`bin/rails generate task` command. ```ruby desc "I am short, but comprehensive description for my cool task" diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 0620849519..3cf5cdd71f 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -107,7 +107,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. -* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id — both very helpful in debugging multi-user production applications. +* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. * `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode. @@ -117,7 +117,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.secret_key_base` used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `config.secret_key_base` initialized to a random key in `config/initializers/secret_token.rb`. -* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. +* `config.serve_static_assets` configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won't be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. * `config.session_store` is usually set up in `config/initializers/session_store.rb` and specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -131,8 +131,6 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.beginning_of_week` sets the default beginning of week for the application. Accepts a valid week day symbol (e.g. `:monday`). -* `config.whiny_nils` enables or disables warnings when a certain set of methods are invoked on `nil` and it does not respond to them. Defaults to true in development and test environments. - ### Configuring Assets * `config.assets.enabled` a flag that controls whether the asset @@ -632,11 +630,11 @@ WARNING: Some parts of your application, notably routing, are not yet set up at ### `Rails::Railtie#initializer` -Rails has several initializers that run on startup that are all defined by using the `initializer` method from `Rails::Railtie`. Here's an example of the `initialize_whiny_nils` initializer from Active Support: +Rails has several initializers that run on startup that are all defined by using the `initializer` method from `Rails::Railtie`. Here's an example of the `set_helpers_path` initializer from Action Controller: ```ruby -initializer "active_support.initialize_whiny_nils" do |app| - require 'active_support/whiny_nil' if app.config.whiny_nils +initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths end ``` @@ -662,7 +660,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer — which runs only if `cache_classes` is set to `false` — uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. * `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. @@ -670,20 +668,6 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `i18n.callbacks` In the development environment, sets up a `to_prepare` callback which will call `I18n.reload!` if any of the locales have changed since the last request. In production mode this callback will only run on the first request. -* `active_support.initialize_whiny_nils` Requires `active_support/whiny_nil` if `config.whiny_nils` is true. This file will output errors such as: - - ``` - Called id for nil, which would mistakenly be 4 — if you really wanted the id of nil, use object_id - ``` - - And: - - ``` - You have a nil object when you didn't expect it! - You might have expected an instance of Array. - The error occurred while evaluating nil.each - ``` - * `active_support.deprecation_behavior` Sets up deprecation reporting for environments, defaulting to `:log` for development, `:notify` for production and `:stderr` for test. If a value isn't set for `config.active_support.deprecation` then this initializer will prompt the user to configure this line in the current environment's `config/environments` file. Can be set to an array of values. * `active_support.initialize_time_zone` Sets the default time zone for the application based on the `config.time_zone` setting, which defaults to "UTC". @@ -694,9 +678,9 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` — if it's not already set — to `Rails.logger`. +* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`. -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` — if it's not already set — to `Rails.cache`. +* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`. * `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. @@ -704,7 +688,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.initialize_timezone` Sets `ActiveRecord::Base.time_zone_aware_attributes` to true, as well as setting `ActiveRecord::Base.default_timezone` to UTC. When attributes are read from the database, they will be converted into the time zone specified by `Time.zone`. -* `active_record.logger` Sets `ActiveRecord::Base.logger` — if it's not already set — to `Rails.logger`. +* `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. * `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. @@ -714,7 +698,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. -* `action_mailer.logger` Sets `ActionMailer::Base.logger` — if it's not already set — to `Rails.logger`. +* `action_mailer.logger` Sets `ActionMailer::Base.logger` - if it's not already set - to `Rails.logger`. * `action_mailer.set_configs` Sets up Action Mailer by using the settings in `config.action_mailer` by `send`'ing the method names as setters to `ActionMailer::Base` and passing the values through. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 2f8a178376..b2b08c82c6 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -11,7 +11,7 @@ After reading this guide, you will know: * How to contribute to the Ruby on Rails documentation. * How to contribute to the Ruby on Rails code. -Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation — all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +Ruby on Rails is not "someone else's framework." Over the years, hundreds of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. -------------------------------------------------------------------------------- @@ -26,16 +26,18 @@ NOTE: Bugs in the most recent released version of Ruby on Rails are likely to ge If you've found a problem in Ruby on Rails which is not a security risk, do a search in GitHub under [Issues](https://github.com/rails/rails/issues) in case it was already reported. If you find no issue addressing it you can [add a new one](https://github.com/rails/rails/issues/new). (See the next section for reporting security issues.) -At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself — and others — to replicate the bug and figure out a fix. +At the minimum, your issue report needs a title and descriptive text. But that's only a minimum. You should include as much relevant information as possible. You need at least to post the code sample that has the issue. Even better is to include a unit test that shows how the expected behavior is not occurring. Your goal should be to make it easy for yourself - and others - to replicate the bug and figure out a fix. Then, don't get your hopes up! Unless you have a "Code Red, Mission Critical, the World is Coming to an End" kind of bug, you're creating this issue report in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the issue report will automatically see any activity or that others will jump to fix it. Creating an issue like this is mostly to help yourself start on the path of fixing the problem and for others to confirm it with an "I'm having this problem too" comment. -### Create a Self-Contained gist for Active Record Issues +### Create a Self-Contained gist for Active Record and Action Controller Issues -If you are filing a bug report for Active Record, please use -[this template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) +If you are filing a bug report, please use +[Active Record template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_gem.rb) or +[Action Controller template for gems](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_gem.rb) if the bug is found in a published gem, and -[this template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) +[Active Record template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/active_record_master.rb) or +[Action Controller template for master](https://github.com/rails/rails/blob/master/guides/bug_report_templates/action_controller_master.rb) if the bug happens in the master branch. ### Special Treatment for Security Issues @@ -44,9 +46,28 @@ WARNING: Please do not report security vulnerabilities with public GitHub issue ### What about Feature Requests? -Please don't put "feature request" items into GitHub Issues. If there's a new feature that you want to see added to Ruby on Rails, you'll need to write the code yourself - or convince someone else to partner with you to write the code. Later in this guide you'll find detailed instructions for proposing a patch to Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you can expect it to be marked "invalid" as soon as it's reviewed. - -If you'd like feedback on an idea for a feature before doing the work for make a patch, please send an email to the [rails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You might get no response, which means that everyone is indifferent. You might find someone who's also interested in building that feature. You might get a "This won't be accepted." But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require. +Please don't put "feature request" items into GitHub Issues. If there's a new +feature that you want to see added to Ruby on Rails, you'll need to write the +code yourself - or convince someone else to partner with you to write the code. +Later in this guide you'll find detailed instructions for proposing a patch to +Ruby on Rails. If you enter a wishlist item in GitHub Issues with no code, you +can expect it to be marked "invalid" as soon as it's reviewed. + +Sometimes, the line between 'bug' and 'feature' is a hard one to draw. +Generally, a feature is anything that adds new behavior, while a bug is +anything that fixes already existing behavior that is mis-behaving. Sometimes, +the core team will have to make a judgement call. That said, the distinction +generally just affects which release your patch will get in to; we love feature +submissions! They just won't get backported to maintenance branches. + +If you'd like feedback on an idea for a feature before doing the work for make +a patch, please send an email to the [rails-core mailing +list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You +might get no response, which means that everyone is indifferent. You might find +someone who's also interested in building that feature. You might get a "This +won't be accepted." But it's the proper place to discuss new ideas. GitHub +Issues are not a particularly good venue for the sometimes long and involved +discussions new features require. Setting Up a Development Environment ------------------------------------ @@ -172,7 +193,7 @@ After applying their branch, test it out! Here are some things to think about: Once you're happy that the pull request contains a good change, comment on the GitHub issue indicating your approval. Your comment should indicate that you like the change and what you like about it. Something like: <blockquote> -I like the way you've restructured that code in generate_finder_sql — much nicer. The tests look good too. +I like the way you've restructured that code in generate_finder_sql - much nicer. The tests look good too. </blockquote> If your comment simply says "+1", then odds are that other reviewers aren't going to take it too seriously. Show that you took the time to review the pull request. @@ -184,7 +205,11 @@ Ruby on Rails has two main sets of documentation: the guides help you in learnin You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). -You can either ask for commit bit if you'd like to contribute to [Docrails](http://github.com/rails/docrails) regularly (Please contact anyone from [the core team](http://rubyonrails.org/core)), or else open a pull request to [Rails](http://github.com/rails/rails) itself. +You can either open a pull request to [Rails](http://github.com/rails/rails) or +ask the [Rails core team](http://rubyonrails.org/core) for commit access on +[docrails](http://github.com/rails/docrails) if you contribute regularly. +Please do not open pull requests in docrails, if you'd like to get feedback on your +change, ask for it in [Rails](http://github.com/rails/rails) instead. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. @@ -194,7 +219,7 @@ When working with documentation, please take into account the [API Documentation NOTE: As explained earlier, ordinary code patches should have proper documentation coverage. Docrails is only used for isolated documentation improvements. -NOTE: To help our CI servers you can add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. +NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. @@ -216,11 +241,11 @@ $ cd rails $ git checkout -b my_new_branch ``` -It doesn’t matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. +It doesn't matter much what name you use, because this branch will only exist on your local computer and your personal repository on GitHub. It won't be part of the Rails Git repository. ### Write Your Code -Now get busy and add or edit code. You’re on your branch now, so you can write whatever you want (you can check to make sure you’re on the right branch with `git branch -a`). But if you’re planning to submit your change back for inclusion in Rails, keep a few things in mind: +Now get busy and add or edit code. You're on your branch now, so you can write whatever you want (you can check to make sure you're on the right branch with `git branch -a`). But if you're planning to submit your change back for inclusion in Rails, keep a few things in mind: * Get the code right. * Use Rails idioms and helpers. @@ -256,7 +281,7 @@ Rails follows a simple set of coding style conventions: * Prefer `method { do_stuff }` instead of `method{do_stuff}` for single-line blocks. * Follow the conventions in the source you see used already. -The above are guidelines — please use your best judgment in using them. +The above are guidelines - please use your best judgment in using them. ### Updating the CHANGELOG @@ -285,7 +310,13 @@ Your name can be added directly after the last word if you don't provide any cod ### Sanity Check -You should not be the only person who looks at the code before you submit it. You know at least one other Rails developer, right? Show them what you’re doing and ask for feedback. Doing this in private before you push a patch out publicly is the “smoke test” for a patch: if you can’t convince one other developer of the beauty of your code, you’re unlikely to convince the core team either. +You should not be the only person who looks at the code before you submit it. +If you know someone else who uses Rails, try asking them if they'll check out +your work. If you don't know anyone else using Rails, try hopping into the IRC +room or posting about your idea to the rails-core mailing list. Doing this in +private before you push a patch out publicly is the “smoke test” for a patch: +if you can’t convince one other developer of the beauty of your code, you’re +unlikely to convince the core team either. ### Commit Your Changes @@ -329,7 +360,7 @@ TIP. Please squash your commits into a single commit when appropriate. This simp ### Update Your Branch -It’s pretty likely that other changes to master have happened while you were working. Go get them: +It's pretty likely that other changes to master have happened while you were working. Go get them: ```bash $ git checkout master @@ -409,11 +440,28 @@ Fill in some details about your potential patch including a meaningful title. Wh ### Get some Feedback -Now you need to get other people to look at your patch, just as you've looked at other people's patches. You can use the [rubyonrails-core mailing list](http://groups.google.com/group/rubyonrails-core/) or the #rails-contrib channel on IRC freenode for this. You might also try just talking to Rails developers that you know. +Most pull requests will go through a few iterations before they get merged. +Different contributors will sometimes have different opinions, and often +patches will need revised before they can get merged. + +Some contributors to Rails have email notifications from GitHub turned on, but +others do not. Furthermore, (almost) everyone who works on Rails is a +volunteer, and so it may take a few days for you to get your first feedback on +a pull request. Don't despair! Sometimes it's quick, sometimes it's slow. Such +is the open source life. + +If it's been over a week, and you haven't heard anything, you might want to try +and nudge things along. You can use the [rubyonrails-core mailing +list](http://groups.google.com/group/rubyonrails-core/) for this. You can also +leave another comment on the pull request. + +While you're waiting for feedback on your pull request, open up a few other +pull requests and give someone else some! I'm sure they'll appreciate it in +the same way that you appreciate feedback on your patches. ### Iterate as Necessary -It’s entirely possible that the feedback you get will suggest changes. Don’t get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it’s worth making the tweaks and resubmitting. If the feedback is that your code doesn’t belong in the core, you might still think about releasing it as a gem. +It's entirely possible that the feedback you get will suggest changes. Don't get discouraged: the whole point of contributing to an active open source project is to tap into community knowledge. If people are encouraging you to tweak your code, then it's worth making the tweaks and resubmitting. If the feedback is that your code doesn't belong in the core, you might still think about releasing it as a gem. #### Squashing commits diff --git a/guides/source/credits.html.erb b/guides/source/credits.html.erb index 10dd8178fb..5beae9c29b 100644 --- a/guides/source/credits.html.erb +++ b/guides/source/credits.html.erb @@ -74,3 +74,7 @@ Oscar Del Ben is a software engineer at <a href="http://www.wildfireapp.com/">Wi <%= author('Heiko Webers', 'hawe') do %> Heiko Webers is the founder of <a href="http://www.bauland42.de">bauland42</a>, a German web application security consulting and development company focused on Ruby on Rails. He blogs at the <a href="http://www.rorsecurity.info">Ruby on Rails Security Project</a>. After 10 years of desktop application development, Heiko has rarely looked back. <% end %> + +<%= author('Akshay Surve', 'startupjockey', 'akshaysurve.jpg') do %> + Akshay Surve is the Founder at <a href="http://www.deltax.com">DeltaX</a>, hackathon specialist, a midnight code junkie and ocassionally writes prose. You can connect with him on <a href="https://twitter.com/akshaysurve">Twitter</a>, <a href="http://www.linkedin.com/in/akshaysurve">Linkedin</a>, <a href="http://www.akshaysurve.com/">Personal Blog</a> or <a href="http://www.quora.com/Akshay-Surve">Quora</a>. +<% end %> diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 50ee934b87..226137c89a 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -198,7 +198,7 @@ Adding extra logging like this makes it easy to search for unexpected or unusual ### Tagged Logging -When running multi-user, multi-account applications, it’s often useful +When running multi-user, multi-account applications, it's often useful to be able to filter the logs using some custom rules. `TaggedLogging` in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. @@ -233,7 +233,7 @@ only evaluated if the output level is the same or included in the allowed level (i.e. lazy loading). The same code rewritten would be: ```ruby -logger.debug {"Person attibutes hash: #{@person.attributes.inspect}"} +logger.debug {"Person attributes hash: #{@person.attributes.inspect}"} ``` The contents of the block, and therefore the string interpolation, is only @@ -386,7 +386,7 @@ Finally, to see where you are in the code again you can type `list=` 7 8 respond_to do |format| 9 format.html # index.html.erb - 10 format.json { render :json => @posts } + 10 format.json { render json: @posts } ``` ### The Context diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index ec25e09222..c4e5789a1a 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -74,7 +74,7 @@ ports. If you have any problems with these libraries, you can install them manually by compiling the source code. Just follow the instructions at the [Red Hat/CentOS section of the Nokogiri tutorials](http://nokogiri.org/tutorials/installing_nokogiri.html#red_hat__centos) . -Also, SQLite3 and its development files for the `sqlite3` gem — in Ubuntu you're done with just +Also, SQLite3 and its development files for the `sqlite3-ruby` gem - in Ubuntu you're done with just ```bash $ sudo apt-get install sqlite3 libsqlite3-dev @@ -113,7 +113,29 @@ and run: $ bundle install --without db ``` -This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. With dependencies installed, you can run the test suite with: +This command will install all dependencies except the MySQL and PostgreSQL Ruby drivers. We will come back to these soon. + +NOTE: If you would like to run the tests that use memcached, you need to ensure that you have it installed and running. + +You can use homebrew to install memcached on OSX: + +```bash +$ brew install memcached +``` + +On Ubuntu you can install it with apt-get: + +```bash +$ sudo apt-get install memcached +``` + +Or use yum on Fedora or CentOS: + +```bash +$ sudo yum install memcached +``` + +With the dependencies now installed, you can run the test suite with: ```bash $ bundle exec rake test @@ -133,13 +155,20 @@ $ cd railties $ TEST_DIR=generators bundle exec rake test ``` -You can run any single test separately too: +You can run the tests for a particular file by using: ```bash $ cd actionpack $ bundle exec ruby -Itest test/template/form_helper_test.rb ``` +Or, you can run a single test in a particular file: + +```bash +$ cd actionpack +$ bundle exec ruby -Itest path/to/test.rb -n test_name +``` + ### Active Record Setup The test suite of Active Record attempts to run four times: once for SQLite3, once for each of the two MySQL gems (`mysql` and `mysql2`), and once for PostgreSQL. We are going to see now how to set up the environment for them. @@ -244,4 +273,4 @@ NOTE: Using the rake task to create the test databases ensures they have the cor NOTE: You'll see the following warning (or localized warning) during activating HStore extension in PostgreSQL 9.1.x or earlier: "WARNING: => is deprecated as an operator". -If you’re using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. +If you're using another database, check the file `activerecord/test/config.yml` or `activerecord/test/config.example.yml` for default connection information. You can edit `activerecord/test/config.yml` to provide different credentials on your machine if you must, but obviously you should not push any such changes back to Rails. diff --git a/guides/source/engines.md b/guides/source/engines.md index a77be917a2..ec51fb9234 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -104,7 +104,7 @@ At the root of this brand new engine's directory lives a `blorgh.gemspec` file. gem 'blorgh', path: "vendor/engines/blorgh" ``` -By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`. +Don't forget to run `bundle install` as usual. By specifying it as a gem within the `Gemfile`, Bundler will load it as such, parsing this `blorgh.gemspec` file and requiring a file within the `lib` directory called `lib/blorgh.rb`. This file requires the `blorgh/engine.rb` file (located at `lib/blorgh/engine.rb`) and defines a base module called `Blorgh`. ```ruby require "blorgh/engine" @@ -399,9 +399,9 @@ def create end private -def comment_params - params.require(:comment).permit(:text) -end + def comment_params + params.require(:comment).permit(:text) + end ``` This is the final part required to get the new comment form working. Displaying the comments however, is not quite right yet. If you were to create a comment right now you would see this error: @@ -466,7 +466,7 @@ NOTE: Other engines, such as Devise, handle this a little differently by making The engine contains migrations for the `blorgh_posts` and `blorgh_comments` table which need to be created in the application's database so that the engine's models can query them correctly. To copy these migrations into the application use this command: ```bash -$ rake blorgh_engine:install:migrations +$ rake blorgh:install:migrations ``` If you have multiple engines that need migrations copied over, use `railties:install:migrations` instead: @@ -525,6 +525,14 @@ First, the `author_name` text field needs to be added to the `app/views/blorgh/p </div> ``` +Next, we need to update our `Blorgh::PostController#post_params` method to permit the new form parameter: + +```ruby +def post_params + params.require(:post).permit(:title, :text, :author_name) +end +``` + The `Blorgh::Post` model should then have some code to convert the `author_name` field into an actual `User` object and associate it as that post's `author` before the post is saved. It will also need to have an `attr_accessor` setup for this field so that the setter and getter methods are defined for it. To do all this, you'll need to add the `attr_accessor` for `author_name`, the association for the author and the `before_save` call into `app/models/blorgh/post.rb`. The `author` association will be hard-coded to the `User` class for the time being. @@ -571,7 +579,7 @@ Run this migration using this command: $ rake db:migrate ``` -Now with all the pieces in place, an action will take place that will associate an author — represented by a record in the `users` table — with a post, represented by the `blorgh_posts` table from the engine. +Now with all the pieces in place, an action will take place that will associate an author - represented by a record in the `users` table - with a post, represented by the `blorgh_posts` table from the engine. Finally, the author's name should be displayed on the post's page. Add this code above the "Title" output inside `app/views/blorgh/posts/show.html.erb`: @@ -681,8 +689,8 @@ There are now no strict dependencies on what the class is, only what the API for Within an engine, there may come a time where you wish to use things such as initializers, internationalization or other configuration options. The great news is that these things are entirely possible because a Rails engine shares much the same functionality as a Rails application. In fact, a Rails application's functionality is actually a superset of what is provided by engines! -If you wish to use an initializer — code that should run before the engine is -loaded — the place for it is the `config/initializers` folder. This directory's +If you wish to use an initializer - code that should run before the engine is +loaded - the place for it is the `config/initializers` folder. This directory's functionality is explained in the [Initializers section](configuring.html#initializers) of the Configuring guide, and works precisely the same way as the `config/initializers` directory inside @@ -699,7 +707,7 @@ The `test` directory should be treated like a typical Rails testing environment, ### Functional tests -A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application — the `test/dummy` application — rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: +A matter worth taking into consideration when writing functional tests is that the tests are going to be running on an application - the `test/dummy` application - rather than your engine. This is due to the setup of the testing environment; an engine needs an application as a host for testing its main functionality, especially controllers. This means that if you were to make a typical `GET` to a controller in a controller's functional test like this: ```ruby get :index @@ -842,7 +850,6 @@ module Blorgh::Concerns::Models::Post before_save :set_author private - def set_author self.author = User.find_or_create_by(name: author_name) end diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index 11e8db9e88..0287b3df73 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -67,7 +67,7 @@ To create this form you will use `form_tag`, `label_tag`, `text_field_tag`, and This will generate the following HTML: ```html -<form accept-charset="UTF-8" action="/search" method="get"> +<form accept-charset="UTF-8" action="/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /></div> <label for="q">Search for:</label> <input id="q" name="q" type="text" /> <input name="commit" type="submit" value="Search" /> @@ -290,7 +290,7 @@ The object yielded by `fields_for` is a form builder like the one yielded by `fo ### Relying on Record Identification -The Article model is directly available to users of the application, so — following the best practices for developing with Rails — you should declare it **a resource**: +The Article model is directly available to users of the application, so - following the best practices for developing with Rails - you should declare it **a resource**: ```ruby resources :articles @@ -381,7 +381,7 @@ Here you have a list of cities whose names are presented to the user. Internally ### The Select and Option Tags -The most generic helper is `select_tag`, which — as the name implies — simply generates the `SELECT` tag that encapsulates an options string: +The most generic helper is `select_tag`, which - as the name implies - simply generates the `SELECT` tag that encapsulates an options string: ```erb <%= select_tag(:city_id, '<option value="1">Lisbon</option>...') %> @@ -421,7 +421,7 @@ output: Whenever Rails sees that the internal value of an option being generated matches this value, it will add the `selected` attribute to that option. -TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` — you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. +TIP: The second argument to `options_for_select` must be exactly equal to the desired internal value. In particular if the value is the integer 2 you cannot pass "2" to `options_for_select` - you must pass 2. Be aware of values extracted from the `params` hash as they are all strings. WARNING: when `:include_blank` or `:prompt` are not present, `:include_blank` is forced true if the select attribute `required` is true, display `size` is one and `multiple` is not true. @@ -451,7 +451,7 @@ In most cases form controls will be tied to a specific database model and as you <%= select(:person, :city_id, [['Lisbon', 1], ['Madrid', 2], ...]) %> ``` -Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one — Rails will do this for you by reading from the `@person.city_id` attribute. +Notice that the third parameter, the options array, is the same kind of argument you pass to `options_for_select`. One advantage here is that you don't have to worry about pre-selecting the correct city if the user already has one - Rails will do this for you by reading from the `@person.city_id` attribute. As with other helpers, if you were to use the `select` helper on a form builder scoped to the `@person` object, the syntax would be: @@ -605,7 +605,7 @@ The object in the `params` hash is an instance of a subclass of IO. Depending on ```ruby def upload uploaded_io = params[:person][:picture] - File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file| + File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'wb') do |file| file.write(uploaded_io.read) end end @@ -664,7 +664,7 @@ Understanding Parameter Naming Conventions As you've seen in the previous sections, values from forms can be at the top level of the `params` hash or nested in another hash. For example in a standard `create` action for a Person model, `params[:person]` would usually be a hash of all the attributes for the person to create. The `params` hash can also contain arrays, arrays of hashes and so on. -Fundamentally HTML forms don't know about any sort of structured data, all they generate is name–value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. +Fundamentally HTML forms don't know about any sort of structured data, all they generate is name-value pairs, where pairs are just plain strings. The arrays and hashes you see in your application are the result of some parameter naming conventions that Rails uses. TIP: You may find you can try out examples in this section faster by using the console to directly invoke Racks' parameter parser. For example, @@ -914,9 +914,9 @@ def create end private -def person_params - params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) -end + def person_params + params.require(:person).permit(:name, addresses_attributes: [:id, :kind, :street]) + end ``` ### Removing Objects diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index acfbf24f12..e5bc5ef038 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -54,9 +54,11 @@ learned elsewhere, you may have a less happy experience. The Rails philosophy includes two major guiding principles: -* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and over again is a bad thing. -* Convention Over Configuration - means that Rails makes assumptions about what you want to do and how you're going to -do it, rather than requiring you to specify every little thing through endless configuration files. +* DRY - "Don't Repeat Yourself" - suggests that writing the same code over and + over again is a bad thing. +* Convention Over Configuration - means that Rails makes assumptions about what + you want to do and how you're going to do it, rather than requiring you to + specify every little thing through endless configuration files. Creating a New Rails Project ---------------------------- @@ -94,10 +96,11 @@ $ gem install rails ``` TIP. A number of tools exist to help you quickly install Ruby and Ruby -on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), while Mac OS X users can use -[Rails One Click](http://railsoneclick.com). +on Rails on your system. Windows users can use [Rails Installer](http://railsinstaller.org), +while Mac OS X users can use [Rails One Click](http://railsoneclick.com). -To verify that you have everything installed correctly, you should be able to run the following: +To verify that you have everything installed correctly, you should be able to +run the following: ```bash $ rails --version @@ -107,29 +110,38 @@ If it says something like "Rails 4.0.0", you are ready to continue. ### Creating the Blog Application -Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself. +Rails comes with a number of scripts called generators that are designed to make +your development life easier by creating everything that's necessary to start +working on a particular task. One of these is the new application generator, +which will provide you with the foundation of a fresh Rails application so that +you don't have to write it yourself. -To use this generator, open a terminal, navigate to a directory where you have rights to create files, and type: +To use this generator, open a terminal, navigate to a directory where you have +rights to create files, and type: ```bash $ rails new blog ``` -This will create a Rails application called Blog in a directory called blog and install the gem dependencies that are already mentioned in `Gemfile` using `bundle install`. +This will create a Rails application called Blog in a directory called blog and +install the gem dependencies that are already mentioned in `Gemfile` using +`bundle install`. -TIP: You can see all of the command line options that the Rails -application builder accepts by running `rails new -h`. +TIP: You can see all of the command line options that the Rails application +builder accepts by running `rails new -h`. -After you create the blog application, switch to its folder to continue work directly in that application: +After you create the blog application, switch to its folder to continue work +directly in that application: ```bash $ cd blog ``` -The `rails new blog` command we ran above created a folder in your -working directory called `blog`. The `blog` directory has a number of -auto-generated files and folders that make up the structure of a Rails -application. Most of the work in this tutorial will happen in the `app/` folder, but here's a basic rundown on the function of each of the files and folders that Rails created by default: +The `rails new blog` command we ran above created a folder in your working +directory called `blog`. The `blog` directory has a number of auto-generated +files and folders that make up the structure of a Rails application. Most of the +work in this tutorial will happen in the `app/` folder, but here's a basic +rundown on the function of each of the files and folders that Rails created by default: | File/Folder | Purpose | | ----------- | ------- | @@ -151,35 +163,65 @@ application. Most of the work in this tutorial will happen in the `app/` folder, Hello, Rails! ------------- -To begin with, let's get some text up on screen quickly. To do this, you need to get your Rails application server running. +To begin with, let's get some text up on screen quickly. To do this, you need to +get your Rails application server running. ### Starting up the Web Server -You actually have a functional Rails application already. To see it, you need to start a web server on your development machine. You can do this by running the following in the root directory of your rails application: +You actually have a functional Rails application already. To see it, you need to +start a web server on your development machine. You can do this by running the +following in the root directory of your rails application: ```bash $ rails server ``` -TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the absence of a runtime will give you an `execjs` error. Usually Mac OS X and Windows come with a JavaScript runtime installed. Rails adds the `therubyracer` gem to Gemfile in a commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to Gemfile in apps generated under JRuby. You can investigate about all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). +TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the +absence of a runtime will give you an `execjs` error. Usually Mac OS X and +Windows come with a JavaScript runtime installed. Rails adds the `therubyracer` +gem to Gemfile in a commented line for new apps and you can uncomment if you +need it. `therubyrhino` is the recommended runtime for JRuby users and is added +by default to Gemfile in apps generated under JRuby. You can investigate about +all the supported runtimes at [ExecJS](https://github.com/sstephenson/execjs#readme). -This will fire up WEBrick, a webserver built into Ruby by default. To see your application in action, open a browser window and navigate to <http://localhost:3000>. You should see the Rails default information page: +This will fire up WEBrick, a webserver built into Ruby by default. To see your +application in action, open a browser window and navigate to <http://localhost:3000>. +You should see the Rails default information page:  -TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server. +TIP: To stop the web server, hit Ctrl+C in the terminal window where it's +running. To verify the server has stopped you should see your command prompt +cursor again. For most UNIX-like systems including Mac OS X this will be a +dollar sign `$`. In development mode, Rails does not generally require you to +restart the server; changes you make in files will be automatically picked up by +the server. -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it +makes sure that you have your software configured correctly enough to serve a +page. You can also click on the _About your application's environment_ link to +see a summary of your application's environment. ### Say "Hello", Rails -To get Rails saying "Hello", you need to create at minimum a _controller_ and a _view_. +To get Rails saying "Hello", you need to create at minimum a _controller_ and a +_view_. -A controller's purpose is to receive specific requests for the application. _Routing_ decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different _actions_. Each action's purpose is to collect information to provide it to a view. +A controller's purpose is to receive specific requests for the application. +_Routing_ decides which controller receives which requests. Often, there is more +than one route to each controller, and different routes can be served by +different _actions_. Each action's purpose is to collect information to provide +it to a view. -A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the _controller_, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called ERB (Embedded Ruby) which is converted by the request cycle in Rails before being sent to the user. +A view's purpose is to display this information in a human readable format. An +important distinction to make is that it is the _controller_, not the view, +where information is collected. The view should just display that information. +By default, view templates are written in a language called ERB (Embedded Ruby) +which is converted by the request cycle in Rails before being sent to the user. -To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "welcome" with an action called "index", just like this: +To create a new controller, you will need to run the "controller" generator and +tell it you want a controller called "welcome" with an action called "index", +just like this: ```bash $ rails generate controller welcome index @@ -206,9 +248,12 @@ invoke scss create app/assets/stylesheets/welcome.css.scss ``` -Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb` and the view, located at `app/views/welcome/index.html.erb`. +Most important of these are of course the controller, located at `app/controllers/welcome_controller.rb` +and the view, located at `app/views/welcome/index.html.erb`. -Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all of the existing code in the file, and replace it with the following single line of code: +Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all +of the existing code in the file, and replace it with the following single line +of code: ```html <h1>Hello, Rails!</h1> @@ -216,7 +261,10 @@ Open the `app/views/welcome/index.html.erb` file in your text editor. Delete all ### Setting the Application Home Page -Now that we have made the controller and view, we need to tell Rails when we want Hello Rails! to show up. In our case, we want it to show up when we navigate to the root URL of our site, <http://localhost:3000>. At the moment, "Welcome Aboard" is occupying that spot. +Now that we have made the controller and view, we need to tell Rails when we +want Hello Rails! to show up. In our case, we want it to show up when we +navigate to the root URL of our site, <http://localhost:3000>. At the moment, +"Welcome Aboard" is occupying that spot. Next, you have to tell Rails where your actual home page is located. @@ -233,27 +281,44 @@ Blog::Application.routes.draw do # root "welcome#index" ``` -This is your application's _routing file_ which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with `root` and uncomment it. It should look something like the following: +This is your application's _routing file_ which holds entries in a special DSL +(domain-specific language) that tells Rails how to connect incoming requests to +controllers and actions. This file contains many sample routes on commented +lines, and one of them actually shows you how to connect the root of your site +to a specific controller and action. Find the line beginning with `root` and +uncomment it. It should look something like the following: ```ruby root "welcome#index" ``` -The `root "welcome#index"` tells Rails to map requests to the root of the application to the welcome controller's index action and `get "welcome/index"` tells Rails to map requests to <http://localhost:3000/welcome/index> to the welcome controller's index action. This was created earlier when you ran the controller generator (`rails generate controller welcome index`). +The `root "welcome#index"` tells Rails to map requests to the root of the +application to the welcome controller's index action and `get "welcome/index"` +tells Rails to map requests to <http://localhost:3000/welcome/index> to the +welcome controller's index action. This was created earlier when you ran the +controller generator (`rails generate controller welcome index`). -If you navigate to <http://localhost:3000> in your browser, you'll see the `Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, indicating that this new route is indeed going to `WelcomeController`'s `index` action and is rendering the view correctly. +If you navigate to <http://localhost:3000> in your browser, you'll see the +`Hello, Rails!` message you put into `app/views/welcome/index.html.erb`, +indicating that this new route is indeed going to `WelcomeController`'s `index` +action and is rendering the view correctly. TIP: For more information about routing, refer to [Rails Routing from the Outside In](routing.html). Getting Up and Running ---------------------- -Now that you've seen how to create a controller, an action and a view, let's create something with a bit more substance. +Now that you've seen how to create a controller, an action and a view, let's +create something with a bit more substance. -In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. +In the Blog application, you will now create a new _resource_. A resource is the +term used for a collection of similar objects, such as posts, people or animals. +You can create, read, update and destroy items for a resource and these +operations are referred to as _CRUD_ operations. -Rails provides a `resources` method which can be used to declare a standard REST resource. -Here's what `config/routes.rb` should look like after the _post resource_ is declared. +Rails provides a `resources` method which can be used to declare a standard REST +resource. Here's what `config/routes.rb` should look like after the _post resource_ +is declared. ```ruby Blog::Application.routes.draw do @@ -283,75 +348,125 @@ edit_post GET /posts/:id/edit(.:format) posts#edit root / welcome#index ``` -In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this: +In the next section, you will add the ability to create new posts in your +application and be able to view them. This is the "C" and the "R" from CRUD: +creation and reading. The form for doing this will look like this:  -It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. +It will look a little basic for now, but that's ok. We'll look at improving the +styling for it afterwards. ### Laying down the ground work -The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to <http://localhost:3000/posts/new> and you'll see a routing error: +The first thing that you are going to need to create a new post within the +application is a place to do that. A great place for that would be at `/posts/new`. +With the route already defined, requests can now be made to `/posts/new` in the +application. Navigate to <http://localhost:3000/posts/new> and you'll see a +routing error:  -This error occurs because the route needs to have a controller defined in order to serve the request. The solution to this particular problem is simple: create a controller called `PostsController`. You can do this by running this command: +This error occurs because the route needs to have a controller defined in order +to serve the request. The solution to this particular problem is simple: create +a controller called `PostsController`. You can do this by running this command: ```bash $ rails g controller posts ``` -If you open up the newly generated `app/controllers/posts_controller.rb` you'll see a fairly empty controller: +If you open up the newly generated `app/controllers/posts_controller.rb` you'll +see a fairly empty controller: ```ruby class PostsController < ApplicationController end ``` -A controller is simply a class that is defined to inherit from `ApplicationController`. It's inside this class that you'll define methods that will become the actions for this controller. These actions will perform CRUD operations on the posts within our system. +A controller is simply a class that is defined to inherit from `ApplicationController`. +It's inside this class that you'll define methods that will become the actions +for this controller. These actions will perform CRUD operations on the posts +within our system. + +NOTE: There are `public`, `private` and `protected` methods in `Ruby` +(for more details you can check on [Programming Ruby](http://www.ruby-doc.org/docs/ProgrammingRuby/)). +But only `public` methods can be actions for controllers. If you refresh <http://localhost:3000/posts/new> now, you'll get a new error:  -This error indicates that Rails cannot find the `new` action inside the `PostsController` that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it you wanted actions during the generation process. +This error indicates that Rails cannot find the `new` action inside the `PostsController` +that you just generated. This is because when controllers are generated in Rails +they are empty by default, unless you tell it you wanted actions during the +generation process. -To manually define an action inside a controller, all you need to do is to define a new method inside the controller. Open `app/controllers/posts_controller.rb` and inside the `PostsController` class, define a `new` method like this: +To manually define an action inside a controller, all you need to do is to +define a new method inside the controller. Open `app/controllers/posts_controller.rb` +and inside the `PostsController` class, define a `new` method like this: ```ruby def new end ``` -With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new> you'll see another error: +With the `new` method defined in `PostsController`, if you refresh <http://localhost:3000/posts/new> +you'll see another error:  -You're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out. +You're getting this error now because Rails expects plain actions like this one +to have views associated with them to display their information. With no view +available, Rails errors out. -In the above image, the bottom line has been truncated. Let's see what the full thing looks like: +In the above image, the bottom line has been truncated. Let's see what the full +thing looks like: <blockquote> Missing template posts/new, application/new with {locale:[:en], formats:[:html], handlers:[:erb, :builder, :coffee]}. Searched in: * "/path/to/blog/app/views" </blockquote> -That's quite a lot of text! Let's quickly go through and understand what each part of it does. - -The first part identifies what template is missing. In this case, it's the `posts/new` template. Rails will first look for this template. If not found, then it will attempt to load a template called `application/new`. It looks for one here because the `PostsController` inherits from `ApplicationController`. - -The next part of the message contains a hash. The `:locale` key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English — or "en" — template. The next key, `:formats` specifies the format of template to be served in response. The default format is `:html`, and so Rails is looking for an HTML template. The final key, `:handlers`, is telling us what _template handlers_ could be used to render our template. `:erb` is most commonly used for HTML templates, `:builder` is used for XML templates, and `:coffee` uses CoffeeScript to build JavaScript templates. - -The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths. - -The simplest template that would work in this case would be one located at `app/views/posts/new.html.erb`. The extension of this file name is key: the first extension is the _format_ of the template, and the second extension is the _handler_ that will be used. Rails is attempting to find a template called `posts/new` within `app/views` for the application. The format for this template can only be `html` and the handler must be one of `erb`, `builder` or `coffee`. Because you want to create a new HTML form, you will be using the `ERB` language. Therefore the file should be called `posts/new.html.erb` and needs to be located inside the `app/views` directory of the application. - -Go ahead now and create a new file at `app/views/posts/new.html.erb` and write this content in it: +That's quite a lot of text! Let's quickly go through and understand what each +part of it does. + +The first part identifies what template is missing. In this case, it's the +`posts/new` template. Rails will first look for this template. If not found, +then it will attempt to load a template called `application/new`. It looks for +one here because the `PostsController` inherits from `ApplicationController`. + +The next part of the message contains a hash. The `:locale` key in this hash +simply indicates what spoken language template should be retrieved. By default, +this is the English - or "en" - template. The next key, `:formats` specifies the +format of template to be served in response. The default format is `:html`, and +so Rails is looking for an HTML template. The final key, `:handlers`, is telling +us what _template handlers_ could be used to render our template. `:erb` is most +commonly used for HTML templates, `:builder` is used for XML templates, and +`:coffee` uses CoffeeScript to build JavaScript templates. + +The final part of this message tells us where Rails has looked for the templates. +Templates within a basic Rails application like this are kept in a single +location, but in more complex applications it could be many different paths. + +The simplest template that would work in this case would be one located at +`app/views/posts/new.html.erb`. The extension of this file name is key: the +first extension is the _format_ of the template, and the second extension is the +_handler_ that will be used. Rails is attempting to find a template called +`posts/new` within `app/views` for the application. The format for this template +can only be `html` and the handler must be one of `erb`, `builder` or `coffee`. +Because you want to create a new HTML form, you will be using the `ERB` +language. Therefore the file should be called `posts/new.html.erb` and needs to +be located inside the `app/views` directory of the application. + +Go ahead now and create a new file at `app/views/posts/new.html.erb` and write +this content in it: ```html <h1>New Post</h1> ``` -When you refresh <http://localhost:3000/posts/new> you'll now see that the page has a title. The route, controller, action and view are now working harmoniously! It's time to create the form for a new post. +When you refresh <http://localhost:3000/posts/new> you'll now see that the page +has a title. The route, controller, action and view are now working +harmoniously! It's time to create the form for a new post. ### The first form @@ -377,14 +492,21 @@ method called `form_for`. To use this method, add this code into `app/views/post <% end %> ``` -If you refresh the page now, you'll see the exact same form as in the example. Building forms in Rails is really just that easy! +If you refresh the page now, you'll see the exact same form as in the example. +Building forms in Rails is really just that easy! When you call `form_for`, you pass it an identifying object for this form. In this case, it's the symbol `:post`. This tells the `form_for` helper what this form is for. Inside the block for this method, the -`FormBuilder` object — represented by `f` — is used to build two labels and two text fields, one each for the title and text of a post. Finally, a call to `submit` on the `f` object will create a submit button for the form. +`FormBuilder` object - represented by `f` - is used to build two labels and two +text fields, one each for the title and text of a post. Finally, a call to +`submit` on the `f` object will create a submit button for the form. -There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the `action` attribute for the form is pointing at `/posts/new`. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new post. +There's one problem with this form though. If you inspect the HTML that is +generated, by viewing the source of the page, you will see that the `action` +attribute for the form is pointing at `/posts/new`. This is a problem because +this route goes to the very page that you're on right at the moment, and that +route should only be used to display the form for a new post. The form needs to use a different URL in order to go somewhere else. This can be done quite simply with the `:url` option of `form_for`. @@ -419,15 +541,21 @@ the form will (by default) send a `POST` request to that route. This is associated with the `create` action of the current controller, the `PostsController`. -With the form and its associated route defined, you will be able to fill in the form and then click the submit button to begin the process of creating a new post, so go ahead and do that. When you submit the form, you should see a familiar error: +With the form and its associated route defined, you will be able to fill in the +form and then click the submit button to begin the process of creating a new +post, so go ahead and do that. When you submit the form, you should see a +familiar error:  -You now need to create the `create` action within the `PostsController` for this to work. +You now need to create the `create` action within the `PostsController` for this +to work. ### Creating posts -To make the "Unknown action" go away, you can define a `create` action within the `PostsController` class in `app/controllers/posts_controller.rb`, underneath the `new` action: +To make the "Unknown action" go away, you can define a `create` action within +the `PostsController` class in `app/controllers/posts_controller.rb`, underneath +the `new` action: ```ruby class PostsController < ApplicationController @@ -439,9 +567,14 @@ class PostsController < ApplicationController end ``` -If you re-submit the form now, you'll see another familiar error: a template is missing. That's ok, we can ignore that for now. What the `create` action should be doing is saving our new post to a database. +If you re-submit the form now, you'll see another familiar error: a template is +missing. That's ok, we can ignore that for now. What the `create` action should +be doing is saving our new post to a database. -When a form is submitted, the fields of the form are sent to Rails as _parameters_. These parameters can then be referenced inside the controller actions, typically to perform a particular task. To see what these parameters look like, change the `create` action to this: +When a form is submitted, the fields of the form are sent to Rails as +_parameters_. These parameters can then be referenced inside the controller +actions, typically to perform a particular task. To see what these parameters +look like, change the `create` action to this: ```ruby def create @@ -449,15 +582,23 @@ def create end ``` -The `render` method here is taking a very simple hash with a key of `text` and value of `params[:post].inspect`. The `params` method is the object which represents the parameters (or fields) coming in from the form. The `params` method returns an `ActiveSupport::HashWithIndifferentAccess` object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form. +The `render` method here is taking a very simple hash with a key of `text` and +value of `params[:post].inspect`. The `params` method is the object which +represents the parameters (or fields) coming in from the form. The `params` +method returns an `ActiveSupport::HashWithIndifferentAccess` object, which +allows you to access the keys of the hash using either strings or symbols. In +this situation, the only parameters that matter are the ones from the form. -If you re-submit the form one more time you'll now no longer get the missing template error. Instead, you'll see something that looks like the following: +If you re-submit the form one more time you'll now no longer get the missing +template error. Instead, you'll see something that looks like the following: ```ruby {"title"=>"First post!", "text"=>"This is my first post."} ``` -This action is now displaying the parameters for the post that are coming in from the form. However, this isn't really all that helpful. Yes, you can see the parameters but nothing in particular is being done with them. +This action is now displaying the parameters for the post that are coming in +from the form. However, this isn't really all that helpful. Yes, you can see the +parameters but nothing in particular is being done with them. ### Creating the Post model @@ -569,13 +710,50 @@ Finally, we redirect the user to the `show` action, which we'll define later. TIP: As we'll see later, `@post.save` returns a boolean indicating whether the model was saved or not. +If you now go to +<http://localhost:3000/posts/new> you'll *almost* be able to create a post. Try +it! You should get an error that looks like this: + + + +Rails has several security features that help you write secure applications, +and you're running into one of them now. This one is called +`strong_parameters`, which requires us to tell Rails exactly which parameters +we want to accept in our controllers. In this case, we want to allow the +`title` and `text` parameters, so change your `create` controller action to +look like this: + +```ruby +def create + @post = Post.new(post_params) + + @post.save + redirect_to @post +end + +private + def post_params + params.require(:post).permit(:title, :text) + end +``` + +See the `permit`? It allows us to accept both `title` and `text` in this +action. + +TIP: Note that `def post_params` is private. This new approach prevents an +attacker from setting the model's attributes by manipulating the hash passed to +the model. +For more information, refer to +[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). + ### Showing Posts If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the `show` action before proceeding. -As we have seen in the output of `rake routes`, the route for `show` action is as follows: +As we have seen in the output of `rake routes`, the route for `show` action is +as follows: ```ruby post GET /posts/:id(.:format) posts#show @@ -594,8 +772,9 @@ end ``` A couple of things to note. We use `Post.find` to find the post we're -interested in. We also use an instance variable (prefixed by `@`) to -hold a reference to the post object. We do this because Rails will pass all instance +interested in, passing in `params[:id]` to get the `:id` parameter from the +request. We also use an instance variable (prefixed by `@`) to hold a +reference to the post object. We do this because Rails will pass all instance variables to the view. Now, create a new file `app/views/posts/show.html.erb` with the following @@ -613,44 +792,11 @@ content: </p> ``` -If you now go to -<http://localhost:3000/posts/new> you'll *almost* be able to create a post. Try -it! You should get an error that looks like this: - - - -Rails has several security features that help you write secure applications, -and you're running into one of them now. This one is called -`strong_parameters`, which requires us to tell Rails exactly which parameters -we want to accept in our controllers. In this case, we want to allow the -`title` and `text` parameters, so change your `create` controller action to -look like this: - -``` -def create - @post = Post.new(post_params) - - @post.save - redirect_to @post -end - -private - def post_params - params.require(:post).permit(:title, :text) - end -``` - -See the `permit`? It allows us to accept both `title` and `text` in this -action. With this change, you should finally be able to create new `Post`s. +With this change, you should finally be able to create new posts. Visit <http://localhost:3000/posts/new> and give it a try!  -TIP: Note that `def post_params` is private. This new approach prevents an attacker from -setting the model's attributes by manipulating the hash passed to the model. -For more information, refer to -[this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). - ### Listing all posts We still need a way to list all our posts, so let's do that. @@ -688,7 +834,8 @@ And then finally a view for this action, located at `app/views/posts/index.html. </table> ``` -Now if you go to `http://localhost:3000/posts` you will see a list of all the posts that you have created. +Now if you go to `http://localhost:3000/posts` you will see a list of all the +posts that you have created. ### Adding links @@ -706,13 +853,17 @@ The `link_to` method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. -Let's add links to the other views as well, starting with adding this "New Post" link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag: +Let's add links to the other views as well, starting with adding this "New Post" +link to `app/views/posts/index.html.erb`, placing it above the `<table>` tag: ```erb <%= link_to 'New post', new_post_path %> ``` -This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template — `app/views/posts/new.html.erb` — to go back to the `index` action. Do this by adding this underneath the form in this template: +This link will allow you to bring up the form that lets you create a new post. +You should also add a link to this template - `app/views/posts/new.html.erb` - +to go back to the `index` action. Do this by adding this underneath the form in +this template: ```erb <%= form_for :post do |f| %> @@ -722,7 +873,9 @@ This link will allow you to bring up the form that lets you create a new post. Y <%= link_to 'Back', posts_path %> ``` -Finally, add another link to the `app/views/posts/show.html.erb` template to go back to the `index` action as well, so that people who are viewing a single post can go back and view the whole list again: +Finally, add another link to the `app/views/posts/show.html.erb` template to go +back to the `index` action as well, so that people who are viewing a single post +can go back and view the whole list again: ```html+erb <p> @@ -808,8 +961,11 @@ private The `new` action is now creating a new instance variable called `@post`, and you'll see why that is in just a few moments. -Notice that inside the `create` action we use `render` instead of `redirect_to` when `save` -returns `false`. The `render` method is used so that the `@post` object is passed back to the `new` template when it is rendered. This rendering is done within the same request as the form submission, whereas the `redirect_to` will tell the browser to issue another request. +Notice that inside the `create` action we use `render` instead of `redirect_to` +when `save` returns `false`. The `render` method is used so that the `@post` +object is passed back to the `new` template when it is rendered. This rendering +is done within the same request as the form submission, whereas the `redirect_to` +will tell the browser to issue another request. If you reload <http://localhost:3000/posts/new> and @@ -854,7 +1010,8 @@ A few things are going on. We check if there are any errors with errors with `@post.errors.full_messages`. `pluralize` is a rails helper that takes a number and a string as its -arguments. If the number is greater than one, the string will be automatically pluralized. +arguments. If the number is greater than one, the string will be automatically +pluralized. The reason why we added `@post = Post.new` in `posts_controller` is that otherwise `@post` would be `nil` in our view, and calling @@ -871,7 +1028,8 @@ attempt to do just that on the new post form [(http://localhost:3000/posts/new)] ### Updating Posts -We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating posts. +We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating +posts. The first step we'll take is adding an `edit` action to `posts_controller`. @@ -1046,8 +1204,8 @@ which URI and method to use. For more information about this use of `form_for`, see [Resource-oriented style](//api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style). -Now, let's update the `app/views/posts/new.html.erb` view to use this new partial, rewriting it -completely: +Now, let's update the `app/views/posts/new.html.erb` view to use this new +partial, rewriting it completely: ```html+erb <h1>New post</h1> @@ -1086,8 +1244,8 @@ people to craft malicious URLs like this: ``` We use the `delete` method for destroying resources, and this route is mapped to -the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't exist yet, but is -provided below: +the `destroy` action inside `app/controllers/posts_controller.rb`, which doesn't +exist yet, but is provided below: ```ruby def destroy @@ -1128,13 +1286,14 @@ together. </table> ``` -Here we're using `link_to` in a different way. We pass the named route as the second argument, -and then the options as another argument. The `:method` and `:'data-confirm'` -options are used as HTML5 attributes so that when the link is clicked, -Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. -This is done via the JavaScript file `jquery_ujs` which is automatically included -into your application's layout (`app/views/layouts/application.html.erb`) when you -generated the application. Without this file, the confirmation dialog box wouldn't appear. +Here we're using `link_to` in a different way. We pass the named route as the +second argument, and then the options as another argument. The `:method` and +`:'data-confirm'` options are used as HTML5 attributes so that when the link is +clicked, Rails will first show a confirm dialog to the user, and then submit the +link with method `delete`. This is done via the JavaScript file `jquery_ujs` +which is automatically included into your application's layout +(`app/views/layouts/application.html.erb`) when you generated the application. +Without this file, the confirmation dialog box wouldn't appear.  @@ -1149,8 +1308,8 @@ For more information about routing, see Adding a Second Model --------------------- -It's time to add a second model to the application. The second model will handle comments on -posts. +It's time to add a second model to the application. The second model will handle +comments on posts. ### Generating a Model @@ -1228,8 +1387,8 @@ this way: * One post can have many comments. In fact, this is very close to the syntax that Rails uses to declare this -association. You've already seen the line of code inside the `Comment` model (app/models/comment.rb) that -makes each comment belong to a Post: +association. You've already seen the line of code inside the `Comment` model +(app/models/comment.rb) that makes each comment belong to a Post: ```ruby class Comment < ActiveRecord::Base @@ -1674,7 +1833,7 @@ along with a number of others. ### Other Security Considerations Security, especially in web applications, is a broad and detailed area. Security -in your Rails application is covered in more depth in +in your Rails application is covered in more depth in The [Ruby on Rails Security Guide](security.html) @@ -1691,12 +1850,19 @@ free to consult these support resources: * The [Ruby on Rails mailing list](http://groups.google.com/group/rubyonrails-talk) * The [#rubyonrails](irc://irc.freenode.net/#rubyonrails) channel on irc.freenode.net -Rails also comes with built-in help that you can generate using the rake command-line utility: +Rails also comes with built-in help that you can generate using the rake +command-line utility: -* Running `rake doc:guides` will put a full copy of the Rails Guides in the `doc/guides` folder of your application. Open `doc/guides/index.html` in your web browser to explore the Guides. -* Running `rake doc:rails` will put a full copy of the API documentation for Rails in the `doc/api` folder of your application. Open `doc/api/index.html` in your web browser to explore the API documentation. +* Running `rake doc:guides` will put a full copy of the Rails Guides in the + `doc/guides` folder of your application. Open `doc/guides/index.html` in your + web browser to explore the Guides. +* Running `rake doc:rails` will put a full copy of the API documentation for + Rails in the `doc/api` folder of your application. Open `doc/api/index.html` + in your web browser to explore the API documentation. -TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake task you need to install the RedCloth gem. Add it to your `Gemfile` and run `bundle install` and you're ready to go. +TIP: To be able to generate the Rails Guides locally with the `doc:guides` rake +task you need to install the RedCloth gem. Add it to your `Gemfile` and run +`bundle install` and you're ready to go. Configuration Gotchas --------------------- diff --git a/guides/source/i18n.md b/guides/source/i18n.md index facfb96d98..948b6f167c 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -13,8 +13,8 @@ So, in the process of _internationalizing_ your Rails application you have to: In the process of _localizing_ your application you'll probably want to do the following three things: -* Replace or supplement Rails' default locale — e.g. date and time formats, month names, Active Record model names, etc. -* Abstract strings in your application into keyed dictionaries — e.g. flash messages, static text in your views, etc. +* Replace or supplement Rails' default locale - e.g. date and time formats, month names, Active Record model names, etc. +* Abstract strings in your application into keyed dictionaries - e.g. flash messages, static text in your views, etc. * Store the resulting dictionaries somewhere. This guide will walk you through the I18n API and contains a tutorial on how to internationalize a Rails application from the start. @@ -28,7 +28,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Rails [I18n Wiki](http://rails-i18n.org/wiki) for more information. +NOTE: The Ruby I18n framework provides you with all necessary means for internationalization/localization of your Rails application. You may, however, use any of various plugins and extensions available, which add additional functionality or features. See the Ruby [I18n Wiki](http://ruby-i18n.org/wiki) for more information. How I18n in Ruby on Rails Works ------------------------------- @@ -38,13 +38,13 @@ Internationalization is a complex problem. Natural languages differ in so many w * providing support for English and similar languages out of the box * making it easy to customize and extend everything for other languages -As part of this solution, **every static string in the Rails framework** — e.g. Active Record validation messages, time and date formats — **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. +As part of this solution, **every static string in the Rails framework** - e.g. Active Record validation messages, time and date formats - **has been internationalized**, so _localization_ of a Rails application means "over-riding" these defaults. ### The Overall Architecture of the Library Thus, the Ruby I18n gem is split into two parts: -* The public API of the i18n framework — a Ruby module with public methods that define how the library works +* The public API of the i18n framework - a Ruby module with public methods that define how the library works * A default backend (which is intentionally named _Simple_ backend) that implements these methods As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. @@ -267,7 +267,7 @@ NOTE: Have a look at two plugins which simplify work with routes in this way: Sv ### Setting the Locale from the Client Supplied Information -In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites — see the box about _sessions_, _cookies_ and RESTful architecture above. +In specific cases, it would make sense to set the locale from client-supplied information, i.e. not from the URL. This information may come for example from the users' preferred language (set in their browser), can be based on the users' geographical location inferred from their IP, or users can provide it simply by choosing the locale in your application interface and saving it to their profile. This approach is more suitable for web-based applications or services, not for websites - see the box about _sessions_, _cookies_ and RESTful architecture above. #### Using `Accept-Language` @@ -282,21 +282,22 @@ def set_locale I18n.locale = extract_locale_from_accept_language_header logger.debug "* Locale set to '#{I18n.locale}'" end + private -def extract_locale_from_accept_language_header - request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first -end + def extract_locale_from_accept_language_header + request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first + end ``` Of course, in a production environment you would need much more robust code, and could use a plugin such as Iain Hecker's [http_accept_language](https://github.com/iain/http_accept_language/tree/master) or even Rack middleware such as Ryan Tomayko's [locale](https://github.com/rack/rack-contrib/blob/master/lib/rack/contrib/locale.rb). #### Using GeoIP (or Similar) Database -Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above — you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. +Another way of choosing the locale from client information would be to use a database for mapping the client IP to the region, such as [GeoIP Lite Country](http://www.maxmind.com/app/geolitecountry). The mechanics of the code would be very similar to the code above - you would need to query the database for the user's IP, and look up your preferred locale for the country/region/city returned. #### User Profile -You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above — you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. +You can also provide users of your application with means to set (and possibly over-ride) the locale in your application interface, as well. Again, mechanics for this approach would be very similar to the code above - you'd probably let users choose a locale from a dropdown list and save it to their profile in the database. Then you'd set the locale to this value. Internationalizing your Application ----------------------------------- @@ -315,6 +316,17 @@ end ``` ```ruby +# app/controllers/application_controller.rb +class ApplicationController < ActionController::Base + before_action :set_locale + + def set_locale + I18n.locale = params[:locale] || I18n.default_locale + end +end +``` + +```ruby # app/controllers/home_controller.rb class HomeController < ApplicationController def index @@ -399,7 +411,7 @@ en: ### Adding Date/Time Formats -OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option — by default the `:default` format is used. +OK! Now let's add a timestamp to the view, so we can demo the **date/time localization** feature as well. To localize the time format you pass the Time object to `I18n.l` or (preferably) use Rails' `#l` helper. You can pick a format by passing the `:format` option - by default the `:default` format is used. ```erb # app/views/home/index.html.erb @@ -499,7 +511,7 @@ I18n.t :message I18n.t 'message' ``` -The `translate` method also takes a `:scope` option which can contain one or more additional keys that will be used to specify a “namespace” or scope for a translation key: +The `translate` method also takes a `:scope` option which can contain one or more additional keys that will be used to specify a "namespace" or scope for a translation key: ```ruby I18n.t :record_invalid, scope: [:activerecord, :errors, :messages] @@ -731,6 +743,19 @@ en: Then `User.model_name.human` will return "Dude" and `User.human_attribute_name("login")` will return "Handle". +You can also set a plural form for model names, adding as following: + +```ruby +en: + activerecord: + models: + user: + one: Dude + other: Dudes +``` + +Then `User.model_name.human(count: 2)` will return "Dudes". With `count: 1` or without params will return "Dude". + #### Error Message Scopes Active Record validation error messages can also be translated easily. Active Record gives you a couple of namespaces where you can place your message translations in order to provide different messages and translation for certain models, attributes, and/or validations. It also transparently takes single table inheritance into account. @@ -870,15 +895,15 @@ Rails uses fixed strings and other localizations, such as format strings and oth #### Action View Helper Methods -* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L51) translations. +* `distance_of_time_in_words` translates and pluralizes its result and interpolates the number of seconds, minutes, hours, and so on. See [datetime.distance_in_words](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L4) translations. -* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L83) scope if applicable. +* `datetime_select` and `select_month` use translated month names for populating the resulting select tag. See [date.month_names](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L15) for translations. `datetime_select` also looks up the order option from [date.order](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L18) (unless you pass the option explicitly). All date selection helpers translate the prompt using the translations in the [datetime.prompts](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L39) scope if applicable. -* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/actionview/lib/action_view/locale/en.yml#L2) scope. +* The `number_to_currency`, `number_with_precision`, `number_to_percentage`, `number_with_delimiter`, and `number_to_human_size` helpers use the number format settings located in the [number](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L37) scope. #### Active Model Methods -* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L29) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". +* `model_name.human` and `human_attribute_name` use translations for model names and attribute names if available in the [activerecord.models](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L36) scope. They also support translations for inherited class names (e.g. for use with STI) as explained above in "Error message scopes". * `ActiveModel::Errors#generate_message` (which is used by Active Model validations but may also be used manually) uses `model_name.human` and `human_attribute_name` (see above). It also translates the error message and supports translations for inherited class names as explained above in "Error message scopes". @@ -886,7 +911,7 @@ Rails uses fixed strings and other localizations, such as format strings and oth #### Active Support Methods -* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L30) scope. +* `Array#to_sentence` uses format settings as given in the [support.array](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/locale/en.yml#L33) scope. Customize your I18n Setup ------------------------- @@ -920,7 +945,7 @@ ReservedInterpolationKey # the translation contains a reserved interpolation UnknownFileType # the backend does not know how to handle a file type that was added to I18n.load_path ``` -The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for `MissingTranslationData` exceptions. When a `MissingTranslationData` exception has been caught, it will return the exception’s error message string containing the missing key/scope. +The I18n API will catch all of these exceptions when they are thrown in the backend and pass them to the default_exception_handler method. This method will re-raise all exceptions except for `MissingTranslationData` exceptions. When a `MissingTranslationData` exception has been caught, it will return the exception's error message string containing the missing key/scope. The reason for this is that during development you'd usually want your views to still render even though a translation is missing. diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 26259408b4..91d12b4432 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -40,7 +40,7 @@ This file is as follows: ```ruby #!/usr/bin/env ruby APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +require_relative '../config/boot' require 'rails/commands' ``` @@ -217,12 +217,12 @@ With the `default_options` set to this: ```ruby def default_options { - :environment => ENV['RACK_ENV'] || "development", - :pid => nil, - :Port => 9292, - :Host => "0.0.0.0", - :AccessLog => [], - :config => "config.ru" + environment: ENV['RACK_ENV'] || "development", + pid: nil, + Port: 9292, + Host: "0.0.0.0", + AccessLog: [], + config: "config.ru" } end ``` diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 71d3c5638b..0513066f5a 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -101,19 +101,15 @@ You're encouraged to help improve the quality of this guide. </p> <p> - If you see any typos or factual errors that you are confident to fix, - you can push the fix to <%= link_to 'docrails', 'https://github.com/rails/docrails' %> (Ask - the <%= link_to 'Rails core team', 'http://rubyonrails.org/core' %> for push access). - If you choose to open a pull request, please do it in <%= link_to 'Rails', 'https://github.com/rails/rails' %> - and not in the <%= link_to 'docrails', 'https://github.com/rails/docrails' %> repository. - Commits made to docrails are still reviewed, but that happens after you've submitted your - contribution. <%= link_to 'docrails', 'https://github.com/rails/docrails' %> is - cross-merged with master periodically. + Please contribute if you see any typos or factual errors. + To get started, you can read our <%= link_to 'documentation contributions', 'http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation' %> section. </p> <p> You may also find incomplete content, or stuff that is not up to date. - Please do add any missing documentation for master. Check the - <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %> + Please do add any missing documentation for master. Make sure to check + <%= link_to 'Edge Guides','http://edgeguides.rubyonrails.org' %> first to verify + if the issues are already fixed or not on the master branch. + Check the <%= link_to 'Ruby on Rails Guides Guidelines', 'ruby_on_rails_guides_guidelines.html' %> for style and conventions. </p> <p> diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 6100fc89c8..ab39b39a1c 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -305,7 +305,7 @@ braces. You can use the following modifiers: For instance, running ```bash -$ rails generate migration AddDetailsToProducts price:decimal{5,2} supplier:references{polymorphic} +$ rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic} ``` will produce a migration that looks like this @@ -692,10 +692,15 @@ Neither of these Rake tasks do anything you could not do with `db:migrate`. They are simply more convenient, since you do not need to explicitly specify the version to migrate to. +### Setup the Database + +The `rake db:setup` task will create the database, load the schema and initialize +it with the seed data. + ### Resetting the Database -The `rake db:reset` task will drop the database, recreate it and load the -current schema into it. +The `rake db:reset` task will drop the database and set it up again. This is +functionally equivalent to `rake db:drop db:setup`. NOTE: This is not the same as running all the migrations. It will only use the contents of the current schema.rb file. If a migration can't be rolled back, diff --git a/guides/source/nested_model_forms.md b/guides/source/nested_model_forms.md index b90b3bb5fc..855fab18e3 100644 --- a/guides/source/nested_model_forms.md +++ b/guides/source/nested_model_forms.md @@ -9,7 +9,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it’s **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). +NOTE: This guide assumes the user knows how to use the [Rails form helpers](form_helpers.html) in general. Also, it's **not** an API reference. For a complete reference please visit [the Rails API documentation](http://api.rubyonrails.org/). Model setup @@ -56,7 +56,7 @@ end ### Custom model -As you might have inflected from this explanation, you _don’t_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: +As you might have inflected from this explanation, you _don't_ necessarily need an ActiveRecord::Base model to use this functionality. The following examples are sufficient to enable the nested model form behavior: #### Single associated object @@ -177,7 +177,7 @@ When this form is posted the Rails parameter parser will construct a hash like t } ``` -That’s it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. +That's it. The controller will simply pass this hash on to the model from the `create` action. The model will then handle building the `address` association for you and automatically save it when the parent (`person`) is saved. #### Nested form for a collection of associated objects diff --git a/guides/source/plugins.md b/guides/source/plugins.md index 9077e424c8..b52504b104 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -5,7 +5,7 @@ A Rails plugin is either an extension or a modification of the core framework. P * a way for developers to share bleeding-edge ideas without hurting the stable code base * a segmented architecture so that units of code can be fixed or updated on their own release schedule -* an outlet for the core developers so that they don’t have to include every cool new feature under the sun +* an outlet for the core developers so that they don't have to include every cool new feature under the sun After reading this guide, you will know: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index 0c70f379be..711d910184 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -47,7 +47,7 @@ The following sections outline the primary methods provided by the API: ### gem(*args) -Adds a `gem` entry for the supplied gem to the generated application’s `Gemfile`. +Adds a `gem` entry for the supplied gem to the generated application's `Gemfile`. For example, if your application depends on the gems `bj` and `nokogiri`: @@ -98,7 +98,7 @@ A block can be used in place of the `data` argument. ### vendor/lib/file/initializer(filename, data = nil, &block) -Adds an initializer to the generated application’s `config/initializers` directory. +Adds an initializer to the generated application's `config/initializers` directory. Let's say you like using `Object#not_nil?` and `Object#not_blank?`: @@ -127,7 +127,7 @@ file 'app/components/foo.rb', <<-CODE CODE ``` -That’ll create the `app/components` directory and put `foo.rb` in there. +That'll create the `app/components` directory and put `foo.rb` in there. ### rakefile(filename, data = nil, &block) @@ -197,7 +197,7 @@ end ### ask(question) -`ask()` gives you a chance to get some feedback from the user and use it in your templates. Let's say you want your user to name the new shiny library you’re adding: +`ask()` gives you a chance to get some feedback from the user and use it in your templates. Let's say you want your user to name the new shiny library you're adding: ```ruby lib_name = ask("What do you want to call the shiny library ?") @@ -211,7 +211,7 @@ CODE ### yes?(question) or no?(question) -These methods let you ask questions from templates and decide the flow based on the user’s answer. Let's say you want to freeze rails only if the user wants to: +These methods let you ask questions from templates and decide the flow based on the user's answer. Let's say you want to freeze rails only if the user wants to: ```ruby rake("rails:freeze:gems") if yes?("Freeze rails gems?") diff --git a/guides/source/routing.md b/guides/source/routing.md index 76c4c25108..37525c48a6 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -863,8 +863,8 @@ resources :user_permissions, controller: 'admin/user_permissions' This will route to the `Admin::UserPermissions` controller. NOTE: Only the directory notation is supported. Specifying the -controller with Ruby constant notation (eg. `:controller => -'Admin::UserPermissions'`) can lead to routing problems and results in +controller with Ruby constant notation (eg. `controller: 'Admin::UserPermissions'`) +can lead to routing problems and results in a warning. ### Specifying Constraints diff --git a/guides/source/security.md b/guides/source/security.md index e4db26c64e..4aba39f55a 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -58,7 +58,7 @@ WARNING: _Stealing a user's session id lets an attacker use the web application Many web applications have an authentication system: a user provides a user name and password, the web application checks them and stores the corresponding user id in the session hash. From now on, the session is valid. On every request the application will load the user, identified by the user id in the session, without the need for new authentication. The session id in the cookie identifies the session. -Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user – with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: +Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: * Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: @@ -72,7 +72,7 @@ Hence, the cookie serves as temporary authentication for the web application. An * Instead of stealing a cookie unknown to the attacker, he fixes a user's session identifier (in the cookie) known to him. Read more about this so-called session fixation later. -The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10–$1000 (depending on the available amount of funds), $0.40–$20 for credit card numbers, $1–$8 for online auction site accounts and $4–$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf). +The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from $10-$1000 (depending on the available amount of funds), $0.40-$20 for credit card numbers, $1-$8 for online auction site accounts and $4-$30 for email passwords, according to the [Symantec Global Internet Security Threat Report](http://eval.symantec.com/mktginfo/enterprise/white_papers/b-whitepaper_internet_security_threat_report_xiii_04-2008.en-us.pdf). ### Session Guidelines @@ -134,7 +134,7 @@ This attack focuses on fixing a user's session id known to the attacker, and for * As the new trap session is unused, the web application will require the user to authenticate. * From now on, the victim and the attacker will co-use the web application with the same session: The session became valid and the victim didn't notice the attack. -### Session Fixation – Countermeasures +### Session Fixation - Countermeasures TIP: _One line of code will protect you from session fixation._ @@ -187,11 +187,11 @@ In the <a href="#sessions">session chapter</a> you have learned that most Rails * Bob's session at www.webapp.com is still alive, because he didn't log out a few minutes ago. * By viewing the post, the browser finds an image tag. It tries to load the suspected image from www.webapp.com. As explained before, it will also send along the cookie with the valid session id. * The web application at www.webapp.com verifies the user information in the corresponding session hash and destroys the project with the ID 1. It then returns a result page which is an unexpected result for the browser, so it will not display the image. -* Bob doesn't notice the attack — but a few days later he finds out that project number one is gone. +* Bob doesn't notice the attack - but a few days later he finds out that project number one is gone. -It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere – in a forum, blog post or email. +It is important to notice that the actual crafted image or link doesn't necessarily have to be situated in the web application's domain, it can be anywhere - in a forum, blog post or email. -CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) — less than 0.1% in 2006 — but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work – _CSRF is an important security issue_. +CSRF appears very rarely in CVE (Common Vulnerabilities and Exposures) - less than 0.1% in 2006 - but it really is a 'sleeping giant' [Grossman]. This is in stark contrast to the results in my (and others) security contract work - _CSRF is an important security issue_. ### CSRF Countermeasures @@ -288,9 +288,9 @@ This example is a Base64 encoded JavaScript which displays a simple message box. NOTE: _Make sure file uploads don't overwrite important files, and process media files asynchronously._ -Many web applications allow users to upload files. _File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like “../../../etc/passwd”, it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so – one more reason to run web servers, database servers and other programs as a less privileged Unix user. +Many web applications allow users to upload files. _File names, which the user may choose (partly), should always be filtered_ as an attacker could use a malicious file name to overwrite any file on the server. If you store file uploads at /var/www/uploads, and the user enters a file name like "../../../etc/passwd", it may overwrite an important file. Of course, the Ruby interpreter would need the appropriate permissions to do so - one more reason to run web servers, database servers and other programs as a less privileged Unix user. -When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all “../” in a file name and an attacker uses a string such as “....//” - the result will be “../”. It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master:) +When filtering user input file names, _don't try to remove malicious parts_. Think of a situation where the web application removes all "../" in a file name and an attacker uses a string such as "....//" - the result will be "../". It is best to use a whitelist approach, which _checks for the validity of a file name with a set of accepted characters_. This is opposed to a blacklist approach which attempts to remove not allowed characters. In case it isn't a valid file name, reject it (or replace not accepted characters), but don't remove them. Here is the file name sanitizer from the [attachment_fu plugin](https://github.com/technoweenie/attachment_fu/tree/master:) ```ruby def sanitize_filename(filename) @@ -313,7 +313,7 @@ The solution to this is best to _process media files asynchronously_: Save the m WARNING: _Source code in uploaded files may be executed when placed in specific directories. Do not place file uploads in Rails' /public directory if it is Apache's home directory._ -The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file “file.cgi” with code in it, which will be executed when someone downloads the file. +The popular Apache web server has an option called DocumentRoot. This is the home directory of the web site, everything in this directory tree will be served by the web server. If there are files with a certain file name extension, the code in it will be executed when requested (might require some options to be set). Examples for this are PHP and CGI files. Now think of a situation where an attacker uploads a file "file.cgi" with code in it, which will be executed when someone downloads the file. _If your Apache DocumentRoot points to Rails' /public directory, do not put file uploads in it_, store files at least one level downwards. @@ -327,7 +327,7 @@ Just as you have to filter file names for uploads, you have to do so for downloa send_file('/var/www/uploads/' + params[:filename]) ``` -Simply pass a file name like “../../../etc/passwd” to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_: +Simply pass a file name like "../../../etc/passwd" to download the server's login information. A simple solution against this, is to _check that the requested file is in the expected directory_: ```ruby basename = File.expand_path(File.join(File.dirname(__FILE__), '../../files')) @@ -406,7 +406,7 @@ NOTE: _Brute-force attacks on accounts are trial and error attacks on the login A list of user names for your web application may be misused to brute-force the corresponding passwords, because most people don't use sophisticated passwords. Most passwords are a combination of dictionary words and possibly numbers. So armed with a list of user names and a dictionary, an automatic program may find the correct password in a matter of minutes. -Because of this, most web applications will display a generic error message “user name or password not correct”, if one of these are not correct. If it said “the user name you entered has not been found”, an attacker could automatically compile a list of user names. +Because of this, most web applications will display a generic error message "user name or password not correct", if one of these are not correct. If it said "the user name you entered has not been found", an attacker could automatically compile a list of user names. However, what most web application designers neglect, are the forgot-password pages. These pages often admit that the entered user name or e-mail address has (not) been found. This allows an attacker to compile a list of user names and brute-force the accounts. @@ -481,7 +481,7 @@ A good password is a long alphanumeric combination of mixed cases. As this is qu INFO: _A common pitfall in Ruby's regular expressions is to match the string's beginning and end by ^ and $, instead of \A and \z._ -Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books make this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this: +Ruby uses a slightly different approach than many other languages to match the end and the beginning of a string. That is why even many Ruby and Rails books get this wrong. So how is this a security threat? Say you wanted to loosely validate a URL field and you used a simple regular expression like this: ```ruby /^https?:\/\/[^\n]+$/i @@ -495,7 +495,7 @@ http://hi.com */ ``` -This URL passes the filter because the regular expression matches – the second line, the rest does not matter. Now imagine we had a view that showed the URL like this: +This URL passes the filter because the regular expression matches - the second line, the rest does not matter. Now imagine we had a view that showed the URL like this: ```ruby link_to "Homepage", @user.homepage @@ -646,7 +646,7 @@ INFO: _The most widespread, and one of the most devastating security vulnerabili An entry point is a vulnerable URL and its parameters where an attacker can start an attack. -The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter – obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests. +The most common entry points are message posts, user comments, and guest books, but project titles, document names and search result pages have also been vulnerable - just about everywhere where the user can input data. But the input does not necessarily have to come from input boxes on web sites, it can be in any URL parameter - obvious, hidden or internal. Remember that the user may intercept any traffic. Applications, such as the [Live HTTP Headers Firefox plugin](http://livehttpheaders.mozdev.org/), or client-site proxies make it easy to change requests. XSS attacks work like this: An attacker injects some code, the web application saves it and displays it on a page, later presented to a victim. Most XSS examples simply display an alert box, but it is more powerful than that. XSS can steal the cookie, hijack the session, redirect the victim to a fake website, display advertisements for the benefit of the attacker, change elements on the web site to get confidential information or install malicious software through security holes in the web browser. @@ -698,10 +698,10 @@ You can mitigate these attacks (in the obvious way) by adding the [httpOnly](htt With web page defacement an attacker can do a lot of things, for example, present false information or lure the victim on the attackers web site to steal the cookie, login credentials or other sensitive data. The most popular way is to include code from external sources by iframes: ```html -<iframe name=”StatPage” src="http://58.xx.xxx.xxx" width=5 height=5 style=”display:none”></iframe> +<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe> ``` -This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the [Mpack attack framework](http://isc.sans.org/diary.html?storyid=3015). Mpack tries to install malicious software through security holes in the web browser – very successfully, 50% of the attacks succeed. +This loads arbitrary HTML and/or JavaScript from an external source and embeds it as part of the site. This iframe is taken from an actual attack on legitimate Italian sites using the [Mpack attack framework](http://isc.sans.org/diary.html?storyid=3015). Mpack tries to install malicious software through security holes in the web browser - very successfully, 50% of the attacks succeed. A more specialized attack could overlap the entire web site or display a login form, which looks the same as the site's original, but transmits the user name and password to the attacker's site. Or it could use CSS and/or JavaScript to hide a legitimate link in the web application, and display another one at its place which redirects to a fake web site. @@ -718,7 +718,7 @@ _It is very important to filter malicious input, but it is also important to esc Especially for XSS, it is important to do _whitelist input filtering instead of blacklist_. Whitelist filtering states the values allowed as opposed to the values not allowed. Blacklists are never complete. -Imagine a blacklist deletes “script” from the user input. Now the attacker injects “<scrscriptipt>”, and after the filter, “<script>” remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible: +Imagine a blacklist deletes "script" from the user input. Now the attacker injects "<scrscriptipt>", and after the filter, "<script>" remains. Earlier versions of Rails used a blacklist approach for the strip_tags(), strip_links() and sanitize() method. So this kind of injection was possible: ```ruby strip_tags("some<<b>script>alert('hello')<</b>/script>") @@ -744,7 +744,7 @@ Network traffic is mostly based on the limited Western alphabet, so new characte lert('XSS')> ``` -This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus “get to know your enemy”, is the [Hackvertor](https://hackvertor.co.uk/public). Rails' sanitize() method does a good job to fend off encoding attacks. +This example pops up a message box. It will be recognized by the above sanitize() filter, though. A great tool to obfuscate and encode strings, and thus "get to know your enemy", is the [Hackvertor](https://hackvertor.co.uk/public). Rails' sanitize() method does a good job to fend off encoding attacks. #### Examples from the Underground @@ -762,7 +762,7 @@ The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filte Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/article/9/Paper_A_PoC_of_a_cross_webmail_worm_XWW_called_Njuda_connection/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. -In December 2006, 34,000 actual user names and passwords were stolen in a [MySpace phishing attack](http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html). The idea of the attack was to create a profile page named “login_home_index_html”, so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. +In December 2006, 34,000 actual user names and passwords were stolen in a [MySpace phishing attack](http://news.netcraft.com/archives/2006/10/27/myspace_accounts_compromised_by_phishers.html). The idea of the attack was to create a profile page named "login_home_index_html", so the URL looked very convincing. Specially-crafted HTML and CSS was used to hide the genuine MySpace content from the page and instead display its own login form. The MySpace Samy worm will be discussed in the CSS Injection section. @@ -784,13 +784,13 @@ So the payload is in the style attribute. But there are no quotes allowed in the <div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')"> ``` -The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word “innerHTML”: +The eval() function is a nightmare for blacklist input filters, as it allows the style attribute to hide the word "innerHTML": ``` alert(eval('document.body.inne' + 'rHTML')); ``` -The next problem was MySpace filtering the word “javascript”, so the author used “java<NEWLINE>script" to get around this: +The next problem was MySpace filtering the word "javascript", so the author used "java<NEWLINE>script" to get around this: ```html <div id="mycode" expr="alert('hah!')" style="background:url('java↵
script:eval(document.all.mycode.expr)')"> @@ -837,7 +837,7 @@ It is recommended to _use RedCloth in combination with a whitelist input filter_ ### Ajax Injection -NOTE: _The same security precautions have to be taken for Ajax actions as for “normal” ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ +NOTE: _The same security precautions have to be taken for Ajax actions as for "normal" ones. There is at least one exception, however: The output has to be escaped in the controller already, if the action doesn't render a view._ If you use the [in_place_editor plugin](http://dev.rubyonrails.org/browser/plugins/in_place_editing), or actions that return a string, rather than rendering a view, _you have to escape the return value in the action_. Otherwise, if the return value contains a XSS string, the malicious code will be executed upon return to the browser. Escape any input value using the h() method. @@ -861,7 +861,7 @@ WARNING: _HTTP headers are dynamically generated and under certain circumstances HTTP request headers have a Referer, User-Agent (client software), and Cookie field, among others. Response headers for example have a status code, Cookie and Location (redirection target URL) field. All of them are user-supplied and may be manipulated with more or less effort. _Remember to escape these header fields, too._ For example when you display the user agent in an administration area. -Besides that, it is _important to know what you are doing when building response headers partly based on user input._ For example you want to redirect the user back to a specific page. To do that you introduced a “referer“ field in a form to redirect to the given address: +Besides that, it is _important to know what you are doing when building response headers partly based on user input._ For example you want to redirect the user back to a specific page. To do that you introduced a "referer" field in a form to redirect to the given address: ```ruby redirect_to params[:referer] diff --git a/guides/source/testing.md b/guides/source/testing.md index 62c9835fa4..5b8829b89a 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -66,18 +66,34 @@ Here's a sample YAML fixture file: ```yaml # lo & behold! I am a YAML comment! david: - name: David Heinemeier Hansson - birthday: 1979-10-15 - profession: Systems development + name: David Heinemeier Hansson + birthday: 1979-10-15 + profession: Systems development steve: - name: Steve Ross Kellock - birthday: 1974-09-27 - profession: guy with keyboard + name: Steve Ross Kellock + birthday: 1974-09-27 + profession: guy with keyboard ``` Each fixture is given a name followed by an indented list of colon-separated key/value pairs. Records are typically separated by a blank space. You can place comments in a fixture file by using the # character in the first column. Keys which resemble YAML keywords such as 'yes' and 'no' are quoted so that the YAML Parser correctly interprets them. +If you are working with [associations](/association_basics.html), you can simply +define a reference node between two different fixtures. Here's an example with +a belongs_to/has_many association: + +```yaml +# In fixtures/categories.yml +about: + name: About + +# In fixtures/articles.yml +one: + title: Welcome to Rails! + body: Hello world! + category: about +``` + #### ERB'in It Up ERB allows you to embed Ruby code within templates. The YAML fixture format is pre-processed with ERB when Rails loads fixtures. This allows you to use Ruby to help you generate some sample data. For example, the following code generates a thousand users: @@ -357,28 +373,28 @@ Here's an extract of the assertions you can use with `minitest`, the default tes | Assertion | Purpose | | ---------------------------------------------------------------- | ------- | | `assert( test, [msg] )` | Ensures that `test` is true.| -| `refute( test, [msg] )` | Ensures that `test` is false.| +| `assert_not( test, [msg] )` | Ensures that `test` is false.| | `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.| -| `refute_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| +| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| | `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.| -| `refute_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| +| `assert_not_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| | `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.| -| `refute_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| +| `assert_not_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| | `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.| -| `refute_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| +| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| | `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| -| `refute_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| +| `assert_not_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| | `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| | `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| | `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| | `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| -| `refute_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| +| `assert_not_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| | `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.| -| `refute_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| +| `assert_not_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| | `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.| -| `refute_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| +| `assert_not_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| | `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.| -| `refute_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| +| `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| | `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?| | `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| @@ -741,24 +757,24 @@ class UserFlowsTest < ActionDispatch::IntegrationTest private - module CustomDsl - def browses_site - get "/products/all" - assert_response :success - assert assigns(:products) + module CustomDsl + def browses_site + get "/products/all" + assert_response :success + assert assigns(:products) + end end - end - def login(user) - open_session do |sess| - sess.extend(CustomDsl) - u = users(user) - sess.https! - sess.post "/login", username: u.username, password: u.password - assert_equal '/welcome', path - sess.https!(false) + def login(user) + open_session do |sess| + sess.extend(CustomDsl) + u = users(user) + sess.https! + sess.post "/login", username: u.username, password: u.password + assert_equal '/welcome', path + sess.https!(false) + end end - end end ``` @@ -871,10 +887,9 @@ class PostsControllerTest < ActionController::TestCase private - def initialize_post - @post = posts(:one) - end - + def initialize_post + @post = posts(:one) + end end ``` @@ -896,7 +911,7 @@ Testing mailer classes requires some specific tools to do a thorough job. ### Keeping the Postman in Check -Your mailer classes — like every other part of your Rails application — should be tested to ensure that it is working as expected. +Your mailer classes - like every other part of your Rails application - should be tested to ensure that it is working as expected. The goals of testing your mailer classes are to ensure that: @@ -990,6 +1005,47 @@ class UserControllerTest < ActionController::TestCase end ``` +Testing helpers +--------------- + +In order to test helpers, all you need to do is check that the output of the +helper method matches what you'd expect. Tests related to the helpers are +located under the `test/helpers` directory. Rails provides a generator which +generates both the helper and the test file: + +```bash +$ rails generate helper User + create app/helpers/user_helper.rb + invoke test_unit + create test/helpers/user_helper_test.rb +``` + +The generated test file contains the following code: + +```ruby +require 'test_helper' + +class UserHelperTest < ActionView::TestCase +end +``` + +A helper is just a simple module where you can define methods which are +available into your views. To test the output of the helper's methods, you just +have to use a mixin like this: + +```ruby +class UserHelperTest < ActionView::TestCase + include UserHelper + + test "should return the user name" do + # ... + end +end +``` + +Moreover, since the test class extends from `ActionView::TestCase`, you have +access to Rails' helper methods such as `link_to` or `pluralize`. + Other Testing Approaches ------------------------ @@ -998,6 +1054,7 @@ The built-in `test/unit` based testing is not the only way to test Rails applica * [NullDB](http://avdi.org/projects/nulldb/), a way to speed up testing by avoiding database use. * [Factory Girl](https://github.com/thoughtbot/factory_girl/tree/master), a replacement for fixtures. * [Machinist](https://github.com/notahat/machinist/tree/master), another replacement for fixtures. +* [Fixture Builder](https://github.com/rdy/fixture_builder), a tool that compiles Ruby factories into fixtures before a test run. * [MiniTest::Spec Rails](https://github.com/metaskills/minitest-spec-rails), use the MiniTest::Spec DSL within your rails tests. * [Shoulda](http://www.thoughtbot.com/projects/shoulda), an extension to `test/unit` with additional helpers, macros, and assertions. * [RSpec](http://relishapp.com/rspec), a behavior-driven development framework diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 73c783085e..3daa10ea07 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -170,8 +170,27 @@ this gem such as `whitelist_attributes` or `mass_assignment_sanitizer` options. ``` * Rails 4.0 has deprecated `ActiveRecord::Fixtures` in favor of `ActiveRecord::FixtureSet`. + * Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`. +* Rails 4.0 has deprecated the old-style hash based finder API. This means that + methods which previously accepted "finder options" no longer do. + +* All dynamic methods except for `find_by_...` and `find_by_...!` are deprecated. + Here's how you can handle the changes: + + * `find_all_by_...` becomes `where(...)`. + * `find_last_by_...` becomes `where(...).last`. + * `scoped_by_...` becomes `where(...)`. + * `find_or_initialize_by_...` becomes `find_or_initialize_by(...)`. + * `find_or_create_by_...` becomes `find_or_create_by(...)`. + +* Note that `where(...)` returns a relation, not an array like the old finders. If you require an `Array`, use `where(...).to_a`. + +* These equivalent methods may not execute the same SQL as the previous implementation. + +* To re-enable the old finders, you can use the [activerecord-deprecated_finders gem](https://github.com/rails/activerecord-deprecated_finders). + ### Active Resource Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. @@ -320,7 +339,7 @@ config.assets.js_compressor = :uglifier ### sass-rails -* `asset-url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` +* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index bd0c796673..301e0e7e6c 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -185,7 +185,7 @@ $(document).ready -> ``` Obviously, you'll want to be a bit more sophisticated than that, but it's a -start. +start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax). ### form_tag diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 7a89e5c219..cc9722e59c 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,25 @@ +* rake notes now searches *.less files + + *Josh Crowder* + +* Generate nested route for namespaced controller generated using + `rails g controller`. + Fixes #11532. + + Example: + + rails g controller admin/dashboard index + + # Before: + get "dashboard/index" + + # After: + namespace :admin do + get "dashboard/index" + end + + *Prathamesh Sonpatki* + * Fix the event name of action_dispatch requests. *Rafael Mendonça França* diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 6c9c53fc69..2e83c0fe14 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -3,6 +3,7 @@ require "rails" %w( active_record action_controller + action_view action_mailer rails/test_unit sprockets diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index be8af5c46c..e8adef2fd3 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -102,7 +102,7 @@ module Rails # paths["config"] # => ["config"] # paths["config/initializers"] # => ["config/initializers"] # paths["config/locales"] # => ["config/locales"] - # paths["config/routes"] # => ["config/routes.rb"] + # paths["config/routes.rb"] # => ["config/routes.rb"] # end # # The <tt>Application</tt> class adds a couple more paths to this set. And as in your diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb index 595256f36a..9969a1475d 100644 --- a/railties/lib/rails/engine/railties.rb +++ b/railties/lib/rails/engine/railties.rb @@ -16,8 +16,6 @@ module Rails def -(others) _all - others end - - delegate :engines, to: "self.class" end end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 675ada7ed0..fb495c918c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -37,6 +37,9 @@ module Rails class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' + class_option :skip_action_view, type: :boolean, aliases: '-V', default: false, + desc: 'Skip Action View files' + class_option :skip_sprockets, type: :boolean, aliases: '-S', default: false, desc: 'Skip Sprockets files' @@ -123,7 +126,7 @@ module Rails end def include_all_railties? - !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets] + !options[:skip_active_record] && !options[:skip_action_view] && !options[:skip_test_unit] && !options[:skip_sprockets] end def comment_if(value) diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 7e938fab47..8aec8bc8f9 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -168,15 +168,15 @@ module Rails as_hook = options.delete(:as) || generator_name names.each do |name| - defaults = if options[:type] == :boolean - { } - elsif [true, false].include?(default_value_for_option(name, options)) - { banner: "" } - else - { desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" } - end - unless class_options.key?(name) + defaults = if options[:type] == :boolean + { } + elsif [true, false].include?(default_value_for_option(name, options)) + { banner: "" } + else + { desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" } + end + class_option(name, defaults.merge!(options)) end @@ -255,12 +255,7 @@ module Rails # Split the class from its module nesting nesting = class_name.split('::') last_name = nesting.pop - - # Extract the last Module in the nesting - last = nesting.inject(Object) do |last_module, nest| - break unless last_module.const_defined?(nest, false) - last_module.const_get(nest) - end + last = extract_last_module(nesting) if last && last.const_defined?(last_name.camelize, false) raise Error, "The name '#{class_name}' is either already used in your application " << @@ -270,6 +265,14 @@ module Rails end end + # Takes in an array of nested modules and extracts the last module + def extract_last_module(nesting) + nesting.inject(Object) do |last_module, nest| + break unless last_module.const_defined?(nest, false) + last_module.const_get(nest) + end + end + # Use Rails default banner. def self.banner "rails generate #{namespace.sub(/^rails:/,'')} #{self.arguments.map{ |a| a.usage }.join(' ')} [options]".gsub(/\s+/, ' ') diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index 4a40ba654d..69c10efa47 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -21,13 +21,13 @@ <%%= f.label :password_confirmation %><br> <%%= f.password_field :password_confirmation %> <% else -%> - <% if attribute.reference? -%> + <%- if attribute.reference? -%> <%%= f.label :<%= attribute.column_name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.column_name %> %> - <% else -%> + <%- else -%> <%%= f.label :<%= attribute.name %> %><br> <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> - <% end -%> + <%- end -%> <% end -%> </div> <% end -%> diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 9fb8ea6955..041bfcb733 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -68,7 +68,7 @@ module Rails directory "bin" do |content| "#{shebang}\n" + content end - chmod "bin", 0755, verbose: false + chmod "bin", 0755 & ~File.umask, verbose: false end def config diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 4d474d5267..ac41a0cadb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -8,6 +8,7 @@ require "active_model/railtie" <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" +<%= comment_if :skip_action_view %>require "action_view/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index bae54623c6..ef84447df9 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -10,11 +10,45 @@ module Rails def add_routes actions.reverse.each do |action| - route %{get "#{file_name}/#{action}"} + route generate_routing_code(action) end end hook_for :template_engine, :test_framework, :helper, :assets + + private + + # This method creates nested route entry for namespaced resources. + # For eg. rails g controller foo/bar/baz index + # Will generate - + # namespace :foo do + # namespace :bar do + # get "baz/index" + # end + # end + def generate_routing_code(action) + depth = class_path.length + # Create 'namespace' ladder + # namespace :foo do + # namespace :bar do + namespace_ladder = class_path.each_with_index.map do |ns, i| + indent("namespace :#{ns} do\n", i * 2) + end.join + + # Create route + # get "baz/index" + route = indent(%{get "#{file_name}/#{action}"\n}, depth * 2) + + # Create `end` ladder + # end + # end + end_ladder = (1..depth).reverse_each.map do |i| + indent("end\n", i * 2) + end.join + + # Combine the 3 parts to generate complete route entry + namespace_ladder + route + end_ladder + end end end end diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb index 310c975262..5508829f6b 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/application.rb @@ -7,6 +7,7 @@ require 'rails/all' <%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" +<%= comment_if :skip_action_view %>require "action_view/railtie" <%= comment_if :skip_sprockets %>require "sprockets/railtie" <%= comment_if :skip_test_unit %>require "rails/test_unit/railtie" <% end -%> diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 0cbbf04da2..eb3b2d8ef4 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -80,7 +80,7 @@ module Rails to_prepare_blocks << blk if blk end - def respond_to?(name) + def respond_to?(name, include_private = false) super || @@options.key?(name.to_sym) end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 290634290f..b806b922b7 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -82,7 +82,7 @@ class SourceAnnotationExtractor case item when /\.(builder|rb|coffee|rake)$/ /#\s*(#{tag}):?\s*(.*)$/ - when /\.(css|scss|sass|js)$/ + when /\.(css|scss|sass|less|js)$/ /\/\/\s*(#{tag}):?\s*(.*)$/ when /\.erb$/ /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 45968052a8..56b8736800 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -28,4 +28,6 @@ Gem::Specification.new do |s| s.add_dependency 'rake', '>= 0.8.7' s.add_dependency 'thor', '>= 0.18.1', '< 2.0' + + s.add_development_dependency 'actionview', version end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 491faf4af9..643cc6b0ee 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -8,6 +8,7 @@ require 'fileutils' require 'active_support' require 'action_controller' +require 'action_view' require 'rails/all' module TestApp diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c51488e0e1..03a735b1c1 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -679,5 +679,12 @@ module ApplicationTests end assert_equal Logger::INFO, Rails.logger.level end + + test "respond_to? accepts include_private" do + make_basic_app + + assert_not Rails.configuration.respond_to?(:method_missing) + assert Rails.configuration.respond_to?(:method_missing, true) + end end end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 9a92c5f6ff..01d751e822 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -25,6 +25,7 @@ module ApplicationTests app_file "app/assets/stylesheets/application.css", "// TODO: note in css" app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" + app_file "app/assets/stylesheets/application.css.less", "// TODO: note in less" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" app_file "lib/tasks/task.rake", "# TODO: note in rake" @@ -48,9 +49,10 @@ module ApplicationTests assert_match(/note in css/, output) assert_match(/note in scss/, output) assert_match(/note in sass/, output) + assert_match(/note in less/, output) assert_match(/note in rake/, output) - assert_equal 10, lines.size + assert_equal 11, lines.size lines.each do |line| assert_equal 4, line[0].size diff --git a/railties/test/application/url_generation_test.rb b/railties/test/application/url_generation_test.rb index 2767779719..efbc853d7b 100644 --- a/railties/test/application/url_generation_test.rb +++ b/railties/test/application/url_generation_test.rb @@ -12,6 +12,7 @@ module ApplicationTests boot_rails require "rails" require "action_controller/railtie" + require "action_view/railtie" class MyApp < Rails::Application config.secret_key_base = "3b7cd727ee24e8444053437c36cc66c4" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 42b6275932..2f0dfc7d3e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -222,6 +222,11 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_action_view_is_given + run_generator [destination_root, "--skip-action-view"] + assert_file "config/application.rb", /#\s+require\s+["']action_view\/railtie["']/ + end + def test_generator_if_skip_sprockets_is_given run_generator [destination_root, "--skip-sprockets"] assert_file "config/application.rb" do |content| diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 5205deafd9..9c664a903a 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -82,4 +82,9 @@ class ControllerGeneratorTest < Rails::Generators::TestCase assert_instance_method :bar, controller end end + + def test_namespaced_routes_are_created_in_routes + run_generator ["admin/dashboard", "index"] + assert_file "config/routes.rb", /namespace :admin do\n\s+get "dashboard\/index"\n/ + end end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 7fdd54fc30..32a3d072c9 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -16,6 +16,7 @@ Rails.application.load_generators require 'active_record' require 'action_dispatch' +require 'action_view' module GeneratorsTestHelper def self.included(base) diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index a4d8b3d1b0..e17925ff65 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -44,7 +44,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase end end - def test_helpr_is_also_namespaced + def test_helper_is_also_namespaced run_generator assert_file "app/helpers/test_app/account_helper.rb", /module TestApp/, / module AccountHelper/ assert_file "test/helpers/test_app/account_helper_test.rb", /module TestApp/, / class AccountHelperTest/ diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 4b837da483..524bbde2b7 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -305,8 +305,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/accounts/_form.html.erb" do |content| - assert_match(/<%= f\.text_field :name %>/, content) - assert_match(/<%= f\.text_field :currency_id %>/, content) + assert_match(/^\W{4}<%= f\.text_field :name %>/, content) + assert_match(/^\W{4}<%= f\.text_field :currency_id %>/, content) end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index a3295a6e69..913e2b5e29 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -135,6 +135,7 @@ module TestHelpers def make_basic_app require "rails" require "action_controller/railtie" + require "action_view/railtie" app = Class.new(Rails::Application) app.config.eager_load = false @@ -242,6 +243,12 @@ module TestHelpers end end + def gsub_app_file(path, regexp, *args, &block) + path = "#{app_path}/#{path}" + content = File.read(path).gsub(regexp, *args, &block) + File.open(path, 'wb') { |f| f.write(content) } + end + def remove_file(path) FileUtils.rm_rf "#{app_path}/#{path}" end diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index a9b237d0a5..e45a5228a1 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -1,5 +1,7 @@ require 'abstract_unit' +ActionController::Base.superclass.send(:include, ActionView::Layouts) + module ActionController class Base include ActionController::Testing |