diff options
153 files changed, 2113 insertions, 1259 deletions
@@ -13,8 +13,11 @@ gem "i18n", ">= 0.3.0" # AR gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git" gem "sqlite3-ruby", ">= 1.2.5" -gem "pg", ">= 0.8.0" -gem "mysql", ">= 2.8.1" + +only :test do + gem "pg", ">= 0.8.0" + gem "mysql", ">= 2.8.1" +end # AP gem "rack", "1.1.0", :git => "git://github.com/rack/rack.git" diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 6f4ba96844..785bf98c55 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,13 +1,14 @@ *Rails 3.0 (pending)* -* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three. +* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted -* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc +* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'} -* Every part of a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want. +* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three. -* By default, a field will return the #decoded value when you send it :to_s and any object that is a container (like header, body etc) will return #encoded value when you send it :to_s +* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc +* Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want. * Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type * Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 201b56a739..8adea46d35 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.homepage = "http://www.rubyonrails.org" s.add_dependency('actionpack', '= 3.0.pre') - s.add_dependency('mail', '~> 1.4.3') + s.add_dependency('mail', '~> 1.5.0') s.files = Dir['CHANGELOG', 'README', 'MIT-LICENSE', 'lib/**/*'] s.has_rdoc = true diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index f34a9bae47..0248e29cb7 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -254,6 +254,7 @@ module ActionMailer #:nodoc: include Quoting extend AdvAttrAccessor + include AbstractController::Logger include AbstractController::Rendering include AbstractController::LocalizedCache include AbstractController::Layouts @@ -364,6 +365,7 @@ module ActionMailer #:nodoc: def mailer_name @mailer_name ||= name.underscore end + alias :controller_path :mailer_name def delivery_method=(method_name) @delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name) diff --git a/actionmailer/lib/action_mailer/rails.rb b/actionmailer/lib/action_mailer/railtie.rb index a3573cdea7..5410c7d75f 100644 --- a/actionmailer/lib/action_mailer/rails.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -1,7 +1,8 @@ require "action_mailer" +require "rails" module ActionMailer - class Plugin < Rails::Plugin + class Railtie < Rails::Railtie plugin_name :action_mailer initializer "action_mailer.set_configs" do |app| diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 84f13a6d3c..0877e7b2cb 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -72,12 +72,12 @@ class LayoutMailerTest < Test::Unit::TestCase mail = AutoLayoutMailer.create_multipart(@recipient) # CHANGED: content_type returns an object # assert_equal "multipart/alternative", mail.content_type - assert_equal "multipart/alternative", mail.content_type.string + assert_equal "multipart/alternative", mail.mime_type assert_equal 2, mail.parts.size # CHANGED: content_type returns an object # assert_equal 'text/plain', mail.parts.first.content_type - assert_equal 'text/plain', mail.parts.first.content_type.string + assert_equal 'text/plain', mail.parts.first.mime_type # CHANGED: body returns an object # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body @@ -85,7 +85,7 @@ class LayoutMailerTest < Test::Unit::TestCase # CHANGED: content_type returns an object # assert_equal 'text/html', mail.parts.last.content_type - assert_equal 'text/html', mail.parts.last.content_type.string + assert_equal 'text/html', mail.parts.last.mime_type # CHANGED: body returns an object # assert_equal "Hello from layout text/html multipart", mail.parts.last.body @@ -96,19 +96,19 @@ class LayoutMailerTest < Test::Unit::TestCase mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed") # CHANGED: content_type returns an object # assert_equal "multipart/mixed", mail.content_type - assert_equal "multipart/mixed", mail.content_type.string + assert_equal "multipart/mixed", mail.mime_type assert_equal 2, mail.parts.size # CHANGED: content_type returns an object # assert_equal 'text/plain', mail.parts.first.content_type - assert_equal 'text/plain', mail.parts.first.content_type.string + assert_equal 'text/plain', mail.parts.first.mime_type # CHANGED: body returns an object # assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s # CHANGED: content_type returns an object # assert_equal 'text/html', mail.parts.last.content_type - assert_equal 'text/html', mail.parts.last.content_type.string + assert_equal 'text/html', mail.parts.last.mime_type # CHANGED: body returns an object # assert_equal "Hello from layout text/html multipart", mail.parts.last.body assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s @@ -116,13 +116,13 @@ class LayoutMailerTest < Test::Unit::TestCase def test_should_fix_multipart_layout mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain") - assert_equal "multipart/alternative", mail.content_type.string + assert_equal "multipart/alternative", mail.mime_type assert_equal 2, mail.parts.size - assert_equal 'text/plain', mail.parts.first.content_type.string + assert_equal 'text/plain', mail.parts.first.mime_type assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body.to_s - assert_equal 'text/html', mail.parts.last.content_type.string + assert_equal 'text/html', mail.parts.last.mime_type assert_equal "Hello from layout text/html multipart", mail.parts.last.body.to_s end diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 152900259d..f66b4a174b 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -362,13 +362,13 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal 2, created.parts.size assert_equal 2, created.parts.first.parts.size - assert_equal "multipart/mixed", created.content_type.string - assert_equal "multipart/alternative", created.parts[0].content_type.string + assert_equal "multipart/mixed", created.mime_type + assert_equal "multipart/alternative", created.parts[0].mime_type assert_equal "bar", created.parts[0].header['foo'].to_s assert_nil created.parts[0].charset - assert_equal "text/plain", created.parts[0].parts[0].content_type.string - assert_equal "text/html", created.parts[0].parts[1].content_type.string - assert_equal "application/octet-stream", created.parts[1].content_type.string + assert_equal "text/plain", created.parts[0].parts[0].mime_type + assert_equal "text/html", created.parts[0].parts[1].mime_type + assert_equal "application/octet-stream", created.parts[1].mime_type end @@ -380,11 +380,11 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal 1,created.parts.size assert_equal 2,created.parts.first.parts.size - assert_equal "multipart/mixed", created.content_type.string - assert_equal "multipart/alternative", created.parts.first.content_type.string - assert_equal "text/plain", created.parts.first.parts.first.content_type.string + assert_equal "multipart/mixed", created.mime_type + assert_equal "multipart/alternative", created.parts.first.mime_type + assert_equal "text/plain", created.parts.first.parts.first.mime_type assert_equal "Nothing to see here.", created.parts.first.parts.first.body.to_s - assert_equal "text/html", created.parts.first.parts.second.content_type.string + assert_equal "text/html", created.parts.first.parts.second.mime_type assert_equal "<b>test</b> HTML<br/>", created.parts.first.parts.second.body.to_s end @@ -468,8 +468,8 @@ class ActionMailerTest < Test::Unit::TestCase assert_nothing_raised { created = TestMailer.create_custom_templating_extension(@recipient) } assert_not_nil created assert_equal 2, created.parts.length - assert_equal 'text/plain', created.parts[0].content_type.string - assert_equal 'text/html', created.parts[1].content_type.string + assert_equal 'text/plain', created.parts[0].mime_type + assert_equal 'text/html', created.parts[1].mime_type end def test_cancelled_account @@ -731,8 +731,8 @@ Content-Type: text/plain; charset=iso-8859-1 The body EOF mail = Mail.new(msg) - assert_equal "testing testing \326\244", mail.subject.to_s - assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail.subject.encoded + assert_equal "testing testing \326\244", mail.subject + assert_equal "Subject: =?utf-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded end def test_unquote_7bit_subject @@ -744,8 +744,8 @@ Content-Type: text/plain; charset=iso-8859-1 The body EOF mail = Mail.new(msg) - assert_equal "this == working?", mail.subject.to_s - assert_equal "Subject: this == working?\r\n", mail.subject.encoded + assert_equal "this == working?", mail.subject + assert_equal "Subject: this == working?\r\n", mail[:subject].encoded end def test_unquote_7bit_body @@ -868,7 +868,7 @@ EOF mail = Mail.new(fixture) attachment = mail.attachments.last assert_equal "smime.p7s", attachment.original_filename - assert_equal "application/pkcs7-signature", mail.parts.last.content_type.string + assert_equal "application/pkcs7-signature", mail.parts.last.mime_type end def test_decode_attachment_without_charset @@ -913,7 +913,7 @@ EOF def test_multipart_with_mime_version mail = TestMailer.create_multipart_with_mime_version(@recipient) - assert_equal "1.1", mail.mime_version.version + assert_equal "1.1", mail.mime_version end def test_multipart_with_utf8_subject @@ -929,29 +929,29 @@ EOF def test_explicitly_multipart_messages mail = TestMailer.create_explicitly_multipart_example(@recipient) assert_equal 3, mail.parts.length - assert_equal 'multipart/mixed', mail.content_type.string - assert_equal "text/plain", mail.parts[0].content_type.string + assert_equal 'multipart/mixed', mail.mime_type + assert_equal "text/plain", mail.parts[0].mime_type - assert_equal "text/html", mail.parts[1].content_type.string + assert_equal "text/html", mail.parts[1].mime_type assert_equal "iso-8859-1", mail.parts[1].charset - assert_equal "image/jpeg", mail.parts[2].content_type.string - assert_equal "attachment", mail.parts[2].content_disposition.disposition_type - assert_equal "foo.jpg", mail.parts[2].content_disposition.filename - assert_equal "foo.jpg", mail.parts[2].content_type.filename + assert_equal "image/jpeg", mail.parts[2].mime_type + assert_equal "attachment", mail.parts[2][:content_disposition].disposition_type + assert_equal "foo.jpg", mail.parts[2][:content_disposition].filename + assert_equal "foo.jpg", mail.parts[2][:content_type].filename assert_nil mail.parts[2].charset end def test_explicitly_multipart_with_content_type mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative") assert_equal 3, mail.parts.length - assert_equal "multipart/alternative", mail.content_type.string + assert_equal "multipart/alternative", mail.mime_type end def test_explicitly_multipart_with_invalid_content_type mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml") assert_equal 3, mail.parts.length - assert_equal 'multipart/mixed', mail.content_type.string + assert_equal 'multipart/mixed', mail.mime_type end def test_implicitly_multipart_messages @@ -960,12 +960,12 @@ EOF mail = TestMailer.create_implicitly_multipart_example(@recipient) assert_equal 3, mail.parts.length assert_equal "1.0", mail.mime_version.to_s - assert_equal "multipart/alternative", mail.content_type.string - assert_equal "text/plain", mail.parts[0].content_type.string + assert_equal "multipart/alternative", mail.mime_type + assert_equal "text/plain", mail.parts[0].mime_type assert_equal "utf-8", mail.parts[0].charset - assert_equal "text/html", mail.parts[1].content_type.string + assert_equal "text/html", mail.parts[1].mime_type assert_equal "utf-8", mail.parts[1].charset - assert_equal "application/x-yaml", mail.parts[2].content_type.string + assert_equal "application/x-yaml", mail.parts[2].mime_type assert_equal "utf-8", mail.parts[2].charset end @@ -974,9 +974,9 @@ EOF mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["application/x-yaml", "text/plain"]) assert_equal 3, mail.parts.length - assert_equal "application/x-yaml", mail.parts[0].content_type.string - assert_equal "text/plain", mail.parts[1].content_type.string - assert_equal "text/html", mail.parts[2].content_type.string + assert_equal "application/x-yaml", mail.parts[0].mime_type + assert_equal "text/plain", mail.parts[1].mime_type + assert_equal "text/html", mail.parts[2].mime_type end def test_implicitly_multipart_messages_with_charset @@ -984,14 +984,14 @@ EOF assert_equal "multipart/alternative", mail.header['content-type'].content_type - assert_equal 'iso-8859-1', mail.parts[0].content_type.parameters[:charset] - assert_equal 'iso-8859-1', mail.parts[1].content_type.parameters[:charset] - assert_equal 'iso-8859-1', mail.parts[2].content_type.parameters[:charset] + assert_equal 'iso-8859-1', mail.parts[0].content_type_parameters[:charset] + assert_equal 'iso-8859-1', mail.parts[1].content_type_parameters[:charset] + assert_equal 'iso-8859-1', mail.parts[2].content_type_parameters[:charset] end def test_html_mail mail = TestMailer.create_html_mail(@recipient) - assert_equal "text/html", mail.content_type.string + assert_equal "text/html", mail.mime_type end def test_html_mail_with_underscores @@ -1043,7 +1043,7 @@ EOF assert_equal(4, mail.parts.first.parts.length) assert_equal("This is the first part.", mail.parts.first.parts.first.body.to_s) assert_equal("test.rb", mail.parts.first.parts.second.filename) - assert_equal("flowed", mail.parts.first.parts.fourth.content_type.parameters[:format]) + assert_equal("flowed", mail.parts.first.parts.fourth.content_type_parameters[:format]) assert_equal('smime.p7s', mail.parts.second.filename) end @@ -1081,9 +1081,9 @@ EOF assert !mail.from_addrs.empty? assert !mail.cc_addrs.empty? assert !mail.bcc_addrs.empty? - assert_match(/:/, mail.from_addrs.to_s) - assert_match(/:/, mail.cc_addrs.to_s) - assert_match(/:/, mail.bcc_addrs.to_s) + assert_match(/:/, mail[:from].decoded) + assert_match(/:/, mail[:cc].decoded) + assert_match(/:/, mail[:bcc].decoded) end def test_deliver_with_mail_object @@ -1095,14 +1095,14 @@ EOF def test_multipart_with_template_path_with_dots mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient) assert_equal 2, mail.parts.length - assert "text/plain", mail.parts[1].content_type.string + assert "text/plain", mail.parts[1].mime_type assert "utf-8", mail.parts[1].charset end def test_custom_content_type_attributes mail = TestMailer.create_custom_content_type_attributes - assert_match %r{format="flowed"}, mail.content_type.encoded - assert_match %r{charset="utf-8"}, mail.content_type.encoded + assert_match %r{format=flowed}, mail.content_type + assert_match %r{charset=utf-8}, mail.content_type end def test_return_path_with_create diff --git a/actionmailer/test/quoting_test.rb b/actionmailer/test/quoting_test.rb index b16d160805..7640f4b086 100644 --- a/actionmailer/test/quoting_test.rb +++ b/actionmailer/test/quoting_test.rb @@ -78,7 +78,7 @@ class QuotingTest < Test::Unit::TestCase mail = Mail.new(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject")) # CHANGED: subject returns an object now # assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject - assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject.decoded + assert_equal "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail", mail.subject end private diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 86d22da9bf..1fed26f78f 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -19,8 +19,8 @@ class TestHelperMailerTest < ActionMailer::TestCase def test_setup_creates_the_expected_mailer assert @expected.is_a?(Mail::Message) - assert_equal "1.0", @expected.mime_version.version - assert_equal "text/plain", @expected.content_type.string + assert_equal "1.0", @expected.mime_version + assert_equal "text/plain", @expected.mime_type end def test_mailer_class_is_correctly_inferred diff --git a/actionmailer/test/tmail_compat_test.rb b/actionmailer/test/tmail_compat_test.rb index faa267e3bf..a1ca6a7243 100644 --- a/actionmailer/test/tmail_compat_test.rb +++ b/actionmailer/test/tmail_compat_test.rb @@ -8,7 +8,7 @@ class TmailCompatTest < Test::Unit::TestCase assert_nothing_raised do mail.set_content_type "text/plain" end - assert_equal mail.content_type.string, "text/plain" + assert_equal mail.mime_type, "text/plain" end def test_transfer_encoding_raises_deprecation_warning @@ -17,7 +17,7 @@ class TmailCompatTest < Test::Unit::TestCase assert_nothing_raised do mail.transfer_encoding "base64" end - assert_equal mail.content_transfer_encoding.value, "base64" + assert_equal mail.content_transfer_encoding, "base64" end end diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index efea81aa71..a6889d5d01 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -72,6 +72,16 @@ module AbstractController # And always exclude explicitly hidden actions hidden_actions end + + # Returns the full controller name, underscored, without the ending Controller. + # For instance, MyApp::MyPostsController would return "my_app/my_posts" for + # controller_name. + # + # ==== Returns + # String + def controller_path + @controller_path ||= name && name.sub(/Controller$/, '').underscore + end end abstract! @@ -96,6 +106,11 @@ module AbstractController process_action(action_name, *args) end + # Delegates to the class' #controller_path + def controller_path + self.class.controller_path + end + private # Returns true if the name can be considered an action. This can # be overridden in subclasses to modify the semantics of what diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 4073e9b386..6fbf6bc392 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -1,4 +1,160 @@ module AbstractController + # 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: + # + # <%= render "shared/header" %> + # Hello World + # <%= render "shared/footer" %> + # + # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose + # and if you ever want to change the structure of these two includes, you'll have to change all the templates. + # + # With layouts, you can flip it around and have the common structure know where to insert changing content. This means + # that the header and footer are only mentioned in one place, like this: + # + # // The header part of this layout + # <%= yield %> + # // The footer part of this layout + # + # And then you have content pages that look like this: + # + # hello world + # + # At rendering time, the content page is computed and then inserted in the layout, like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + # <h1><%= @page_title %></h1> + # <%= yield %> + # + # ...and content pages that fulfill these references _at_ rendering time: + # + # <% @page_title = "Welcome" %> + # Off-world colonies offers you a chance to start a new life + # + # The result after rendering is: + # + # <h1>Welcome</h1> + # Off-world colonies offers you a chance to start a new life + # + # == Layout assignment + # + # You can either specify a layout declaratively (using the #layout class method) or give + # it the same name as your controller, and place it in <tt>app/views/layouts</tt>. + # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. + # + # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>, + # that template will be used for all actions in PostsController and controllers inheriting + # from PostsController. + # + # If you use a module, for instance Weblog::PostsController, you will need a template named + # <tt>app/views/layouts/weblog/posts.html.erb</tt>. + # + # Since all your controllers inherit from ApplicationController, they will use + # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified + # or provided. + # + # == Inheritance Examples + # + # class BankController < ActionController::Base + # layout "bank_standard" + # + # class InformationController < BankController + # + # class TellerController < BankController + # # teller.html.erb exists + # + # class TillController < TellerController + # + # class VaultController < BankController + # layout :access_level_layout + # + # class EmployeeController < BankController + # layout nil + # + # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites + # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. + # + # The TellerController uses +teller.html.erb+, and TillController inherits that layout and + # uses it as well. + # + # == Types of layouts + # + # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes + # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can + # be done either by specifying a method reference as a symbol or using an inline method (as a proc). + # + # The method reference is the preferred approach to variable layouts and is used like this: + # + # class WeblogController < ActionController::Base + # layout :writers_and_readers + # + # def index + # # fetching posts + # end + # + # private + # def writers_and_readers + # logged_in? ? "writer_layout" : "reader_layout" + # end + # + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # is logged in or not. + # + # If you want to use an inline method, such as a proc, do something like this: + # + # class WeblogController < ActionController::Base + # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # + # Of course, the most common way of specifying a layout is still just as a plain template name: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>. + # Otherwise, it will be looked up relative to the template root. + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard", :except => :rss + # + # # ... + # + # end + # + # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout + # around the rendered view. + # + # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so + # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. + # + # == Using a different layout in the action render call + # + # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # def help + # render :action => "help", :layout => "help" + # end + # end + # + # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. module Layouts extend ActiveSupport::Concern @@ -89,7 +245,7 @@ module AbstractController # ==== Returns # String:: A template name def _implied_layout_name - name && name.underscore + controller_path end # Takes the specified layout and creates a _layout method to be called diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 64a8a5f241..332d86b089 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,5 +1,4 @@ require "abstract_controller/base" -require "abstract_controller/logger" module AbstractController class DoubleRenderError < Error @@ -13,8 +12,6 @@ module AbstractController module Rendering extend ActiveSupport::Concern - include AbstractController::Logger - included do extlib_inheritable_accessor :_view_paths self._view_paths ||= ActionView::PathSet.new @@ -67,7 +64,7 @@ module AbstractController def render_to_body(options = {}) # TODO: Refactor so we can just use the normal template logic for this if options.key?(:partial) - view_context.render_partial(options) + _render_partial(options) else _determine_template(options) _render_template(options) @@ -87,11 +84,18 @@ module AbstractController # ==== Options # _template<ActionView::Template>:: The template to render # _layout<ActionView::Template>:: The layout to wrap the template in (optional) - # _partial<TrueClass, FalseClass>:: Whether or not the template to be rendered is a partial def _render_template(options) view_context.render_template(options) end + # Renders the given partial. + # + # ==== Options + # partial<String|Object>:: The partial name or the object to be rendered + def _render_partial(options) + view_context.render_partial(options) + end + # The list of view paths for this controller. See ActionView::ViewPathSet for # more details about writing custom view paths. def view_paths diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 26a85d4de8..d66fc3fcc9 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,31 +13,29 @@ module ActionController autoload :Middleware autoload_under "metal" do - autoload :Benchmarking + autoload :Compatibility autoload :ConditionalGet autoload :Configuration + autoload :Cookies + autoload :FilterParameterLogging + autoload :Flash autoload :Head autoload :Helpers autoload :HideActions - autoload :Layouts + autoload :HttpAuthentication autoload :Logger autoload :MimeResponds autoload :RackDelegation - autoload :Compatibility autoload :Redirecting autoload :Rendering autoload :Renderers + autoload :RequestForgeryProtection autoload :Rescue autoload :Responder autoload :SessionManagement + autoload :Streaming autoload :UrlFor autoload :Verification - autoload :Flash - autoload :RequestForgeryProtection - autoload :Streaming - autoload :HttpAuthentication - autoload :FilterParameterLogging - autoload :Cookies end autoload :Dispatcher, 'action_controller/dispatch/dispatcher' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 67656110c4..b23be66910 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -3,7 +3,7 @@ module ActionController abstract! include AbstractController::Callbacks - include AbstractController::Logger + include AbstractController::Layouts include ActionController::Helpers include ActionController::HideActions @@ -11,7 +11,6 @@ module ActionController include ActionController::Redirecting include ActionController::Rendering include ActionController::Renderers::All - include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackDelegation include ActionController::Logger diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index f445ca70ee..1819c0f886 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -28,21 +28,6 @@ module ActionController self.class.controller_name end - # Returns the full controller name, underscored, without the ending Controller. - # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_name. - # - # ==== Returns - # String - def self.controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore - end - - # Delegates to the class' #controller_path - def controller_path - self.class.controller_path - end - # The details below can be overridden to support a specific # Request and Response object. The default ActionController::Base # implementation includes RackDelegation, which makes a request diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb deleted file mode 100644 index f44498a884..0000000000 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ /dev/null @@ -1,171 +0,0 @@ -module ActionController - # 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: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # // The header part of this layout - # <%= yield %> - # // The footer part of this layout - # - # And then you have content pages that look like this: - # - # hello world - # - # At rendering time, the content page is computed and then inserted in the layout, like this: - # - # // The header part of this layout - # hello world - # // The footer part of this layout - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - # <h1><%= @page_title %></h1> - # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - # <h1>Welcome</h1> - # Off-world colonies offers you a chance to start a new life - # - # == Layout assignment - # - # You can either specify a layout declaratively (using the #layout class method) or give - # it the same name as your controller, and place it in <tt>app/views/layouts</tt>. - # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. - # - # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>, - # that template will be used for all actions in PostsController and controllers inheriting - # from PostsController. - # - # If you use a module, for instance Weblog::PostsController, you will need a template named - # <tt>app/views/layouts/weblog/posts.html.erb</tt>. - # - # Since all your controllers inherit from ApplicationController, they will use - # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified - # or provided. - # - # == Inheritance Examples - # - # class BankController < ActionController::Base - # layout "bank_standard" - # - # class InformationController < BankController - # - # class TellerController < BankController - # # teller.html.erb exists - # - # class TillController < TellerController - # - # class VaultController < BankController - # layout :access_level_layout - # - # class EmployeeController < BankController - # layout nil - # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # The TellerController uses +teller.html.erb+, and TillController inherits that layout and - # uses it as well. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>. - # Otherwise, it will be looked up relative to the template root. - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. - # - # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so - # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. - # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # def help - # render :action => "help", :layout => "help" - # end - # end - # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. - module Layouts - extend ActiveSupport::Concern - - include ActionController::Rendering - include AbstractController::Layouts - - module ClassMethods - # If no layout is provided, look for a layout with this name. - def _implied_layout_name - controller_path - end - end - end -end diff --git a/actionpack/lib/action_controller/metal/logger.rb b/actionpack/lib/action_controller/metal/logger.rb index e71f77fbb2..4f4370e5f0 100644 --- a/actionpack/lib/action_controller/metal/logger.rb +++ b/actionpack/lib/action_controller/metal/logger.rb @@ -8,6 +8,10 @@ module ActionController module Logger extend ActiveSupport::Concern + included do + include AbstractController::Logger + end + attr_internal :view_runtime def process_action(action) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 20eb524e50..74e50bb032 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -20,12 +20,6 @@ module ActionController def render_to_body(options) _process_options(options) - - if options.key?(:partial) - options[:partial] = action_name if options[:partial] == true - options[:_details] = {:formats => formats} - end - super end @@ -43,6 +37,12 @@ module ActionController super end + def _render_partial(options) + options[:partial] = action_name if options[:partial] == true + options[:_details] = {:formats => formats} + super + end + def format_for_text formats.first end diff --git a/actionpack/lib/action_controller/rails.rb b/actionpack/lib/action_controller/railtie.rb index df708c315b..f861d12905 100644 --- a/actionpack/lib/action_controller/rails.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -1,5 +1,8 @@ +require "action_controller" +require "rails" + module ActionController - class Plugin < Rails::Plugin + class Railtie < Rails::Railtie plugin_name :action_controller initializer "action_controller.set_configs" do |app| diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index d0c66eda60..81c9c88820 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -504,8 +504,9 @@ module ActionView end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object - # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify - # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged + # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation + # is found in the current I18n locale (through views.labels.<modelname>.<attribute>) or you specify it explicitly. + # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to # target labels for radio_button tags (where the value is used in the ID of the input tag). # @@ -513,6 +514,29 @@ module ActionView # label(:post, :title) # # => <label for="post_title">Title</label> # + # You can localize your labels based on model and attribute names. + # For example you can define the following in your locale (e.g. en.yml) + # + # views: + # labels: + # post: + # body: "Write your entire text here" + # + # Which then will result in + # + # label(:post, :body) + # # => <label for="post_body">Write your entire text here</label> + # + # Localization can also be based purely on the translation of the attribute-name like this: + # + # activemodel: + # attribute: + # post: + # cost: "Total cost" + # + # label(:post, :cost) + # # => <label for="post_cost">Total cost</label> + # # label(:post, :title, "A short title") # # => <label for="post_title">A short title</label> # @@ -751,7 +775,19 @@ module ActionView add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options["for"] ||= name_and_id["id"] - content = (text.blank? ? nil : text.to_s) || method_name.humanize + + content = if text.blank? + I18n.t("views.labels.#{object_name}.#{method_name}", :default => "").presence + else + text.to_s + end + + content ||= if object && object.class.respond_to?(:human_attribute_name) + object.class.human_attribute_name(method_name) + end + + content ||= method_name.humanize + label_tag(name_and_id["id"], content, options) end diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb new file mode 100644 index 0000000000..a90e0636b9 --- /dev/null +++ b/actionpack/lib/action_view/railtie.rb @@ -0,0 +1,2 @@ +require "action_view" +require "rails"
\ No newline at end of file diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 0302e44b4e..48316cac53 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -22,15 +22,18 @@ module ActionView return _render_partial(options) end - layout = find(layout, {:formats => formats}) if layout + template = if options[:file] + find(options[:file], {:formats => formats}) + elsif options[:inline] + handler = Template.handler_class_for_extension(options[:type] || "erb") + Template.new(options[:inline], "inline template", handler, {}) + elsif options[:text] + Template::Text.new(options[:text]) + end - if file = options[:file] - template = find(file, {:formats => formats}) + if template + layout = find(layout, {:formats => formats}) if layout _render_template(template, layout, :locals => options[:locals]) - elsif inline = options[:inline] - _render_inline(inline, layout, options) - elsif text = options[:text] - _render_text(text, layout, options[:locals]) end when :update update_page(&block) @@ -76,42 +79,23 @@ module ActionView capture(&block) end - def _render_inline(inline, layout, options) - locals = options[:locals] - - content = ActiveSupport::Notifications.instrument(:render_inline) do - handler = Template.handler_class_for_extension(options[:type] || "erb") - template = Template.new(options[:inline], "inline template", handler, {}) - template.render(self, locals) - end - - _render_text(content, layout, locals) - end - - def _render_text(content, layout, locals) - ActiveSupport::Notifications.instrument(:render_text) - content = _render_layout(layout, locals){ content } if layout - content - end - # This is the API to render a ViewContext's template from a controller. # # Internal Options: # _template:: The Template object to render # _layout:: The layout, if any, to wrap the Template in - # _partial:: true if the template is a partial def render_template(options) _evaluate_assigns_and_ivars - template, layout, partial = options.values_at(:_template, :_layout, :_partial) - _render_template(template, layout, options, partial) + template, layout = options.values_at(:_template, :_layout) + _render_template(template, layout, options) end - def _render_template(template, layout = nil, options = {}, partial = nil) + def _render_template(template, layout = nil, options = {}) locals = options[:locals] || {} content = ActiveSupport::Notifications.instrument(:render_template, :identifier => template.identifier, :layout => (layout ? layout.identifier : nil)) do - partial ? _render_partial_object(template, options) : template.render(self, locals) + template.render(self, locals) end @_content_for[:layout] = content diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 84d5ce6ad4..c8e8b3857e 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -155,7 +155,7 @@ class CookieTest < ActionController::TestCase def test_permanent_cookie get :set_permanent_cookie assert_match /Jamie/, @response.headers["Set-Cookie"] - assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"] + assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"] end def test_signed_cookie @@ -165,7 +165,7 @@ class CookieTest < ActionController::TestCase def test_permanent_signed_cookie get :set_permanent_signed_cookie - assert_match %r(#{20.years.from_now.year}), @response.headers["Set-Cookie"] + assert_match %r(#{20.years.from_now.utc.year}), @response.headers["Set-Cookie"] assert_equal 100, @controller.send(:cookies).signed[:remember_me] end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 823de8bdc7..b0e5d7a94c 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -54,6 +54,7 @@ end class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost) extend ActiveModel::Naming include ActiveModel::Conversion + extend ActiveModel::Translation alias_method :secret?, :secret diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 44734abb18..b1e9fe99a2 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -6,6 +6,25 @@ class FormHelperTest < ActionView::TestCase def setup super + + # Create "label" locale for testing I18n label helpers + I18n.backend.store_translations 'label', { + :activemodel => { + :attributes => { + :post => { + :cost => "Total cost" + } + } + }, + :views => { + :labels => { + :post => { + :body => "Write entire text here" + } + } + } + } + @post = Post.new @comment = Comment.new def @post.errors() @@ -51,6 +70,27 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('<label for="post_secret">Secret?</label>', label(:post, :secret?)) end + def test_label_with_locales_strings + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('<label for="post_body">Write entire text here</label>', label("post", "body")) + ensure + I18n.locale = old_locale + end + + def test_label_with_human_attribute_name + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('<label for="post_cost">Total cost</label>', label(:post, :cost)) + ensure + I18n.locale = old_locale + end + + def test_label_with_locales_symbols + old_locale, I18n.locale = I18n.locale, :label + assert_dom_equal('<label for="post_body">Write entire text here</label>', label(:post, :body)) + ensure + I18n.locale = old_locale + end + def test_label_with_for_attribute_as_symbol assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for")) end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 8f855958c6..977a101277 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -235,10 +235,6 @@ module ActiveModel # It's also possible to instantiate related objects, so a Client class belonging to the clients # table with a +master_id+ foreign key can instantiate master through Client#master. def method_missing(method_id, *args, &block) - if method_id == :to_ary || method_id == :to_str - raise NoMethodError, "undefined method `#{method_id}' for #{inspect}:#{self.class}" - end - method_name = method_id.to_s if match = match_attribute_method?(method_name) guard_private_attribute_method!(method_name, args) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index e8bb62953d..abc084a74b 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -97,18 +97,19 @@ module ActiveModel full_messages = [] each do |attribute, messages| - messages = Array.wrap(messages) + messages = Array(messages) next if messages.empty? if attribute == :base messages.each {|m| full_messages << m } else - attr_name = @base.class.human_attribute_name(attribute) - options = { :default => ' ', :scope => @base.class.i18n_scope } - prefix = attr_name + I18n.t(:"errors.format.separator", options) + attr_name = attribute.to_s.gsub('.', '_').humanize + attr_name = @base.class.human_attribute_name(attribute, :default => attr_name) + options = { :default => "{{attribute}} {{message}}", :attribute => attr_name, + :scope => @base.class.i18n_scope } messages.each do |m| - full_messages << "#{prefix}#{m}" + full_messages << I18n.t(:"errors.format", options.merge(:message => m)) end end end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1c2347adbf..0be82aa180 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -41,6 +41,20 @@ module ActiveModel assert_boolean model.destroyed?, "destroyed?" end + # naming + # ------ + # + # Model.model_name must returns a string with some convenience methods as + # :human and :partial_path. Check ActiveModel::Naming for more information. + # + def test_model_naming + assert model.class.respond_to?(:model_name), "The model should respond to model_name" + model_name = model.class.model_name + assert_kind_of String, model_name + assert_kind_of String, model_name.human + assert_kind_of String, model_name.partial_path + end + # errors # ------ # diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 0c2cf9ea33..1cdb897f13 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -1,6 +1,9 @@ en: activemodel: errors: + # model.errors.full_messages format. + format: "{{attribute}} {{message}}" + # The values :model, :attribute and :value are always available for interpolation # The value :count is available when applicable. Can be used for pluralization. messages: diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb new file mode 100644 index 0000000000..63ffe5db63 --- /dev/null +++ b/activemodel/lib/active_model/railtie.rb @@ -0,0 +1,2 @@ +require "active_model" +require "rails"
\ No newline at end of file diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index da7d2112dc..63804004ee 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -4,6 +4,8 @@ class LintTest < ActiveModel::TestCase include ActiveModel::Lint::Tests class CompliantModel + extend ActiveModel::Naming + def to_model self end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 2717a09331..a7656fe219 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -21,20 +21,6 @@ class I18nValidationTest < ActiveModel::TestCase I18n.backend = @old_backend end - def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%s interpolation syntax was deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this') - end - end - - def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%d interpolation syntaxes are deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2) - end - end - def test_errors_add_on_empty_generates_message @person.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) @person.errors.add_on_empty :title @@ -57,10 +43,16 @@ class I18nValidationTest < ActiveModel::TestCase def test_errors_full_messages_translates_human_attribute_name_for_model_attributes @person.errors.add('name', 'empty') - I18n.expects(:translate).with(:"person.name", :default => ['Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name') + I18n.expects(:translate).with(:"person.name", :default => ['Name', 'Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name') @person.errors.full_messages end + def test_errors_full_messages_uses_format + I18n.backend.store_translations('en', :activemodel => {:errors => {:format => "Field {{attribute}} {{message}}"}}) + @person.errors.add('name', 'empty') + assert_equal ["Field Name empty"], @person.errors.full_messages + end + # ActiveRecord::Validations # validates_confirmation_of w/ mocha def test_validates_confirmation_of_generates_message diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 61910395b5..38a2a716a7 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -71,6 +71,12 @@ class ValidationsTest < ActiveModel::TestCase assert_equal 2, r.errors.count end + def test_errors_on_nested_attributes_expands_name + t = Topic.new + t.errors["replies.name"] << "can't be blank" + assert_equal ["Replies name can't be blank"], t.errors.full_messages + end + def test_errors_on_base r = Reply.new r.content = "Mismatch" diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index a524dc50a1..cf439b0dc0 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -54,6 +54,7 @@ module ActiveRecord autoload :QueryMethods autoload :FinderMethods autoload :CalculationMethods + autoload :PredicateBuilder end autoload :Base diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f0bad6c3ba..d74b21b690 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1465,8 +1465,7 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}, join_dependency = nil) - join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + def find_with_associations(options = {}, join_dependency) rows = select_all_rows(options, join_dependency) join_dependency.instantiate(rows) rescue ThrowResult @@ -1488,7 +1487,7 @@ module ActiveRecord dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] - dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions] + dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.table_name) if reflection.options[:conditions] dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") dependent_conditions = dependent_conditions.gsub('@', '\@') @@ -1706,7 +1705,7 @@ module ActiveRecord def construct_finder_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1751,7 +1750,7 @@ module ActiveRecord def construct_finder_sql_for_association_limiting(options, join_dependency) scope = scope(:find) - relation = arel_table(options[:from]) + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) @@ -1764,89 +1763,12 @@ module ActiveRecord order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_limit(options[:offset], scope)). + from(options[:from]). select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))) relation.to_sql end - def tables_in_string(string) - return [] if string.blank? - string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten - end - - def tables_in_hash(hash) - return [] if hash.blank? - tables = hash.map do |key, value| - if value.is_a?(Hash) - key.to_s - else - tables_in_string(key) if key.is_a?(String) - end - end - tables.flatten.compact - end - - def conditions_tables(options) - # look in both sets of conditions - conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond| - case cond - when nil then all - when Array then all << tables_in_string(cond.first) - when Hash then all << tables_in_hash(cond) - else all << tables_in_string(cond) - end - end - conditions.flatten - end - - def order_tables(options) - order = [options[:order], scope(:find, :order) ].join(", ") - return [] unless order && order.is_a?(String) - tables_in_string(order) - end - - def selects_tables(options) - select = options[:select] - return [] unless select && select.is_a?(String) - tables_in_string(select) - end - - def joined_tables(options) - scope = scope(:find) - joins = options[:joins] - merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) - [table_name] + case merged_joins - when Symbol, Hash, Array - if array_of_strings?(merged_joins) - tables_in_string(merged_joins.join(' ')) - else - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil) - join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact - end - else - tables_in_string(merged_joins) - end - end - - # Checks if the conditions reference a table other than the current model table - def include_eager_conditions?(options, tables = nil, joined_tables = nil) - ((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any? - end - - # Checks if the query order references a table other than the current model's table. - def include_eager_order?(options, tables = nil, joined_tables = nil) - ((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any? - end - - def include_eager_select?(options, joined_tables = nil) - (selects_tables(options) - (joined_tables || joined_tables(options))).any? - end - - def references_eager_loaded_tables?(options) - joined_tables = joined_tables(options) - include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables) - end - def using_limitable_reflections?(reflections) reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero? end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1ceb0dbf96..358db6df1d 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -21,7 +21,7 @@ module ActiveRecord construct_sql end - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped def select(select = nil, &block) if block_given? @@ -58,7 +58,7 @@ module ActiveRecord find_scope = construct_scope[:find].slice(:conditions, :order) with_scope(:find => find_scope) do - relation = @reflection.klass.send(:construct_finder_arel_with_includes, options) + relation = @reflection.klass.send(:construct_finder_arel, options) case args.first when :first, :last, :all diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 7d8f4670fa..022dd2ae9b 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -161,7 +161,7 @@ module ActiveRecord end # Forwards the call to the reflection class. - def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name) + def sanitize_sql(sql, table_name = @reflection.klass.table_name) @reflection.klass.send(:sanitize_sql, sql, table_name) end diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 9569b0c6f9..bd05d1014c 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -44,7 +44,7 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else - relation = arel_table(@reflection.options[:join_table]) + relation = Arel::Table.new(@reflection.options[:join_table]) attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s @@ -70,7 +70,7 @@ module ActiveRecord if sql = @reflection.options[:delete_sql] records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) } else - relation = arel_table(@reflection.options[:join_table]) + relation = Arel::Table.new(@reflection.options[:join_table]) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) ).delete diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index be74ddfcf0..d3336cf2d2 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -69,7 +69,7 @@ module ActiveRecord when :delete_all @reflection.klass.delete(records.map { |record| record.id }) else - relation = arel_table(@reflection.table_name) + relation = Arel::Table.new(@reflection.table_name) relation.where(relation[@reflection.primary_key_name].eq(@owner.id). and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id))) ).update(relation[@reflection.primary_key_name] => nil) diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 44c668b619..98ab64537e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -267,7 +267,7 @@ module ActiveRecord unless valid = association.valid? if reflection.options[:autosave] association.errors.each do |attribute, message| - attribute = "#{reflection.name}_#{attribute}" + attribute = "#{reflection.name}.#{attribute}" errors[attribute] << message if errors[attribute].empty? end else diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c4bdbdad08..70776c7aa2 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -645,7 +645,7 @@ module ActiveRecord #:nodoc: options = args.extract_options! set_readonly_option!(options) - relation = construct_finder_arel_with_includes(options) + relation = construct_finder_arel(options) case args.first when :first, :last, :all @@ -655,7 +655,7 @@ module ActiveRecord #:nodoc: end end - delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :from, :lock, :readonly, :having, :to => :scoped + delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the # same arguments to this method as you can to <tt>find(:first)</tt>. @@ -815,8 +815,8 @@ module ActiveRecord #:nodoc: # # # Delete multiple rows # Todo.delete([2,3,4]) - def delete(id) - delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ]) + def delete(id_or_array) + active_relation.where(construct_conditions(nil, scope(:find))).delete(id_or_array) end # Destroy an object (or multiple objects) that has the given id, the object is instantiated first, @@ -873,7 +873,7 @@ module ActiveRecord #:nodoc: def update_all(updates, conditions = nil, options = {}) scope = scope(:find) - relation = arel_table + relation = active_relation if conditions = construct_conditions(conditions, scope) relation = relation.where(Arel::SqlLiteral.new(conditions)) @@ -938,7 +938,7 @@ module ActiveRecord #:nodoc: # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) - arel_table.where(construct_conditions(conditions, scope(:find))).delete_all + active_relation.where(construct_conditions(conditions, scope(:find))).delete_all end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -1391,7 +1391,8 @@ module ActiveRecord #:nodoc: # end def reset_column_information undefine_attribute_methods - @arel_table = @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil + @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil + @active_relation = @active_relation_engine = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: @@ -1504,8 +1505,16 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end - def arel_table(table = nil) - Relation.new(self, Arel::Table.new(table || table_name)) + def active_relation + @active_relation ||= Relation.new(self, active_relation_table) + end + + def active_relation_table(table_name_alias = nil) + Arel::Table.new(table_name, :as => table_name_alias) + end + + def active_relation_engine + @active_relation_engine ||= Arel::Sql::Engine.new(self) end private @@ -1561,7 +1570,7 @@ module ActiveRecord #:nodoc: def construct_finder_arel(options = {}, scope = scope(:find)) validate_find_options(options) - relation = arel_table. + relation = active_relation. joins(construct_join(options[:joins], scope)). where(construct_conditions(options[:conditions], scope)). select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). @@ -1570,7 +1579,8 @@ module ActiveRecord #:nodoc: order(construct_order(options[:order], scope)). limit(construct_limit(options[:limit], scope)). offset(construct_offset(options[:offset], scope)). - from(options[:from]) + from(options[:from]). + includes( merge_includes(scope && scope[:include], options[:include])) lock = (scope && scope[:lock]) || options[:lock] relation = relation.lock if lock.present? @@ -1580,21 +1590,6 @@ module ActiveRecord #:nodoc: relation end - def construct_finder_arel_with_includes(options = {}) - relation = construct_finder_arel(options) - include_associations = merge_includes(scope(:find, :include), options[:include]) - - if include_associations.any? - if references_eager_loaded_tables?(options) - relation = relation.eager_load(include_associations) - else - relation = relation.preload(include_associations) - end - end - - relation - end - def construct_join(joins, scope) merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) case merged_joins @@ -1662,7 +1657,7 @@ module ActiveRecord #:nodoc: def build_association_joins(joins) join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil) - relation = arel_table.relation + relation = active_relation.relation join_dependency.join_associations.map { |association| if (association_relation = association.relation).is_a?(Array) [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation), @@ -1677,14 +1672,14 @@ module ActiveRecord #:nodoc: o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end - def type_condition(table_alias=nil) - quoted_table_alias = self.connection.quote_table_name(table_alias || table_name) - quoted_inheritance_column = connection.quote_column_name(inheritance_column) - type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass| - condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' " - end + def type_condition(table_alias = nil) + table = Arel::Table.new(table_name, :engine => active_relation_engine, :as => table_alias) + + sti_column = table[inheritance_column] + condition = sti_column.eq(sti_name) + subclasses.each{|subclass| condition = condition.or(sti_column.eq(subclass.sti_name)) } - " (#{type_condition}) " + condition.to_sql end # Guesses the table name, but does not decorate it with prefix and suffix information. @@ -1713,7 +1708,7 @@ module ActiveRecord #:nodoc: super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel_with_includes(options) : scoped + relation = options.any? ? construct_finder_arel(options) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block @@ -1964,7 +1959,7 @@ module ActiveRecord #:nodoc: # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'" # { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'" # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition, table_name = quoted_table_name) + def sanitize_sql_for_conditions(condition, table_name = self.table_name) return nil if condition.blank? case condition @@ -2035,30 +2030,12 @@ module ActiveRecord #:nodoc: # And for value objects on a composed_of relationship: # { :address => Address.new("123 abc st.", "chicago") } # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name) + def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) attrs = expand_hash_conditions_for_aggregates(attrs) - conditions = attrs.map do |attr, value| - table_name = default_table_name - - unless value.is_a?(Hash) - attr = attr.to_s - - # Extract table name from qualified attribute names. - if attr.include?('.') - attr_table_name, attr = attr.split('.', 2) - attr_table_name = connection.quote_table_name(attr_table_name) - else - attr_table_name = table_name - end - - attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value) - else - sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s)) - end - end.join(' AND ') - - replace_bind_variables(conditions, expand_range_bind_variables(attrs.values)) + table = Arel::Table.new(default_table_name, active_relation_engine) + builder = PredicateBuilder.new(active_relation_engine) + builder.build_from_hash(attrs, table).map(&:to_sql).join(' AND ') end alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions @@ -2323,7 +2300,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).delete + self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).delete_all end @destroyed = true @@ -2610,7 +2587,7 @@ module ActiveRecord #:nodoc: def update(attribute_names = @attributes.keys) attributes_with_values = arel_attributes_values(false, false, attribute_names) return 0 if attributes_with_values.empty? - self.class.arel_table.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) + self.class.active_relation.where(self.class.active_relation[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2623,9 +2600,9 @@ module ActiveRecord #:nodoc: attributes_values = arel_attributes_values new_id = if attributes_values.empty? - self.class.arel_table.insert connection.empty_insert_statement_value + self.class.active_relation.insert connection.empty_insert_statement_value else - self.class.arel_table.insert attributes_values + self.class.active_relation.insert attributes_values end self.id ||= new_id @@ -2720,7 +2697,7 @@ module ActiveRecord #:nodoc: if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array)) value = value.to_yaml end - attrs[self.class.arel_table[name]] = value + attrs[self.class.active_relation[name]] = value end end end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index d51d9f2159..20d287faeb 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -162,7 +162,7 @@ module ActiveRecord join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, includes, construct_join(options[:joins], scope)) construct_calculation_arel_with_included_associations(options, join_dependency) else - arel_table. + active_relation. joins(construct_join(options[:joins], scope)). from((scope && scope[:from]) || options[:from]). where(construct_conditions(options[:conditions], scope)). @@ -178,7 +178,7 @@ module ActiveRecord def construct_calculation_arel_with_included_associations(options, join_dependency) scope = scope(:find) - relation = arel_table + relation = active_relation for association in join_dependency.join_associations relation = association.join_relation(relation) diff --git a/activerecord/lib/active_record/locale/en.yml b/activerecord/lib/active_record/locale/en.yml index 092f5f0023..e33d389f8c 100644 --- a/activerecord/lib/active_record/locale/en.yml +++ b/activerecord/lib/active_record/locale/en.yml @@ -1,6 +1,9 @@ en: activerecord: errors: + # model.errors.full_messages format. + format: "{{attribute}} {{message}}" + # The values :model, :attribute and :value are always available for interpolation # The value :count is available when applicable. Can be used for pluralization. messages: diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 986bc7009b..f9e538c586 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -78,11 +78,11 @@ module ActiveRecord attribute_names.uniq! begin - arel_table = self.class.arel_table(self.class.table_name) + relation = self.class.active_relation - affected_rows = arel_table.where( - arel_table[self.class.primary_key].eq(quoted_id).and( - arel_table[self.class.locking_column].eq(quote_value(previous_value)) + affected_rows = relation.where( + relation[self.class.primary_key].eq(quoted_id).and( + relation[self.class.locking_column].eq(quote_value(previous_value)) ) ).update(arel_attributes_values(false, false, attribute_names)) diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index a6336e762a..f63b249241 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -27,9 +27,9 @@ module ActiveRecord Scope.new(self, options, &block) else unless scoped?(:find) - finder_needs_type_condition? ? arel_table.where(type_condition) : arel_table + finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn else - construct_finder_arel_with_includes + construct_finder_arel end end end diff --git a/activerecord/lib/active_record/rails.rb b/activerecord/lib/active_record/railtie.rb index a13bd2a5da..657ee738c0 100644 --- a/activerecord/lib/active_record/rails.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -2,10 +2,12 @@ # rails, so let's make sure that it gets required before # here. This is needed for correctly setting up the middleware. # In the future, this might become an optional require. -require "action_controller/rails" +require "active_record" +require "action_controller/railtie" +require "rails" module ActiveRecord - class Plugin < Rails::Plugin + class Railtie < Rails::Railtie plugin_name :active_record rake_tasks do diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ae03e1d7e9..114095b7ef 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -2,32 +2,60 @@ module ActiveRecord class Relation include QueryMethods, FinderMethods, CalculationMethods - delegate :to_sql, :to => :relation delegate :length, :collect, :map, :each, :all?, :to => :to_a - attr_reader :relation, :klass, :preload_associations, :eager_load_associations - attr_writer :readonly, :preload_associations, :eager_load_associations + attr_reader :relation, :klass + attr_writer :readonly, :table + attr_accessor :preload_associations, :eager_load_associations, :include_associations def initialize(klass, relation) @klass, @relation = klass, relation @preload_associations = [] @eager_load_associations = [] + @include_associations = [] @loaded, @readonly = false end + def new(*args, &block) + with_create_scope { @klass.new(*args, &block) } + end + + def create(*args, &block) + with_create_scope { @klass.create(*args, &block) } + end + + def create!(*args, &block) + with_create_scope { @klass.create!(*args, &block) } + end + def merge(r) raise ArgumentError, "Cannot merge a #{r.klass.name} relation with #{@klass.name} relation" if r.klass != @klass - joins(r.relation.joins(r.relation)). - group(r.send(:group_clauses).join(', ')). - order(r.send(:order_clauses).join(', ')). - where(r.send(:where_clause)). - limit(r.taken). - offset(r.skipped). - select(r.send(:select_clauses).join(', ')). - eager_load(r.eager_load_associations). - preload(r.preload_associations). - from(r.send(:sources).present? ? r.send(:from_clauses) : nil) + merged_relation = spawn(table).eager_load(r.eager_load_associations).preload(r.preload_associations).includes(r.include_associations) + merged_relation.readonly = r.readonly + + [self.relation, r.relation].each do |arel| + merged_relation = merged_relation. + joins(arel.joins(arel)). + group(arel.groupings). + order(arel.send(:order_clauses).join(', ')). + limit(arel.taken). + offset(arel.skipped). + select(arel.send(:select_clauses)). + from(arel.sources) + end + + merged_wheres = @relation.wheres + + r.wheres.each do |w| + if w.is_a?(Arel::Predicates::Equality) + merged_wheres = merged_wheres.reject {|p| p.is_a?(Arel::Predicates::Equality) && p.operand1.name == w.operand1.name } + end + + merged_wheres << w + end + + merged_relation.where(*merged_wheres) end alias :& :merge @@ -47,7 +75,9 @@ module ActiveRecord def to_a return @records if loaded? - @records = if @eager_load_associations.any? + find_with_associations = @eager_load_associations.any? || references_eager_loaded_tables? + + @records = if find_with_associations begin @klass.send(:find_with_associations, { :select => @relation.send(:select_clauses).join(', '), @@ -59,7 +89,7 @@ module ActiveRecord :offset => @relation.skipped, :from => (@relation.send(:from_clauses) if @relation.send(:sources).present?) }, - ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations + @include_associations, nil)) rescue ThrowResult [] end @@ -67,7 +97,10 @@ module ActiveRecord @klass.find_by_sql(@relation.to_sql) end - @preload_associations.each {|associations| @klass.send(:preload_associations, @records, associations) } + preload = @preload_associations + preload += @include_associations unless find_with_associations + preload.each {|associations| @klass.send(:preload_associations, @records, associations) } + @records.each { |record| record.readonly! } if @readonly @loaded = true @@ -109,6 +142,10 @@ module ActiveRecord @relation.delete.tap { reset } end + def delete(id_or_array) + where(@klass.primary_key => id_or_array).delete_all + end + def loaded? @loaded end @@ -119,19 +156,33 @@ module ActiveRecord end def reset - @first = @last = nil + @first = @last = @create_scope = @to_sql = nil @records = [] self end def spawn(relation = @relation) - relation = self.class.new(@klass, relation) + relation = Relation.new(@klass, relation) relation.readonly = @readonly relation.preload_associations = @preload_associations relation.eager_load_associations = @eager_load_associations + relation.include_associations = @include_associations + relation.table = table relation end + def table + @table ||= Arel::Table.new(@klass.table_name, Arel::Sql::Engine.new(@klass)) + end + + def primary_key + @primary_key ||= table[@klass.primary_key] + end + + def to_sql + @to_sql ||= @relation.to_sql + end + protected def method_missing(method, *args, &block) @@ -153,9 +204,30 @@ module ActiveRecord end end - def where_clause(join_string = "\n\tAND ") + def with_create_scope + @klass.send(:with_scope, :create => create_scope) { yield } + end + + def create_scope + @create_scope ||= wheres.inject({}) do |hash, where| + hash[where.operand1.name] = where.operand2.value if where.is_a?(Arel::Predicates::Equality) + hash + end + end + + def where_clause(join_string = " AND ") @relation.send(:where_clauses).join(join_string) end + def references_eager_loaded_tables? + joined_tables = (tables_in_string(@relation.joins(relation)) + [table.name, table.table_alias]).compact.uniq + (tables_in_string(to_sql) - joined_tables).any? + end + + def tables_in_string(string) + return [] if string.blank? + string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.uniq + end + end end diff --git a/activerecord/lib/active_record/relation/calculation_methods.rb b/activerecord/lib/active_record/relation/calculation_methods.rb index a925a99464..5246c7bc5d 100644 --- a/activerecord/lib/active_record/relation/calculation_methods.rb +++ b/activerecord/lib/active_record/relation/calculation_methods.rb @@ -53,7 +53,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: column = if @klass.column_names.include?(column_name.to_s) - Arel::Attribute.new(@klass.arel_table, column_name) + Arel::Attribute.new(@klass.active_relation, column_name) else Arel::SqlLiteral.new(column_name == :all ? "*" : column_name.to_s) end @@ -77,7 +77,7 @@ module ActiveRecord select_statement = if operation == 'count' && column_name == :all "COUNT(*) AS count_all" else - Arel::Attribute.new(@klass.arel_table, column_name).send(operation).as(aggregate_alias).to_sql + Arel::Attribute.new(@klass.active_relation, column_name).send(operation).as(aggregate_alias).to_sql end select_statement << ", #{group_field} AS #{group_alias}" diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7a1d6fc538..c3e5f27838 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -21,8 +21,8 @@ module ActiveRecord end def exists?(id = nil) - relation = select("#{@klass.quoted_table_name}.#{@klass.primary_key}").limit(1) - relation = relation.where(@klass.primary_key => id) if id + relation = select(primary_key).limit(1) + relation = relation.where(primary_key.eq(id)) if id relation.first ? true : false end @@ -78,7 +78,7 @@ module ActiveRecord end def find_one(id) - record = where(@klass.primary_key => id).first + record = where(primary_key.eq(id)).first unless record conditions = where_clause(', ') @@ -90,7 +90,7 @@ module ActiveRecord end def find_some(ids) - result = where(@klass.primary_key => ids).all + result = where(primary_key.in(ids)).all expected_size = if @relation.taken && ids.size > @relation.taken diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 0000000000..6b7d941350 --- /dev/null +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,45 @@ +module ActiveRecord + class PredicateBuilder + + def initialize(engine) + @engine = engine + end + + def build_from_hash(attributes, default_table) + predicates = attributes.map do |column, value| + table = default_table + + if value.is_a?(Hash) + table = Arel::Table.new(column, :engine => @engine) + build_from_hash(value, table) + else + column = column.to_s + + if column.include?('.') + table_name, column = column.split('.', 2) + table = Arel::Table.new(table_name, :engine => @engine) + end + + attribute = table[column] || Arel::Attribute.new(table, column.to_sym) + + case value + when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope + attribute.in(value) + when Range + # TODO : Arel should handle ranges with excluded end. + if value.exclude_end? + [attribute.gteq(value.begin), attribute.lt(value.end)] + else + attribute.in(value) + end + else + attribute.eq(value) + end + end + end + + predicates.flatten + end + + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 631c80da25..cf2cc7ba70 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -5,6 +5,10 @@ module ActiveRecord spawn.tap {|r| r.preload_associations += Array.wrap(associations) } end + def includes(*associations) + spawn.tap {|r| r.include_associations += Array.wrap(associations) } + end + def eager_load(*associations) spawn.tap {|r| r.eager_load_associations += Array.wrap(associations) } end @@ -104,14 +108,19 @@ module ActiveRecord def where(*args) return spawn if args.blank? - if [String, Hash, Array].include?(args.first.class) - conditions = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) - conditions = Arel::SqlLiteral.new(conditions) if conditions + builder = PredicateBuilder.new(Arel::Sql::Engine.new(@klass)) + + conditions = if [String, Array].include?(args.first.class) + merged = @klass.send(:merge_conditions, args.size > 1 ? Array.wrap(args) : args.first) + Arel::SqlLiteral.new(merged) if merged + elsif args.first.is_a?(Hash) + attributes = @klass.send(:expand_hash_conditions_for_aggregates, args.first) + builder.build_from_hash(attributes, table) else - conditions = args.first + args.first end - spawn(@relation.where(conditions)) + conditions.is_a?(String) ? spawn(@relation.where(conditions)) : spawn(@relation.where(*conditions)) end private diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index ffbe1b5c40..7efd312357 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -8,24 +8,25 @@ module ActiveRecord def validate_each(record, attribute, value) finder_class = find_finder_class_for(record) + table = finder_class.active_relation + table_name = record.class.quoted_table_name sql, params = mount_sql_and_params(finder_class, table_name, attribute, value) + relation = table.where(sql, *params) + Array(options[:scope]).each do |scope_item| scope_value = record.send(scope_item) - sql << " AND " << record.class.send(:attribute_condition, "#{table_name}.#{scope_item}", scope_value) - params << scope_value + relation = relation.where(scope_item => scope_value) end unless record.new_record? - sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" - params << record.send(:id) + # TODO : This should be in Arel + relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id)) end - finder_class.send(:with_exclusive_scope) do - if finder_class.exists?([sql, *params]) - record.errors.add(attribute, :taken, :default => options[:message], :value => value) - end + if relation.exists? + record.errors.add(attribute, :taken, :default => options[:message], :value => value) end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 289c89d1e2..d359ad48c5 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -327,10 +327,4 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert !account.new_record? assert_equal 500, account.credit_limit end - - def test_create!_respects_hash_condition - account = companies(:first_firm).create_account_limit_500_with_hash_conditions! - assert !account.new_record? - assert_equal 500, account.credit_limit - end end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 803e5b25b1..cf763d730a 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -786,14 +786,14 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase def test_should_automatically_validate_the_associated_model @pirate.ship.name = '' assert @pirate.invalid? - assert @pirate.errors[:ship_name].any? + assert @pirate.errors[:"ship.name"].any? end def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid @pirate.ship.name = nil @pirate.catchphrase = nil assert @pirate.invalid? - assert @pirate.errors[:ship_name].any? + assert @pirate.errors[:"ship.name"].any? assert @pirate.errors[:catchphrase].any? end @@ -886,7 +886,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase def test_should_automatically_validate_the_associated_model @ship.pirate.catchphrase = '' assert @ship.invalid? - assert @ship.errors[:pirate_catchphrase].any? + assert @ship.errors[:"pirate.catchphrase"].any? end def test_should_merge_errors_on_the_associated_model_onto_the_parent_even_if_it_is_not_valid @@ -894,7 +894,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @ship.pirate.catchphrase = nil assert @ship.invalid? assert @ship.errors[:name].any? - assert @ship.errors[:pirate_catchphrase].any? + assert @ship.errors[:"pirate.catchphrase"].any? end def test_should_still_allow_to_bypass_validations_on_the_associated_model @@ -961,7 +961,7 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).each { |child| child.name = '' } assert !@pirate.valid? - assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"] + assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[@association_name].empty? end @@ -969,16 +969,33 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).build(:name => '') assert !@pirate.valid? - assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"] + assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[@association_name].empty? end + def test_should_default_invalid_error_from_i18n + I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => + { @association_name.to_s.singularize.to_sym => { :blank => "cannot be blank" } } + }}) + + @pirate.send(@association_name).build(:name => '') + + assert !@pirate.valid? + assert_equal ["cannot be blank"], @pirate.errors["#{@association_name}.name"] + assert_equal ["#{@association_name.to_s.titleize} name cannot be blank"], @pirate.errors.full_messages + assert @pirate.errors[@association_name].empty? + ensure + I18n.backend.store_translations(:en, :activerecord => { :errors => { :models => + { @association_name.to_s.singularize.to_sym => nil } + }}) + end + def test_should_merge_errors_on_the_associated_models_onto_the_parent_even_if_it_is_not_valid @pirate.send(@association_name).each { |child| child.name = '' } @pirate.catchphrase = nil assert !@pirate.valid? - assert_equal ["can't be blank"], @pirate.errors["#{@association_name}_name"] + assert_equal ["can't be blank"], @pirate.errors["#{@association_name}.name"] assert @pirate.errors[:catchphrase].any? end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ebb717812d..730d9d8df7 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -201,7 +201,7 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.new(:title => "New Topic") assert topic.save! - reply = Reply.new + reply = WrongReply.new assert_raise(ActiveRecord::RecordInvalid) { reply.save! } end @@ -959,6 +959,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_update_attributes! + Reply.validates_presence_of(:title) reply = Reply.find(2) assert_equal "The Second Topic of the day", reply.title assert_equal "Have a nice day", reply.content @@ -974,6 +975,8 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "Have a nice day", reply.content assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(:title => nil, :content => "Have a nice evening") } + ensure + Reply.reset_callbacks(:validate) end def test_mass_assignment_should_raise_exception_if_accessible_and_protected_attribute_writers_are_both_used diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 60c5bad225..8891282915 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -592,7 +592,7 @@ module NestedAttributesOnACollectionAssociationTests assert_no_difference ['Man.count', 'Interest.count'] do man = Man.create(:name => 'John', :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) - assert !man.errors[:interests_man].empty? + assert !man.errors[:"interests.man"].empty? end end # restore :inverse_of diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 6605c8bf34..18f6152cc0 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -10,6 +10,7 @@ require 'models/comment' require 'models/entrant' require 'models/developer' require 'models/company' +require 'models/bird' class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, @@ -177,7 +178,7 @@ class RelationTest < ActiveRecord::TestCase end end - def test_find_with_included_associations + def test_find_with_preloaded_associations assert_queries(2) do posts = Post.preload(:comments) assert posts.first.comments.first @@ -205,6 +206,29 @@ class RelationTest < ActiveRecord::TestCase end end + def test_find_with_included_associations + assert_queries(2) do + posts = Post.includes(:comments) + assert posts.first.comments.first + end + + assert_queries(2) do + posts = Post.scoped.includes(:comments) + assert posts.first.comments.first + end + + assert_queries(2) do + posts = Post.includes(:author) + assert posts.first.author + end + + assert_queries(3) do + posts = Post.includes(:author, :comments).to_a + assert posts.first.author + assert posts.first.comments.first + end + end + def test_default_scope_with_conditions_string assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.scoped.map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name @@ -477,4 +501,43 @@ class RelationTest < ActiveRecord::TestCase assert posts.many? assert ! posts.limit(1).many? end + + def test_build + posts = Post.scoped + + post = posts.new + assert_kind_of Post, post + end + + def test_scoped_build + posts = Post.where(:title => 'You told a lie') + + post = posts.new + assert_kind_of Post, post + assert_equal 'You told a lie', post.title + end + + def test_create + birds = Bird.scoped + + sparrow = birds.create + assert_kind_of Bird, sparrow + assert sparrow.new_record? + + hen = birds.where(:name => 'hen').create + assert ! hen.new_record? + assert_equal 'hen', hen.name + end + + def test_create_bang + birds = Bird.scoped + + assert_raises(ActiveRecord::RecordInvalid) { birds.create! } + + hen = birds.where(:name => 'hen').create! + assert_kind_of Bird, hen + assert ! hen.new_record? + assert_equal 'hen', hen.name + end + end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 278a7a6a06..5ed997356b 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -10,7 +10,7 @@ require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics, :owners - repair_validations(Topic) + repair_validations(Topic, Reply) def test_validates_size_of_association repair_validations(Owner) do @@ -40,7 +40,8 @@ class AssociationValidationTest < ActiveRecord::TestCase end def test_validates_associated_many - Topic.validates_associated( :replies ) + Topic.validates_associated(:replies) + Reply.validates_presence_of(:content) t = Topic.create("title" => "uhohuhoh", "content" => "whatever") t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")] assert !t.valid? diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 532de67d99..f017f24048 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -3,8 +3,9 @@ require 'models/topic' require 'models/reply' class I18nValidationTest < ActiveRecord::TestCase + repair_validations(Topic, Reply) def setup - Topic.reset_callbacks(:validate) + Reply.validates_presence_of(:title) @topic = Topic.new @old_load_path, @old_backend = I18n.load_path, I18n.backend I18n.load_path.clear @@ -13,7 +14,6 @@ class I18nValidationTest < ActiveRecord::TestCase end def teardown - Topic.reset_callbacks(:validate) I18n.load_path.replace @old_load_path I18n.backend = @old_backend end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index db633339f3..8f84841fe6 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -5,7 +5,6 @@ require 'models/reply' require 'models/warehouse_thing' require 'models/guid' require 'models/event' -require 'models/developer' # The following methods in Topic are used in test_conditional_validation_* class Topic @@ -276,14 +275,4 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert w6.errors[:city].any?, "Should have errors for city" assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" end - - def test_validates_uniqueness_of_with_custom_message_using_quotes - repair_validations(Developer) do - Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes" - d = Developer.new - d.name = "David" - assert !d.valid? - assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name] - end - end end diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb index e04738d209..11912ca1cc 100644 --- a/activerecord/test/cases/validations_repair_helper.rb +++ b/activerecord/test/cases/validations_repair_helper.rb @@ -4,31 +4,19 @@ module ActiveRecord module ClassMethods def repair_validations(*model_classes) - setup do - @_stored_callbacks = {} - model_classes.each do |k| - @_stored_callbacks[k] = k._validate_callbacks.dup - end - end teardown do model_classes.each do |k| - k._validate_callbacks = @_stored_callbacks[k] - k.__update_callbacks(:validate) + k.reset_callbacks(:validate) end end end end - def repair_validations(*model_classes, &block) - @__stored_callbacks = {} - model_classes.each do |k| - @__stored_callbacks[k] = k._validate_callbacks.dup - end - return block.call + def repair_validations(*model_classes) + yield ensure model_classes.each do |k| - k._validate_callbacks = @__stored_callbacks[k] - k.__update_callbacks(:validate) + k.reset_callbacks(:validate) end end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 7462d944e0..3a1d5ae212 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -42,7 +42,7 @@ class ValidationsTest < ActiveRecord::TestCase repair_validations(Topic) def test_error_on_create - r = Reply.new + r = WrongReply.new r.title = "Wrong Create" assert !r.valid? assert r.errors[:title].any?, "A reply with a bad title should mark that attribute as invalid" @@ -50,7 +50,7 @@ class ValidationsTest < ActiveRecord::TestCase end def test_error_on_update - r = Reply.new + r = WrongReply.new r.title = "Bad" r.content = "Good" assert r.save, "First save should be successful" @@ -63,11 +63,11 @@ class ValidationsTest < ActiveRecord::TestCase end def test_invalid_record_exception - assert_raise(ActiveRecord::RecordInvalid) { Reply.create! } - assert_raise(ActiveRecord::RecordInvalid) { Reply.new.save! } + assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } + assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! } begin - r = Reply.new + r = WrongReply.new r.save! flunk rescue ActiveRecord::RecordInvalid => invalid @@ -77,13 +77,13 @@ class ValidationsTest < ActiveRecord::TestCase def test_exception_on_create_bang_many assert_raise(ActiveRecord::RecordInvalid) do - Reply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }]) + WrongReply.create!([ { "title" => "OK" }, { "title" => "Wrong Create" }]) end end def test_exception_on_create_bang_with_block assert_raise(ActiveRecord::RecordInvalid) do - Reply.create!({ "title" => "OK" }) do |r| + WrongReply.create!({ "title" => "OK" }) do |r| r.content = nil end end @@ -91,15 +91,15 @@ class ValidationsTest < ActiveRecord::TestCase def test_exception_on_create_bang_many_with_block assert_raise(ActiveRecord::RecordInvalid) do - Reply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r| + WrongReply.create!([{ "title" => "OK" }, { "title" => "Wrong Create" }]) do |r| r.content = nil end end end def test_scoped_create_without_attributes - Reply.send(:with_scope, :create => {}) do - assert_raise(ActiveRecord::RecordInvalid) { Reply.create! } + WrongReply.send(:with_scope, :create => {}) do + assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } end end @@ -122,15 +122,15 @@ class ValidationsTest < ActiveRecord::TestCase end def test_create_without_validation - reply = Reply.new + reply = WrongReply.new assert !reply.save assert reply.save(false) end def test_create_without_validation_bang - count = Reply.count - assert_nothing_raised { Reply.new.save_without_validation! } - assert count+1, Reply.count + count = WrongReply.count + assert_nothing_raised { WrongReply.new.save_without_validation! } + assert count+1, WrongReply.count end def test_validates_acceptance_of_with_non_existant_table diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index ba5a1d1d01..f1ba45b528 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -7,11 +7,13 @@ class Reply < Topic belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count" has_many :replies, :class_name => "SillyReply", :dependent => :destroy, :foreign_key => "parent_id" + attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title +end + +class WrongReply < Reply validate :errors_on_empty_content validate :title_is_wrong_create, :on => :create - attr_accessible :title, :author_name, :author_email_address, :written_on, :content, :last_read, :parent_title - validate :check_empty_title validate :check_content_mismatch, :on => :create validate :check_wrong_update, :on => :update diff --git a/activeresource/lib/active_resource/railtie.rb b/activeresource/lib/active_resource/railtie.rb new file mode 100644 index 0000000000..4f264c82b8 --- /dev/null +++ b/activeresource/lib/active_resource/railtie.rb @@ -0,0 +1,2 @@ +require "active_resource" +require "rails"
\ No newline at end of file diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index 6a41aab166..ee02ecb043 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/benchmark' +require 'active_support/core_ext/hash/keys' module ActiveSupport module Benchmarkable diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index f2d957f154..ad238c1d96 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -4,6 +4,7 @@ require 'active_support/core_ext/benchmark' require 'active_support/core_ext/exception' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/string/inflections' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. diff --git a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb index d87eb17337..d2370d78c5 100644 --- a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb @@ -1,3 +1,5 @@ +require 'active_support/gzip' + module ActiveSupport module Cache class CompressedMemCacheStore < MemCacheStore diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 1b6b820ca4..d584c9e254 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -1,4 +1,5 @@ require 'memcache' +require 'active_support/core_ext/array/extract_options' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 5f6fe22416..86c7703c27 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/string/inflections' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 05bc453dbf..8719dc0e3f 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/inheritable_attributes' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/object/metaclass' module ActiveSupport # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic @@ -367,12 +368,6 @@ module ActiveSupport method << "halted ? false : (block_given? ? value : true)" method.compact.join("\n") end - - def clone(klass) - chain = CallbackChain.new(@name, @config.dup) - callbacks = map { |c| c.clone(chain, klass) } - chain.push(*callbacks) - end end module ClassMethods @@ -389,10 +384,16 @@ module ActiveSupport # key. See #define_callbacks for more information. # def __define_runner(symbol) #:nodoc: + send("_update_#{symbol}_superclass_callbacks") body = send("_#{symbol}_callbacks").compile(nil) body, line = <<-RUBY_EVAL, __LINE__ def _run_#{symbol}_callbacks(key = nil, &blk) + if self.class.send("_update_#{symbol}_superclass_callbacks") + self.class.__define_runner(#{symbol.inspect}) + return _run_#{symbol}_callbacks(key, &blk) + end + if key name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" @@ -431,6 +432,8 @@ module ActiveSupport # CallbackChain. # def __update_callbacks(name, filters = [], block = nil) #:nodoc: + send("_update_#{name}_superclass_callbacks") + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before options = filters.last.is_a?(Hash) ? filters.pop : {} filters.unshift(block) if block @@ -470,7 +473,8 @@ module ActiveSupport def set_callback(name, *filter_list, &block) __update_callbacks(name, filter_list, block) do |chain, type, filters, options| filters.map! do |filter| - chain.delete_if {|c| c.matches?(type, filter) } + removed = chain.delete_if {|c| c.matches?(type, filter) } + send("_removed_#{name}_callbacks").push(*removed) Callback.new(chain, filter, type, options.dup, self) end @@ -482,16 +486,17 @@ module ActiveSupport # def skip_callback(name, *filter_list, &block) __update_callbacks(name, filter_list, block) do |chain, type, filters, options| - chain = send("_#{name}_callbacks=", chain.clone(self)) - filters.each do |filter| filter = chain.find {|c| c.matches?(type, filter) } if filter && options.any? - filter.recompile!(options, options[:per_key] || {}) - else - chain.delete(filter) + new_filter = filter.clone(chain, self) + chain.insert(chain.index(filter), new_filter) + new_filter.recompile!(options, options[:per_key] || {}) end + + chain.delete(filter) + send("_removed_#{name}_callbacks") << filter end end end @@ -499,7 +504,9 @@ module ActiveSupport # Reset callbacks for a given type. # def reset_callbacks(symbol) - send("_#{symbol}_callbacks").clear + callbacks = send("_#{symbol}_callbacks") + callbacks.clear + send("_removed_#{symbol}_callbacks").concat(callbacks) __define_runner(symbol) end @@ -546,14 +553,46 @@ module ActiveSupport # # Defaults to :kind. # - def define_callbacks(*symbols) - config = symbols.last.is_a?(Hash) ? symbols.pop : {} - symbols.each do |symbol| - extlib_inheritable_accessor("_#{symbol}_callbacks") do - CallbackChain.new(symbol, config) + def define_callbacks(*callbacks) + config = callbacks.last.is_a?(Hash) ? callbacks.pop : {} + callbacks.each do |callback| + extlib_inheritable_reader("_#{callback}_callbacks") do + CallbackChain.new(callback, config) + end + + extlib_inheritable_reader("_removed_#{callback}_callbacks") do + [] end - __define_runner(symbol) + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def self._#{callback}_superclass_callbacks + if superclass.respond_to?(:_#{callback}_callbacks) + superclass._#{callback}_callbacks + superclass._#{callback}_superclass_callbacks + else + [] + end + end + + def self._update_#{callback}_superclass_callbacks + changed, index = false, 0 + + callbacks = (_#{callback}_superclass_callbacks - + _#{callback}_callbacks) - _removed_#{callback}_callbacks + + callbacks.each do |callback| + if new_index = _#{callback}_callbacks.index(callback) + index = new_index + 1 + else + changed = true + _#{callback}_callbacks.insert(index, callback) + index = index + 1 + end + end + changed + end + METHOD + + __define_runner(callback) end end end diff --git a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb index 1c3ef05526..d3c3575748 100644 --- a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +++ b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb @@ -1,3 +1,5 @@ +require 'cgi' + class CGI #:nodoc: if RUBY_VERSION >= '1.9' def self.escape_skipping_slashes(str) diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 301c09fc73..72e0eefb0a 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/array/extract_options' class Class diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index e4d22516c1..2b8e2b544f 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -159,7 +159,7 @@ class Class # (error out or do the same as other methods above) instead of silently # moving on). In particular, this makes the return value of this function # less useful. - def extlib_inheritable_reader(*ivars) + def extlib_inheritable_reader(*ivars, &block) options = ivars.extract_options! ivars.each do |ivar| @@ -178,6 +178,7 @@ class Class end RUBY end + instance_variable_set(:"@#{ivar}", yield) if block_given? end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 2b76b93153..9d2ad2bbcf 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,6 +1,7 @@ require 'date' require 'active_support/duration' require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/object/acts_like' class Date class << self diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index e93abfa4a3..26979aa906 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,4 +1,5 @@ require 'rational' unless RUBY_VERSION >= '1.9.2' +require 'active_support/core_ext/object/acts_like' class DateTime class << self diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 35ccec5df4..cfd840fb93 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,6 +1,8 @@ require 'active_support/time' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' +require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/string/inflections' class Hash # This module exists to decorate files deserialized using Hash.from_xml with diff --git a/activesupport/lib/active_support/core_ext/module/loading.rb b/activesupport/lib/active_support/core_ext/module/loading.rb index 4b4b110b25..43d0578ae6 100644 --- a/activesupport/lib/active_support/core_ext/module/loading.rb +++ b/activesupport/lib/active_support/core_ext/module/loading.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/string/inflections' + class Module # Returns String#underscore applied to the module name minus trailing classes. # diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb index f72d512340..115b8abd4e 100644 --- a/activesupport/lib/active_support/core_ext/module/synchronization.rb +++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/module/aliasing' +require 'active_support/core_ext/array/extract_options' class Module # Synchronize access around a method, delegating synchronization to a diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index de8121f274..b8b6970382 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/class/removal' +require 'active_support/core_ext/object/blank' + class Class # Rubinius if defined?(Class.__subclasses__) diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 7ca763cbad..49e41e919a 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -1,3 +1,5 @@ + + class Object # Alias of <tt>to_s</tt>. def to_param diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index deb29a82b8..cec8024b17 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/module/deprecation' require 'active_support/core_ext/module/aliasing' +require 'active_support/core_ext/array/extract_options' module ActiveSupport class << Deprecation diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 713ae1b671..c1f0e4bf81 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,5 +1,6 @@ require 'active_support/basic_object' require 'active_support/core_ext/array/conversions' +require 'active_support/core_ext/object/acts_like' module ActiveSupport # Provides accurate date and time measurements using Date#advance and diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 347af9dc76..51fa626b45 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -1,4 +1,5 @@ require 'openssl' +require 'active_support/base64' module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored somewhere diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 87e4b1ad33..6c46b68eaf 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,3 +1,6 @@ +require 'active_support/base64' +require 'active_support/core_ext/object/blank' + module ActiveSupport # MessageVerifier makes it easy to generate and verify messages which are signed # to prevent tampering. diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index f0119f5994..6e660f8647 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -1,5 +1,7 @@ require 'active_support/core_ext/class/inheritable_attributes' require 'active_support/core_ext/proc' +require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/array/extract_options' module ActiveSupport # Rescuable module adds support for easier exception handling. diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 66e32fa5d7..24eea1e40b 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -4,6 +4,7 @@ begin require 'fileutils' require 'rails/version' require 'active_support/core_ext/class/delegating_attributes' + require 'active_support/core_ext/string/inflections' module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 8304f6c434..6b554e7158 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,4 +1,5 @@ require "active_support/values/time_zone" +require 'active_support/core_ext/object/acts_like' module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 5c3d93c4a1..48c1cb3fe9 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -3,6 +3,8 @@ raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFO require 'jruby' include Java +require 'active_support/core_ext/object/blank' + import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory import java.io.StringReader unless defined? StringReader diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 0f7ba1918b..9cf187302f 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -1,4 +1,6 @@ require 'libxml' +require 'active_support/core_ext/object/returning' +require 'active_support/core_ext/object/blank' # = XmlMini LibXML implementation module ActiveSupport @@ -12,7 +14,7 @@ module ActiveSupport if !data.respond_to?(:read) data = StringIO.new(data || '') end - + char = data.getc if char.nil? {} @@ -34,106 +36,42 @@ module LibXML #:nodoc: end module Node #:nodoc: - CONTENT_ROOT = '__content__' - LIB_XML_LIMIT = 30000000 # Hardcoded LibXML limit + CONTENT_ROOT = '__content__'.freeze # Convert XML document to hash # # hash:: # Hash to merge the converted element into. def to_hash(hash={}) - if text? || cdata? - raise LibXML::XML::Error if hash[CONTENT_ROOT].to_s.length + content.length >= LIB_XML_LIMIT - hash[CONTENT_ROOT] = hash[CONTENT_ROOT].to_s + content - else - sub_hash = insert_name_into_hash(hash, name) - attributes_to_hash(sub_hash) - if array? - children_array_to_hash(sub_hash) - elsif yaml? - children_yaml_to_hash(sub_hash) - else - children_to_hash(sub_hash) - end - end - hash - end + node_hash = {} - protected - - # Insert name into hash - # - # hash:: - # Hash to merge the converted element into. - # name:: - # name to to merge into hash - def insert_name_into_hash(hash, name) - sub_hash = {} - if hash[name] - if !hash[name].kind_of? Array - hash[name] = [hash[name]] - end - hash[name] << sub_hash - else - hash[name] = sub_hash - end - sub_hash + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash end - # Insert children into hash - # - # hash:: - # Hash to merge the children into. - def children_to_hash(hash={}) - each { |child| child.to_hash(hash) } - - if hash.length > 1 && hash[CONTENT_ROOT].blank? - hash.delete(CONTENT_ROOT) + # Handle child elements + each_child do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] << c.content end - - attributes_to_hash(hash) - hash end - # Convert xml attributes to hash - # - # hash:: - # Hash to merge the attributes into - def attributes_to_hash(hash={}) - each_attr { |attr| hash[attr.name] = attr.value } - hash + # Remove content node if it is blank + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) end - # Convert array into hash - # - # hash:: - # Hash to merge the array into - def children_array_to_hash(hash={}) - hash[child.name] = map do |child| - returning({}) { |sub_hash| child.children_to_hash(sub_hash) } - end - hash - end - - # Convert yaml into hash - # - # hash:: - # Hash to merge the yaml into - def children_yaml_to_hash(hash = {}) - hash[CONTENT_ROOT] = content unless content.blank? - hash - end - - # Check if child is of type array - def array? - child? && child.next? && child.name == child.next.name - end - - # Check if child is of type yaml - def yaml? - attributes.collect{|x| x.value}.include?('yaml') - end + # Handle attributes + each_attr { |a| node_hash[a.name] = a.value } + hash + end end end end diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb new file mode 100644 index 0000000000..d7b2f4c5be --- /dev/null +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -0,0 +1,84 @@ +require 'libxml' + +# = XmlMini LibXML implementation using a SAX-based parser +module ActiveSupport + module XmlMini_LibXMLSAX + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder + + include LibXML::XML::SaxParser::Callbacks + + CONTENT_KEY = '__content__'.freeze + HASH_SIZE_KEY = '__hash_size__'.freeze + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def on_start_document + @hash = { CONTENT_KEY => '' } + @hash_stack = [@hash] + end + + def on_end_document + @hash = @hash_stack.pop + @hash.delete(CONTENT_KEY) + end + + def on_start_element(name, attrs = {}) + new_hash = { CONTENT_KEY => '' }.merge(attrs) + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def on_end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def on_characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :on_cdata_block, :on_characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || '') + end + + char = data.getc + if char.nil? + {} + else + data.ungetc(char) + + LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) + parser = LibXML::XML::SaxParser.io(data) + document = self.document_class.new + + parser.callbacks = document + parser.parse + document.hash + end + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 17bacd8441..eb61a7fc22 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -1,4 +1,5 @@ require 'nokogiri' +require 'active_support/core_ext/object/blank' # = XmlMini Nokogiri implementation module ActiveSupport @@ -12,13 +13,13 @@ module ActiveSupport if !data.respond_to?(:read) data = StringIO.new(data || '') end - + char = data.getc if char.nil? {} else data.ungetc(char) - doc = Nokogiri::XML(data) { |cfg| cfg.noblanks } + doc = Nokogiri::XML(data) raise doc.errors.first if doc.errors.length > 0 doc.to_hash end @@ -32,39 +33,41 @@ module ActiveSupport end module Node #:nodoc: - CONTENT_ROOT = '__content__' + CONTENT_ROOT = '__content__'.freeze # Convert XML document to hash # # hash:: # Hash to merge the converted element into. - def to_hash(hash = {}) - attributes = attributes_as_hash - if hash[name] - hash[name] = [hash[name]].flatten - hash[name] << attributes - else - hash[name] ||= attributes - end + def to_hash(hash={}) + node_hash = {} - children.each { |child| - next if child.blank? && 'file' != self['type'] + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash + end - if child.text? || child.cdata? - (attributes[CONTENT_ROOT] ||= '') << child.content - next + # Handle child elements + children.each do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= '' + node_hash[CONTENT_ROOT] << c.content end + end - child.to_hash attributes - } + # Remove content node if it is blank and there are child tags + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) + end - hash - end + # Handle attributes + attribute_nodes.each { |a| node_hash[a.node_name] = a.value } - def attributes_as_hash - Hash[*(attribute_nodes.map { |node| - [node.node_name, node.value] - }.flatten)] + hash end end end diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb new file mode 100644 index 0000000000..d538a9110f --- /dev/null +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -0,0 +1,82 @@ +require 'nokogiri' + +# = XmlMini Nokogiri implementation using a SAX-based parser +module ActiveSupport + module XmlMini_NokogiriSAX + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder < Nokogiri::XML::SAX::Document + + CONTENT_KEY = '__content__'.freeze + HASH_SIZE_KEY = '__hash_size__'.freeze + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def start_document + @hash = {} + @hash_stack = [@hash] + end + + def end_document + raise "Parse stack not empty!" if @hash_stack.size > 1 + end + + def error(error_message) + raise error_message + end + + def start_element(name, attrs = []) + new_hash = { CONTENT_KEY => '' } + new_hash[attrs.shift] = attrs.shift while attrs.length > 0 + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == '' + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :cdata_block, :characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || '') + end + + char = data.getc + if char.nil? + {} + else + data.ungetc(char) + document = self.document_class.new + parser = Nokogiri::XML::SAX::Parser.new(document) + parser.parse(data) + document.hash + end + end + end +end
\ No newline at end of file diff --git a/activesupport/test/callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index 18721eab19..2e978f01be 100644 --- a/activesupport/test/callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -56,6 +56,31 @@ class Child < GrandParent end end +class EmptyParent + include ActiveSupport::Callbacks + + def performed? + @performed ||= false + end + + define_callbacks :dispatch + + def perform! + @performed = true + end + + def dispatch + _run_dispatch_callbacks + self + end +end + +class EmptyChild < EmptyParent + set_callback :dispatch, :before, :do_nothing + + def do_nothing + end +end class BasicCallbacksTest < Test::Unit::TestCase def setup @@ -113,3 +138,13 @@ class InheritedCallbacksTest2 < Test::Unit::TestCase assert_equal %w(before1 before2 update after2 after1), @update2.log end end + +class DynamicInheritedCallbacks < Test::Unit::TestCase + def test_callbacks_looks_to_the_superclass_before_running + child = EmptyChild.new.dispatch + assert !child.performed? + EmptyParent.set_callback :dispatch, :before, :perform! + child = EmptyChild.new.dispatch + assert child.performed? + end +end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 4642bb1330..5b1d53ac7b 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -902,9 +902,11 @@ class HashToXmlTest < Test::Unit::TestCase def test_expansion_count_is_limited expected = { - 'ActiveSupport::XmlMini_REXML' => 'RuntimeError', - 'ActiveSupport::XmlMini_Nokogiri' => 'Nokogiri::XML::SyntaxError', - 'ActiveSupport::XmlMini_LibXML' => 'LibXML::XML::Error', + 'ActiveSupport::XmlMini_REXML' => 'RuntimeError', + 'ActiveSupport::XmlMini_Nokogiri' => 'Nokogiri::XML::SyntaxError', + 'ActiveSupport::XmlMini_NokogiriSAX' => 'RuntimeError', + 'ActiveSupport::XmlMini_LibXML' => 'LibXML::XML::Error', + 'ActiveSupport::XmlMini_LibXMLSAX' => 'LibXML::XML::Error', }[ActiveSupport::XmlMini.backend.name].constantize assert_raise expected do diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb index 900c8052d6..83d03bccc6 100644 --- a/activesupport/test/xml_mini/libxml_engine_test.rb +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -184,6 +184,15 @@ class LibxmlEngineTest < Test::Unit::TestCase eoxml end + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) + <root> + <products type="file"> </products> + </root> + eoxml + end + + private def assert_equal_rexml(xml) hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } diff --git a/activesupport/test/xml_mini/libxmlsax_engine_test.rb b/activesupport/test/xml_mini/libxmlsax_engine_test.rb new file mode 100644 index 0000000000..864810099e --- /dev/null +++ b/activesupport/test/xml_mini/libxmlsax_engine_test.rb @@ -0,0 +1,194 @@ +require 'abstract_unit' +require 'active_support/xml_mini' +require 'active_support/core_ext/hash/conversions' + +begin + require 'libxml' +rescue LoadError + # Skip libxml tests +else + +class LibXMLSAXEngineTest < Test::Unit::TestCase + include ActiveSupport + + def setup + @default_backend = XmlMini.backend + XmlMini.backend = 'LibXMLSAX' + end + + def teardown + XmlMini.backend = @default_backend + end + + def test_exception_thrown_on_expansion_attack + assert_raise LibXML::XML::Error do + attack_xml = <<-EOT + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + EOT + + Hash.from_xml(attack_xml) + end + end + + def test_setting_libxml_as_backend + XmlMini.backend = 'LibXMLSAX' + assert_equal XmlMini_LibXMLSAX, XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse('')) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + <blog> + <posts type="array"> + <post>a post</post> + <post>another post</post> + </posts> + </blog> + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + </products> + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + <book name="america" id="67890" /> + </products> + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + <products> + hello world + </products> + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + <root> + <products> + <book name="america" id="67890" /> + </products> + </root> + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello everyone + </products> + </root> + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_parse_from_io + io = StringIO.new(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + XmlMini.parse(io) + end + + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> + morning + </products> + </root> + eoxml + end + + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + private + def assert_equal_rexml(xml) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + assert_equal(hash, XmlMini.parse(xml)) + end +end + +end diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index e16f36acee..db0d7c5b02 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -159,17 +159,53 @@ class NokogiriEngineTest < Test::Unit::TestCase XmlMini.parse(io) end - def test_children_with_cdata + def test_children_with_simple_cdata assert_equal_rexml(<<-eoxml) <root> <products> - hello <![CDATA[everyone]]> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> morning </products> </root> eoxml end + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) + <root> + <products type="file"> </products> + </root> + eoxml + end + private def assert_equal_rexml(xml) hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb new file mode 100644 index 0000000000..1149d0fecc --- /dev/null +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -0,0 +1,217 @@ +require 'abstract_unit' +require 'active_support/xml_mini' +require 'active_support/core_ext/hash/conversions' + +begin + require 'nokogiri' +rescue LoadError + # Skip nokogiri tests +else + +class NokogiriSAXEngineTest < Test::Unit::TestCase + include ActiveSupport + + def setup + @default_backend = XmlMini.backend + XmlMini.backend = 'NokogiriSAX' + end + + def teardown + XmlMini.backend = @default_backend + end + + def test_file_from_xml + hash = Hash.from_xml(<<-eoxml) + <blog> + <logo type="file" name="logo.png" content_type="image/png"> + </logo> + </blog> + eoxml + assert hash.has_key?('blog') + assert hash['blog'].has_key?('logo') + + file = hash['blog']['logo'] + assert_equal 'logo.png', file.original_filename + assert_equal 'image/png', file.content_type + end + + def test_exception_thrown_on_expansion_attack + assert_raise RuntimeError do + attack_xml = <<-EOT + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + EOT + + Hash.from_xml(attack_xml) + end + end + + def test_setting_nokogiri_as_backend + XmlMini.backend = 'Nokogiri' + assert_equal XmlMini_Nokogiri, XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse('')) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + <blog> + <posts type="array"> + <post>a post</post> + <post>another post</post> + </posts> + </blog> + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + </products> + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + <book name="america" id="67890" /> + </products> + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + <products> + hello world + </products> + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + <root> + <products> + <book name="america" id="67890" /> + </products> + </root> + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello everyone + </products> + </root> + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_parse_from_io + io = StringIO.new(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + XmlMini.parse(io) + end + + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> + morning + </products> + </root> + eoxml + end + + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + def test_children_with_blank_text_and_attribute + assert_equal_rexml(<<-eoxml) + <root> + <products type="file"> </products> + </root> + eoxml + end + + private + def assert_equal_rexml(xml) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + assert_equal(hash, XmlMini.parse(xml)) + end +end + +end diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 9fb3cd9f94..d69e3eea6a 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1,9 +1,106 @@ -require "rails/core" +require "pathname" -%w(active_model active_record action_controller action_view action_mailer active_resource).each do |framework| - begin - require framework - require "#{framework}/rails" - rescue LoadError +require 'active_support' +require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/logger' +require 'action_dispatch' + +require 'rails/initializable' +require 'rails/application' +require 'rails/railtie' +require 'rails/plugin' +require 'rails/railties_path' +require 'rails/version' +require 'rails/rack' +require 'rails/paths' +require 'rails/configuration' +require 'rails/deprecation' +require 'rails/ruby_version_check' + +# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the +# multibyte safe operations. Plugin authors supporting other encodings +# should override this behaviour and set the relevant +default_charset+ +# on ActionController::Base. +# +# For Ruby 1.9, UTF-8 is the default internal and external encoding. +if RUBY_VERSION < '1.9' + $KCODE='u' +else + Encoding.default_external = Encoding::UTF_8 +end + +RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV) + +module Rails + # Needs to be duplicated from Active Support since its needed before Active + # Support is available. Here both Options and Hash are namespaced to prevent + # conflicts with other implementations AND with the classes residing in Active Support. + # --- + # TODO: w0t? + class << self + def application + @@application ||= nil + end + + def application=(application) + @@application = application + end + + # The Configuration instance used to configure the Rails environment + def configuration + application.configuration + end + + def initialize! + application.initialize! + end + + def initialized? + @initialized || false + end + + def initialized=(initialized) + @initialized ||= initialized + end + + def logger + if defined?(RAILS_DEFAULT_LOGGER) + RAILS_DEFAULT_LOGGER + else + nil + end + end + + def backtrace_cleaner + @@backtrace_cleaner ||= begin + # Relies on ActiveSupport, so we have to lazy load to postpone definition until AS has been loaded + require 'rails/backtrace_cleaner' + Rails::BacktraceCleaner.new + end + end + + def root + application && application.config.root + end + + def env + @_env ||= ActiveSupport::StringInquirer.new(RAILS_ENV) + end + + def cache + RAILS_CACHE + end + + def version + VERSION::STRING + end + + def public_path + @@public_path ||= self.root ? File.join(self.root, "public") : "public" + end + + def public_path=(path) + @@public_path = path + end end -end
\ No newline at end of file +end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb new file mode 100644 index 0000000000..7dfe2b8b63 --- /dev/null +++ b/railties/lib/rails/all.rb @@ -0,0 +1,15 @@ +require "rails" + +%w( + active_model + active_record + action_controller + action_view + action_mailer + active_resource +).each do |framework| + begin + require "#{framework}/railtie" + rescue LoadError + end +end
\ No newline at end of file diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5419b46f19..26ffbe5a58 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -92,8 +92,8 @@ module Rails def plugins @plugins ||= begin plugin_names = config.plugins || [:all] - Plugin.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } + - Plugin::Vendored.all(config.plugins || [:all], config.paths.vendor.plugins) + Railtie.plugins.select { |p| plugin_names.include?(:all) || plugin_names.include?(p.plugin_name) }.map { |p| p.new } + + Plugin.all(config.plugins || [:all], config.paths.vendor.plugins) end end @@ -269,16 +269,5 @@ module Rails ActiveSupport::Dependencies.unhook! end end - - # For each framework, search for instrument file with Notifications hooks. - # - initializer :load_notifications_hooks do - config.frameworks.each do |framework| - begin - require "#{framework}/notifications" - rescue LoadError => e - end - end - end end end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 086f67a419..f0a0d5e55e 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -3,7 +3,7 @@ require 'active_support/ordered_options' module Rails # Temporarily separate the plugin configuration class from the main # configuration class while this bit is being cleaned up. - class Plugin::Configuration + class Railtie::Configuration def self.default @default ||= new @@ -45,12 +45,12 @@ module Rails end def config_keys - ([ :active_support, :action_view, :action_mailer, :active_resource ] + - Plugin.plugin_names).map { |n| n.to_s }.uniq + ([ :active_support, :action_view ] + + Railtie.plugin_names).map { |n| n.to_s }.uniq end end - class Configuration < Plugin::Configuration + class Configuration < Railtie::Configuration attr_accessor :after_initialize_blocks, :cache_classes, :consider_all_requests_local, :dependency_loading, :gems, :load_once_paths, :logger, :metals, :plugins, @@ -59,7 +59,7 @@ module Rails attr_writer :cache_store, :controller_paths, :database_configuration_file, :eager_load_paths, - :frameworks, :framework_root_path, :i18n, :load_paths, + :i18n, :load_paths, :log_level, :log_path, :paths, :routes_configuration_file, :view_path @@ -119,6 +119,13 @@ module Rails end end + def frameworks(*args) + raise "config.frameworks in no longer supported. See the generated" \ + "config/boot.rb for steps on how to limit the frameworks that" \ + "will be loaded" + end + alias frameworks= frameworks + # Enable threaded mode. Allows concurrent requests to controller actions and # multiple database connections. Also disables automatic dependency loading # after boot, and disables reloading code on every request, as these are @@ -134,21 +141,6 @@ module Rails self end - def framework_paths - paths = %w(railties railties/lib activesupport/lib) - paths << 'actionpack/lib' if frameworks.include?(:action_controller) || frameworks.include?(:action_view) - - [:active_record, :action_mailer, :active_resource, :action_web_service].each do |framework| - paths << "#{framework.to_s.gsub('_', '')}/lib" if frameworks.include?(framework) - end - - paths.map { |dir| "#{framework_root_path}/#{dir}" }.select { |dir| File.directory?(dir) } - end - - def framework_root_path - defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root}/vendor/rails" - end - # Loads and returns the contents of the #database_configuration_file. The # contents of the file are processed via ERB before being sent through # YAML::load. @@ -239,10 +231,6 @@ module Rails @log_level ||= RAILS_ENV == 'production' ? :info : :debug end - def frameworks - @frameworks ||= [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] - end - def i18n @i18n ||= begin i18n = ActiveSupport::OrderedOptions.new diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb deleted file mode 100644 index ab95edc676..0000000000 --- a/railties/lib/rails/core.rb +++ /dev/null @@ -1,105 +0,0 @@ -require "pathname" - -require 'active_support' -require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/logger' -require 'action_dispatch' - -require 'rails/initializable' -require 'rails/application' -require 'rails/plugin' -require 'rails/railties_path' -require 'rails/version' -require 'rails/rack' -require 'rails/paths' -require 'rails/configuration' -require 'rails/deprecation' -require 'rails/ruby_version_check' - -# For Ruby 1.8, this initialization sets $KCODE to 'u' to enable the -# multibyte safe operations. Plugin authors supporting other encodings -# should override this behaviour and set the relevant +default_charset+ -# on ActionController::Base. -# -# For Ruby 1.9, UTF-8 is the default internal and external encoding. -if RUBY_VERSION < '1.9' - $KCODE='u' -else - Encoding.default_external = Encoding::UTF_8 -end - -RAILS_ENV = (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup unless defined?(RAILS_ENV) - -module Rails - # Needs to be duplicated from Active Support since its needed before Active - # Support is available. Here both Options and Hash are namespaced to prevent - # conflicts with other implementations AND with the classes residing in Active Support. - # --- - # TODO: w0t? - class << self - def application - @@application ||= nil - end - - def application=(application) - @@application = application - end - - # The Configuration instance used to configure the Rails environment - def configuration - application.configuration - end - - def initialize! - application.initialize! - end - - def initialized? - @initialized || false - end - - def initialized=(initialized) - @initialized ||= initialized - end - - def logger - if defined?(RAILS_DEFAULT_LOGGER) - RAILS_DEFAULT_LOGGER - else - nil - end - end - - def backtrace_cleaner - @@backtrace_cleaner ||= begin - # Relies on ActiveSupport, so we have to lazy load to postpone definition until AS has been loaded - require 'rails/backtrace_cleaner' - Rails::BacktraceCleaner.new - end - end - - def root - application && application.config.root - end - - def env - @_env ||= ActiveSupport::StringInquirer.new(RAILS_ENV) - end - - def cache - RAILS_CACHE - end - - def version - VERSION::STRING - end - - def public_path - @@public_path ||= self.root ? File.join(self.root, "public") : "public" - end - - def public_path=(path) - @@public_path = path - end - end -end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 2ba56bc3c5..6419961415 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -10,7 +10,7 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/inflections' # TODO: Do not always push on vendored thor -$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.1/lib") +$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.3/lib") require 'rails/generators/base' require 'rails/generators/named_base' diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index c03836ed91..ee401b1fde 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -152,7 +152,7 @@ module Rails::Generators inside "tmp" do %w(sessions sockets cache pids).each do |dir| - empty_directory_with_gitkeep(dir) + empty_directory(dir) end end end 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 15dc553e53..ec0729db04 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -13,16 +13,8 @@ module <%= app_name.classify %> # :all can be used as a placeholder for all plugins not explicitly named # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Skip frameworks you're not going to use. To use Rails without a database, - # you must remove the Active Record framework. -<% if options[:skip_activerecord] -%> - config.frameworks -= [ :active_record ] -<% else -%> - # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] - # Activate observers that should always be running # config.active_record.observers = :cacher, :garbage_collector, :forum_observer -<% end -%> # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb index 5aa49ca5e6..6de1725260 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -13,4 +13,13 @@ else require 'rubygems' end -require 'rails' +require 'rails/all' +# To pick the frameworks you want, remove 'require "rails/all"' +# and list the framework railties that you want: +# +# require "active_model/railtie" +# require "active_record/railtie" +# require "action_controller/railtie" +# require "action_view/railtie" +# require "action_mailer/railtie" +# require "active_resource/railtie"
\ No newline at end of file diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index e154e9b706..9cc6b9c35b 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -1,106 +1,64 @@ module Rails - class Plugin - include Initializable - - def self.plugin_name(plugin_name = nil) - @plugin_name ||= name.demodulize.underscore - @plugin_name = plugin_name if plugin_name - @plugin_name - end - - def self.inherited(klass) - @plugins ||= [] - @plugins << klass unless klass == Vendored - end - - def self.plugins - @plugins - end + class Plugin < Railtie + def self.all(list, paths) + plugins = [] + paths.each do |path| + Dir["#{path}/*"].each do |plugin_path| + plugin = new(plugin_path) + next unless list.include?(plugin.name) || list.include?(:all) + plugins << plugin + end + end - def self.plugin_names - plugins.map { |p| p.plugin_name } + plugins.sort_by do |p| + [list.index(p.name) || list.index(:all), p.name.to_s] + end end - def self.config - Configuration.default - end + attr_reader :name, :path - def self.rake_tasks(&blk) - @rake_tasks ||= [] - @rake_tasks << blk if blk - @rake_tasks + def initialize(path) + @name = File.basename(path).to_sym + @path = path end - def rake_tasks - self.class.rake_tasks + def load_paths + Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"] end def load_tasks - return unless rake_tasks - rake_tasks.each { |blk| blk.call } + Dir["#{path}/**/tasks/**/*.rake"].sort.each { |ext| load ext } end - class Vendored < Plugin - def self.all(list, paths) - plugins = [] - paths.each do |path| - Dir["#{path}/*"].each do |plugin_path| - plugin = new(plugin_path) - next unless list.include?(plugin.name) || list.include?(:all) - plugins << plugin - end - end - - plugins.sort_by do |p| - [list.index(p.name) || list.index(:all), p.name.to_s] - end - end - - attr_reader :name, :path - - def initialize(path) - @name = File.basename(path).to_sym - @path = path - end - - def load_paths - Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"] - end - - def load_tasks - Dir["#{path}/**/tasks/**/*.rake"].sort.each { |ext| load ext } - end - - initializer :add_to_load_path, :after => :set_autoload_paths do |app| - load_paths.each do |path| - $LOAD_PATH << path - require "active_support/dependencies" + initializer :add_to_load_path, :after => :set_autoload_paths do |app| + load_paths.each do |path| + $LOAD_PATH << path + require "active_support/dependencies" - ActiveSupport::Dependencies.load_paths << path + ActiveSupport::Dependencies.load_paths << path - unless app.config.reload_plugins - ActiveSupport::Dependencies.load_once_paths << path - end + unless app.config.reload_plugins + ActiveSupport::Dependencies.load_once_paths << path end end + end - initializer :load_init_rb, :before => :load_application_initializers do |app| - file = "#{@path}/init.rb" - config = app.config - eval File.read(file), binding, file if File.file?(file) - end + initializer :load_init_rb, :before => :load_application_initializers do |app| + file = "#{@path}/init.rb" + config = app.config + eval File.read(file), binding, file if File.file?(file) + end - initializer :add_view_paths, :after => :initialize_framework_views do - ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views") - end + initializer :add_view_paths, :after => :initialize_framework_views do + ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views") + end - initializer :add_routing_file, :after => :initialize_routing do |app| - routing_file = "#{path}/config/routes.rb" - if File.exist?(routing_file) - app.route_configuration_files << routing_file - app.reload_routes! - end + initializer :add_routing_file, :after => :initialize_routing do |app| + routing_file = "#{path}/config/routes.rb" + if File.exist?(routing_file) + app.route_configuration_files << routing_file + app.reload_routes! end end end -end +end
\ No newline at end of file diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb new file mode 100644 index 0000000000..ff28ade35d --- /dev/null +++ b/railties/lib/rails/railtie.rb @@ -0,0 +1,43 @@ +module Rails + class Railtie + include Initializable + + def self.plugin_name(plugin_name = nil) + @plugin_name ||= name.demodulize.underscore + @plugin_name = plugin_name if plugin_name + @plugin_name + end + + def self.inherited(klass) + @plugins ||= [] + @plugins << klass unless klass == Plugin + end + + def self.plugins + @plugins + end + + def self.plugin_names + plugins.map { |p| p.plugin_name } + end + + def self.config + Configuration.default + end + + def self.rake_tasks(&blk) + @rake_tasks ||= [] + @rake_tasks << blk if blk + @rake_tasks + end + + def rake_tasks + self.class.rake_tasks + end + + def load_tasks + return unless rake_tasks + rake_tasks.each { |blk| blk.call } + end + end +end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb deleted file mode 100644 index 650253d648..0000000000 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Thor - VERSION = "0.12.1".freeze -end diff --git a/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.12.3/CHANGELOG.rdoc index 606a0cdb52..d02fc43e84 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/CHANGELOG.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.3/CHANGELOG.rdoc @@ -1,9 +1,7 @@ -== TODO - -* Improve spec coverage for Thor::Runner - -== 0.12, released 2009-11-06 +== 0.12, released 2010-01-02 +* Removed rr in favor to rspec mock framework +* Improved output for thor -T * [#7] Do not force white color on status * [#8] Yield a block with the filename on directory @@ -17,7 +15,7 @@ * thor help now show information about any class/task. All those calls are possible: - + thor help describe thor help describe:amazing @@ -47,7 +45,7 @@ are in the 'standard' group. Running 'thor -T' will only show the standard tasks - adding --all will show all tasks. You can also filter on a specific group using the --group option: thor -T --group advanced - + == 0.9.6, released 2008-09-13 * Generic improvements diff --git a/railties/lib/rails/vendor/thor-0.12.1/LICENSE b/railties/lib/rails/vendor/thor-0.12.3/LICENSE index 98722da459..98722da459 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/LICENSE +++ b/railties/lib/rails/vendor/thor-0.12.3/LICENSE diff --git a/railties/lib/rails/vendor/thor-0.12.1/README.rdoc b/railties/lib/rails/vendor/thor-0.12.3/README.rdoc index ee545f3d97..ee545f3d97 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/README.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.3/README.rdoc diff --git a/railties/lib/rails/vendor/thor-0.12.1/Thorfile b/railties/lib/rails/vendor/thor-0.12.3/Thorfile index ff1cb4498a..ff1cb4498a 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/Thorfile +++ b/railties/lib/rails/vendor/thor-0.12.3/Thorfile diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb index 68944f140d..d4d8fbd64d 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb @@ -78,14 +78,14 @@ class Thor @method_options end - # Adds an option to the set of class options. If :for is given as option, + # Adds an option to the set of method options. If :for is given as option, # it allows you to change the options from a previous defined task. # # def previous_task # # magic # end # - # method_options :foo => :bar, :for => :previous_task + # method_option :foo => :bar, :for => :previous_task # # def next_task # # magic @@ -101,7 +101,6 @@ class Thor # :default - Default value for this argument. It cannot be required and have default values. # :aliases - Aliases for this option. # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean. - # :group - The group for this options. Use by class options to output options in different levels. # :banner - String to show on usage notes. # def method_option(name, options={}) @@ -140,49 +139,48 @@ class Thor end end - # Prints help information. If a task name is given, it shows information - # only about the specific task. + # Prints help information for the given task. # # ==== Parameters - # meth<String>:: An optional task name to print usage information about. + # shell<Thor::Shell> + # task_name<String> + # + def task_help(shell, task_name) + task = all_tasks[task_name] + raise UndefinedTaskError, "task '#{task_name}' could not be found in namespace '#{self.namespace}'" unless task + + shell.say "Usage:" + shell.say " #{banner(task)}" + shell.say + class_options_help(shell, nil => task.options.map { |_, o| o }) + shell.say task.description + end + + # Prints help information for this class. # - # ==== Options - # namespace:: When true, shows the namespace in the output before the usage. - # skip_inherited:: When true, does not show tasks from superclass. + # ==== Parameters + # shell<Thor::Shell> # - def help(shell, meth=nil, options={}) - meth, options = nil, meth if meth.is_a?(Hash) - - if meth - task = all_tasks[meth] - raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task - - shell.say "Usage:" - shell.say " #{banner(task, options[:namespace], false)}" - shell.say - class_options_help(shell, "Class", :Method => task.options.map { |_, o| o }) - shell.say task.description - else - list = (options[:short] ? tasks : all_tasks).map do |_, task| - item = [ banner(task, options[:namespace]) ] - item << "# #{task.short_description}" if task.short_description - item << " " - end - - options[:ident] ||= 2 - if options[:short] - shell.print_list(list, :ident => options[:ident]) - else - shell.say "Tasks:" - shell.print_list(list, :ident => options[:ident]) - end + def help(shell) + list = printable_tasks + Thor::Util.thor_classes_in(self).each do |klass| + list += klass.printable_tasks(false) + end + list.sort!{ |a,b| a[0] <=> b[0] } - Thor::Util.thor_classes_in(self).each do |subclass| - namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '') - subclass.help(shell, options.merge(:short => true, :namespace => namespace)) - end + shell.say "Tasks:" + shell.print_table(list, :ident => 2, :truncate => true) + shell.say + class_options_help(shell) + end - class_options_help(shell, "Class") unless options[:short] + # Returns tasks ready to be printed. + def printable_tasks(all=true) + (all ? all_tasks : tasks).map do |_, task| + item = [] + item << banner(task) + item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "") + item end end @@ -193,8 +191,8 @@ class Thor # the task that is going to be invoked and a boolean which indicates if # the namespace should be displayed as arguments. # - def banner(task, namespace=true, show_options=true) - task.formatted_usage(self, namespace, show_options) + def banner(task) + "thor " + task.formatted_usage(self) end def baseclass #:nodoc: @@ -237,6 +235,6 @@ class Thor desc "help [TASK]", "Describe available tasks or one specific task" def help(task=nil) - self.class.help(shell, task, :namespace => task && task.include?(?:)) + task ? self.class.task_help(shell, task) : self.class.help(shell) end end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions.rb index 4bfb7c2870..da98444bf2 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions.rb @@ -1,4 +1,5 @@ require 'fileutils' +require 'thor/core_ext/file_binary_read' Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action| require action @@ -38,17 +39,17 @@ class Thor # Add runtime options that help actions execution. # def add_runtime_options! - class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, - :desc => "Run but do not make any changes" - class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, :desc => "Overwrite files that already exist" - class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, - :desc => "Skip files that already exist" + class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, + :desc => "Run but do not make any changes" class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, :desc => "Supress status output" + + class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, + :desc => "Skip files that already exist" end end @@ -114,7 +115,7 @@ class Thor @source_paths ||= self.class.source_paths_for_search end - # Receives a file or directory and search for it in the source paths. + # Receives a file or directory and search for it in the source paths. # def find_in_source_paths(file) relative_root = relative_to_original_destination_root(destination_root, false) @@ -222,7 +223,7 @@ class Thor run "#{command}", config.merge(:with => Thor::Util.ruby_command) end - # Run a thor command. A hash of options can be given and it's converted to + # Run a thor command. A hash of options can be given and it's converted to # switches. # # ==== Parameters diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/create_file.rb index a3d9296823..6e0eeb43e2 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/create_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/create_file.rb @@ -42,7 +42,7 @@ class Thor # Boolean:: true if it is identical, false otherwise. # def identical? - exists? && File.read(destination) == render + exists? && File.binread(destination) == render end # Holds the content to be added to the file. @@ -58,7 +58,7 @@ class Thor def invoke! invoke_with_conflict_check do FileUtils.mkdir_p(File.dirname(destination)) - File.open(destination, 'w'){ |f| f.write render } + File.open(destination, 'wb') { |f| f.write render } end given_destination end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/directory.rb index 2e0b459fa3..2e0b459fa3 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/directory.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/empty_directory.rb index 484cb820f8..484cb820f8 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/empty_directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/empty_directory.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/file_manipulation.rb index 8a45c83f25..44d6836c10 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/file_manipulation.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/file_manipulation.rb @@ -23,7 +23,7 @@ class Thor source = File.expand_path(find_in_source_paths(source.to_s)) create_file destination, nil, config do - content = File.read(source) + content = File.binread(source) content = block.call(content) if block content end @@ -48,7 +48,7 @@ class Thor # def get(source, destination=nil, config={}, &block) source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\// - render = open(source).read + render = File.binread(source) destination ||= if block_given? block.arity == 1 ? block.call(render) : block.call @@ -80,7 +80,7 @@ class Thor context = instance_eval('binding') create_file destination, nil, config do - content = ERB.new(::File.read(source), nil, '-').result(context) + content = ERB.new(::File.binread(source), nil, '-').result(context) content = block.call(content) if block content end @@ -193,7 +193,7 @@ class Thor say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) unless options[:pretend] - content = File.read(path) + content = File.binread(path) content.gsub!(flag, *args, &block) File.open(path, 'wb') { |file| file.write(content) } end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/inject_into_file.rb index 6b0b42ea02..350ab73862 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/actions/inject_into_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/actions/inject_into_file.rb @@ -11,7 +11,7 @@ class Thor # data<String>:: Data to add to the file. Can be given as a block. # config<Hash>:: give :verbose => false to not log the status and the flag # for injection (:after or :before). - # + # # ==== Examples # # inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n" @@ -90,7 +90,7 @@ class Thor # def replace!(regexp, string) unless base.options[:pretend] - content = File.read(destination) + content = File.binread(destination) content.gsub!(regexp, string) File.open(destination, 'wb') { |file| file.write(content) } end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/base.rb index 700d794123..aae4cdb89c 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/base.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/base.rb @@ -92,6 +92,8 @@ class Thor end module ClassMethods + attr_accessor :debugging + # Adds an argument to the class and creates an attr_accessor for it. # # Arguments are different from options in several aspects. The first one @@ -347,10 +349,11 @@ class Thor # Default way to start generators from the command line. # def start(given_args=ARGV, config={}) + self.debugging = given_args.include?("--debug") config[:shell] ||= Thor::Base.shell.new yield rescue Thor::Error => e - if given_args.include?("--debug") + if debugging raise e else config[:shell].error e.message @@ -361,48 +364,43 @@ class Thor protected # Prints the class options per group. If an option does not belong to - # any group, it uses the ungrouped name value. This method provide to - # hooks to add extra options, one of them if the third argument called - # extra_group that should be a hash in the format :group => Array[Options]. - # - # The second is by returning a lambda used to print values. The lambda - # requires two options: the group name and the array of options. + # any group, it's printed as Class option. # - def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: - groups = {} - + def class_options_help(shell, groups={}) #:nodoc: + # Group options by group class_options.each do |_, value| groups[value.group] ||= [] groups[value.group] << value end - printer = proc do |group_name, options| - list = [] - padding = options.collect{ |o| o.aliases.size }.max.to_i * 4 + # Deal with default group + global_options = groups.delete(nil) || [] + print_options(shell, global_options) + + # Print all others + groups.each do |group_name, options| + print_options(shell, options, group_name) + end + end + + # Receives a set of options and print them. + def print_options(shell, options, group_name=nil) + return if options.empty? - options.each do |option| - item = [ option.usage(padding) ] - item.push(option.description ? "# #{option.description}" : "") + list = [] + padding = options.collect{ |o| o.aliases.size }.max.to_i * 4 - list << item - list << [ "", "# Default: #{option.default}" ] if option.show_default? - end + options.each do |option| + item = [ option.usage(padding) ] + item.push(option.description ? "# #{option.description}" : "") - unless list.empty? - shell.say(group_name ? "#{group_name} options:" : "Options:") - shell.print_table(list, :ident => 2) - shell.say "" - end + list << item + list << [ "", "# Default: #{option.default}" ] if option.show_default? end - # Deal with default group - global_options = groups.delete(nil) || [] - printer.call(ungrouped_name, global_options) if global_options - - # Print all others - groups = extra_group.merge(groups) if extra_group - groups.each(&printer) - printer + shell.say(group_name ? "#{group_name} options:" : "Options:") + shell.print_table(list, :ident => 2) + shell.say "" end # Raises an error if the word given is a Thor reserved word. diff --git a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb new file mode 100644 index 0000000000..d6af7e44b0 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/file_binary_read.rb @@ -0,0 +1,9 @@ +class File #:nodoc: + + unless File.respond_to?(:binread) + def self.binread(file) + File.open(file, 'rb') { |f| f.read } + end + end + +end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/hash_with_indifferent_access.rb index 40d201d9e4..78bc5cf4bf 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -65,7 +65,7 @@ class Thor else self[$1] == args.first end - else + else self[method] end end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/ordered_hash.rb index 27fea5bb35..27fea5bb35 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/core_ext/ordered_hash.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/core_ext/ordered_hash.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/error.rb index f9b31a35d1..f9b31a35d1 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/error.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/error.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb index 021a067a3e..a585b37b73 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/group.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/group.rb @@ -41,16 +41,12 @@ class Thor::Group # ==== Options # short:: When true, shows only usage. # - def help(shell, options={}) - if options[:short] - shell.say banner - else - shell.say "Usage:" - shell.say " #{banner}" - shell.say - class_options_help(shell) - shell.say self.desc if self.desc - end + def help(shell) + shell.say "Usage:" + shell.say " #{banner}\n" + shell.say + class_options_help(shell) + shell.say self.desc if self.desc end # Stores invocations for this class merging with superclass values. @@ -132,7 +128,7 @@ class Thor::Group names.each do |name| unless class_options.key?(name) - raise ArgumentError, "You have to define the option #{name.inspect} " << + raise ArgumentError, "You have to define the option #{name.inspect} " << "before setting invoke_from_option." end @@ -177,15 +173,11 @@ class Thor::Group # Overwrite class options help to allow invoked generators options to be # shown recursively when invoking a generator. # - def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: - group_options = {} - - get_options_from_invocations(group_options, class_options) do |klass| - klass.send(:get_options_from_invocations, group_options, class_options) + def class_options_help(shell, groups={}) #:nodoc: + get_options_from_invocations(groups, class_options) do |klass| + klass.send(:get_options_from_invocations, groups, class_options) end - - group_options.merge!(extra_group) if extra_group - super(shell, ungrouped_name, group_options) + super(shell, groups) end # Get invocations array and merge options from invocations. Those @@ -218,13 +210,26 @@ class Thor::Group end end + # Returns tasks ready to be printed. + def printable_tasks(*) + item = [] + item << banner + item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "") + [item] + end + protected # The banner for this class. You can customize it if you are invoking the # thor class by another ways which is not the Thor::Runner. # def banner - "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}" + "thor #{self_task.formatted_usage(self, false)}" + end + + # Represents the whole class as a task. + def self_task #:nodoc: + Thor::Task::Dynamic.new(self.namespace, class_options) end def baseclass #:nodoc: @@ -243,7 +248,6 @@ class Thor::Group # Shortcut to invoke with padding and block handling. Use internally by # invoke and invoke_from_option class methods. - # def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc: shell.padding += 1 diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/invocation.rb index 32e6a72454..32e6a72454 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/invocation.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/invocation.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser.rb index 57a3f6e1a5..57a3f6e1a5 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/argument.rb index aa8ace4719..aa8ace4719 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/argument.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/argument.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/arguments.rb index fb5d965e06..fb5d965e06 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/arguments.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/arguments.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/option.rb index e09b4901e2..9e40ec73fa 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/option.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/option.rb @@ -36,7 +36,7 @@ class Thor # string (--foo=value) or booleans (just --foo). # # By default all options are optional, unless :required is given. - # + # def self.parse(key, value) if key.is_a?(Array) name, *aliases = key diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/options.rb index 75092308b5..75092308b5 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/parser/options.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/parser/options.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/rake_compat.rb index 0d0757fdda..0d0757fdda 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/rake_compat.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/rake_compat.rb diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/runner.rb index 079f9e0c65..f197081e3f 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/runner.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/runner.rb @@ -36,7 +36,7 @@ class Thor::Runner < Thor #:nodoc: def install(name) initialize_thorfiles - # If a directory name is provided as the argument, look for a 'main.thor' + # If a directory name is provided as the argument, look for a 'main.thor' # task in said directory. begin if File.directory?(File.expand_path(name)) @@ -124,11 +124,7 @@ class Thor::Runner < Thor #:nodoc: method_options :internal => :boolean def installed initialize_thorfiles(nil, true) - - klasses = Thor::Base.subclasses - klasses -= [Thor, Thor::Runner] unless options["internal"] - - display_klasses(true, klasses) + display_klasses(true, options["internal"]) end desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)" @@ -144,11 +140,15 @@ class Thor::Runner < Thor #:nodoc: (options[:all] || k.group == group) && k.namespace =~ search end - display_klasses(false, klasses) + display_klasses(false, false, klasses) end private + def self.banner(task) + "thor " + task.formatted_usage(self, false) + end + def thor_root Thor::Util.thor_root end @@ -156,7 +156,7 @@ class Thor::Runner < Thor #:nodoc: def thor_yaml @thor_yaml ||= begin yaml_file = File.join(thor_root, "thor.yml") - yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) + yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) yaml || {} end end @@ -215,9 +215,6 @@ class Thor::Runner < Thor #:nodoc: # 5. c:\ <-- no Thorfiles found! # def thorfiles(relevant_to=nil, skip_lookup=false) - # TODO Remove this dealing with deprecated thor when :namespaces: is available as constants - save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml) - thorfiles = [] unless skip_lookup @@ -253,47 +250,54 @@ class Thor::Runner < Thor #:nodoc: # Display information about the given klasses. If with_module is given, # it shows a table with information extracted from the yaml file. # - def display_klasses(with_modules=false, klasses=Thor.subclasses) - klasses -= [Thor, Thor::Runner] unless with_modules + def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses) + klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal + raise Error, "No Thor tasks available" if klasses.empty? + show_modules if with_modules && !thor_yaml.empty? - if with_modules && !thor_yaml.empty? - info = [] - labels = ["Modules", "Namespaces"] + # Remove subclasses + klasses.dup.each do |klass| + klasses -= Thor::Util.thor_classes_in(klass) + end - info << labels - info << [ "-" * labels[0].size, "-" * labels[1].size ] + list = Hash.new { |h,k| h[k] = [] } + groups = klasses.select { |k| k.ancestors.include?(Thor::Group) } - thor_yaml.each do |name, hash| - info << [ name, hash[:namespaces].join(", ") ] - end + # Get classes which inherit from Thor + (klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) } - print_table info - say "" - end + # Get classes which inherit from Thor::Base + groups.map! { |k| k.printable_tasks(false).first } + list["root"] = groups - unless klasses.empty? - klasses.dup.each do |klass| - klasses -= Thor::Util.thor_classes_in(klass) - end + # Order namespaces with default coming first + list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') } + list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? } + end - klasses.each { |k| display_tasks(k) } - else - say "\033[1;34mNo Thor tasks available\033[0m" - end + def display_tasks(namespace, list) #:nodoc: + list.sort!{ |a,b| a[0] <=> b[0] } + + say shell.set_color(namespace, :blue, true) + say "-" * namespace.size + + print_table(list, :truncate => true) + say end - # Display tasks from the given Thor class. - # - def display_tasks(klass) - unless klass.tasks.empty? - base = klass.namespace + def show_modules #:nodoc: + info = [] + labels = ["Modules", "Namespaces"] - color = base == "default" ? :magenta : :blue - say shell.set_color(base, color, true) - say "-" * base.length + info << labels + info << [ "-" * labels[0].size, "-" * labels[1].size ] - klass.help(shell, :short => true, :ident => 0, :namespace => true) + thor_yaml.each do |name, hash| + info << [ name, hash[:namespaces].join(", ") ] end + + print_table info + say "" end end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell.rb index 1dc8f0e5b4..64a173de83 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell.rb @@ -22,7 +22,7 @@ class Thor end module Shell - SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table] + SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table] # Add shell to initialize config values. # diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/basic.rb index f6be3575ca..a11f45b4e9 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/basic.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/basic.rb @@ -75,30 +75,6 @@ class Thor !yes?(statement, color) end - # Prints a list of items. - # - # ==== Parameters - # list<Array[String, String, ...]> - # - # ==== Options - # mode:: Can be :rows or :inline. Defaults to :rows. - # ident:: Ident each item with the value given. - # - def print_list(list, options={}) - return if list.empty? - - ident = " " * (options[:ident] || 0) - content = case options[:mode] - when :inline - last = list.pop - "#{list.join(", ")}, and #{last}" - else # rows - ident + list.join("\n#{ident}") - end - - $stdout.puts content - end - # Prints a table. # # ==== Parameters @@ -110,20 +86,26 @@ class Thor def print_table(table, options={}) return if table.empty? - formats = [] + formats, ident = [], options[:ident].to_i + options[:truncate] = terminal_width if options[:truncate] == true + 0.upto(table.first.length - 2) do |i| maxima = table.max{ |a,b| a[i].size <=> b[i].size }[i].size formats << "%-#{maxima + 2}s" end - formats[0] = formats[0].insert(0, " " * options[:ident]) if options[:ident] + formats[0] = formats[0].insert(0, " " * ident) formats << "%s" table.each do |row| + sentence = "" + row.each_with_index do |column, i| - $stdout.print formats[i] % column.to_s + sentence << formats[i] % column.to_s end - $stdout.puts + + sentence = truncate(sentence, options[:truncate]) if options[:truncate] + $stdout.puts sentence end end @@ -214,6 +196,44 @@ HELP base && base.options[:quiet] end + # This code was copied from Rake, available under MIT-LICENSE + # Copyright (c) 2003, 2004 Jim Weirich + def terminal_width + if ENV['THOR_COLUMNS'] + result = ENV['THOR_COLUMNS'].to_i + else + result = unix? ? dynamic_width : 80 + end + (result < 10) ? 80 : result + rescue + 80 + end + + # Calculate the dynamic width of the terminal + def dynamic_width + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty + %x{stty size 2>/dev/null}.split[1].to_i + end + + def dynamic_width_tput + %x{tput cols 2>/dev/null}.to_i + end + + def unix? + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def truncate(string, width) + if string.length <= width + string + else + ( string[0, width-3] || "" ) + "..." + end + end + end end end diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/color.rb index 24704f7885..b2bc66dfba 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/shell/color.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/shell/color.rb @@ -63,7 +63,7 @@ class Thor # def show_diff(destination, content) #:nodoc: if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil? - actual = File.read(destination).to_s.split("\n") + actual = File.binread(destination).to_s.split("\n") content = content.to_s.split("\n") Diff::LCS.sdiff(actual, content).each do |diff| diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/task.rb index 91c7564d3f..5c8877591b 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/task.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/task.rb @@ -1,11 +1,11 @@ class Thor class Task < Struct.new(:name, :description, :usage, :options) + FILE_REGEXP = /^#{Regexp.escape(File.expand_path(__FILE__))}:[\w:]+ `run'$/ # A dynamic task that handles method missing scenarios. - # class Dynamic < Task - def initialize(name) - super(name.to_s, "A dynamically-generated task", name.to_s) + def initialize(name, options=nil) + super(name.to_s, "A dynamically-generated task", name.to_s, options) end def run(instance, args=[]) @@ -25,84 +25,73 @@ class Thor self.options = other.options.dup if other.options end - def short_description - description.split("\n").first if description - end - # By default, a task invokes a method in the thor class. You can change this # implementation to create custom tasks. - # def run(instance, args=[]) raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance) instance.send(name, *args) rescue ArgumentError => e + raise e if instance.class.respond_to?(:debugging) && instance.class.debugging parse_argument_error(instance, e, caller) rescue NoMethodError => e + raise e if instance.class.respond_to?(:debugging) && instance.class.debugging parse_no_method_error(instance, e) end - # Returns the formatted usage. If a class is given, the class arguments are - # injected in the usage. - # - def formatted_usage(klass=nil, namespace=false, show_options=true) - formatted = if namespace.is_a?(String) - "#{namespace}:" - elsif klass && namespace - "#{klass.namespace.gsub(/^default/,'')}:" + # Returns the formatted usage by injecting given required arguments + # and required options into the given usage. + def formatted_usage(klass, namespace=nil) + namespace = klass.namespace if namespace.nil? + + # Add namespace + formatted = if namespace + "#{namespace.gsub(/^(default|thor:runner:)/,'')}:" else "" end - formatted << formatted_arguments(klass) - formatted << " #{formatted_options}" if show_options - formatted.strip! - formatted - end - - # Injects the class arguments into the task usage. - # - def formatted_arguments(klass) - if klass && !klass.arguments.empty? + # Add usage with required arguments + formatted << if klass && !klass.arguments.empty? usage.to_s.gsub(/^#{name}/) do |match| - match << " " << klass.arguments.map{ |a| a.usage }.join(' ') + match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ') end else usage.to_s end - end - # Returns the options usage for this task. - # - def formatted_options - @formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ") + # Add required options + formatted << " #{required_options}" + + # Strip and go! + formatted.strip end protected + def required_options + @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ") + end + # Given a target, checks if this class name is not a private/protected method. - # def public_method?(instance) #:nodoc: collection = instance.private_methods + instance.protected_methods (collection & [name.to_s, name.to_sym]).empty? end - # Clean everything that comes from the Thor gempath and remove the caller. - # - def sans_backtrace(backtrace, caller) #:nodoc: - dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/ - saned = backtrace.reject { |frame| frame =~ dirname } - saned -= caller + # For Ruby <= 1.8.7, we have to match the method name that we are trying to call. + # In Ruby >= 1.9.1, we have to match the method run in this file. + def backtrace_match?(backtrace) #:nodoc: + method_name = /`#{Regexp.escape(name.split(':').last)}'/ + backtrace =~ method_name || backtrace =~ FILE_REGEXP end def parse_argument_error(instance, e, caller) #:nodoc: - backtrace = sans_backtrace(e.backtrace, caller) - - if backtrace.empty? && e.message =~ /wrong number of arguments/ + if e.message =~ /wrong number of arguments/ && backtrace_match?(e.backtrace.first.to_s) if instance.is_a?(Thor::Group) raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?" else raise InvocationError, "'#{name}' was called incorrectly. Call as " << - "'#{formatted_usage(instance.class, true)}'" + "'#{formatted_usage(instance.class)}'" end else raise e diff --git a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/util.rb index ebae0a3193..c2aed89ccf 100644 --- a/railties/lib/rails/vendor/thor-0.12.1/lib/thor/util.rb +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/util.rb @@ -76,8 +76,10 @@ class Thor # Returns the thor classes declared inside the given class. # def self.thor_classes_in(klass) + stringfied_constants = klass.constants.map { |c| c.to_s } Thor::Base.subclasses.select do |subclass| - klass.constants.include?(subclass.name.gsub("#{klass.name}::", '')) + next unless subclass.name + stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", '')) end end @@ -155,7 +157,7 @@ class Thor # inside the sandbox to avoid namespacing conflicts. # def self.load_thorfile(path, content=nil) - content ||= File.read(path) + content ||= File.binread(path) begin Thor::Sandbox.class_eval(content, path) @@ -164,26 +166,6 @@ class Thor end end - # Receives a yaml (hash) and updates all constants entries to namespace. - # This was added to deal with deprecated versions of Thor. - # - # TODO Deprecate this method in the future. - # - # ==== Returns - # TrueClass|FalseClass:: Returns true if any change to the yaml file was made. - # - def self.convert_constants_to_namespaces(yaml) - yaml_changed = false - - yaml.each do |k, v| - next unless v[:constants] && v[:namespaces].nil? - yaml_changed = true - yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)} - end - - yaml_changed - end - def self.user_home @@user_home ||= if ENV["HOME"] ENV["HOME"] diff --git a/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb new file mode 100644 index 0000000000..3c9dd6f808 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.12.3/lib/thor/version.rb @@ -0,0 +1,3 @@ +class Thor + VERSION = "0.12.3".freeze +end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 66ab5a08c3..2d6983076a 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -20,7 +20,7 @@ require 'active_support/core_ext/logger' require 'active_support/test_case' require 'action_controller' -require 'rails' +require 'rails/all' # TODO: Remove these hacks class TestApp < Rails::Application diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index ece41f9de8..adb867ca6d 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -16,6 +16,7 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) build_app boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") end test "the application root is set correctly" do @@ -75,10 +76,11 @@ module ApplicationTests test "the application can be marked as threadsafe when there are no frameworks" do FileUtils.rm_rf("#{app_path}/config/environments") add_to_config <<-RUBY - config.frameworks = [] config.threadsafe! RUBY + use_frameworks [] + assert_nothing_raised do require "#{app_path}/config/application" end @@ -99,5 +101,21 @@ module ApplicationTests assert !ActionController.autoload?(:RecordIdentifier) end + + test "runtime error is raised if config.frameworks= is used" do + add_to_config "config.frameworks = []" + + assert_raises RuntimeError do + require "#{app_path}/config/environment" + end + end + + test "runtime error is raised if config.frameworks is used" do + add_to_config "config.frameworks -= []" + + assert_raises RuntimeError do + require "#{app_path}/config/environment" + end + end end end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 7b27c780aa..0c858d6394 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -14,7 +14,7 @@ module ApplicationTests end def with_config - require "rails" + require "rails/all" require "rails/generators" yield app_const.config end diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb index 42fc416faa..3fd0b0e5df 100644 --- a/railties/test/application/initializer_test.rb +++ b/railties/test/application/initializer_test.rb @@ -7,7 +7,7 @@ module ApplicationTests def setup build_app boot_rails - require "rails" + FileUtils.rm_rf "#{app_path}/config/environments" end test "initializing an application adds the application paths to the load path" do @@ -51,8 +51,9 @@ module ApplicationTests assert_nothing_raised NameError do add_to_config <<-RUBY config.root = "#{app_path}" - config.frameworks = [] RUBY + + use_frameworks [] require "#{app_path}/config/environment" end end @@ -160,6 +161,7 @@ module ApplicationTests def setup build_app boot_rails + FileUtils.rm_rf "#{app_path}/config/environments" end test "database middleware doesn't initialize when activerecord is not in frameworks" do diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb index 8229e83147..b57e829cca 100644 --- a/railties/test/application/notifications_test.rb +++ b/railties/test/application/notifications_test.rb @@ -18,6 +18,7 @@ module ApplicationTests def setup build_app boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") require "active_support/notifications" @events = [] diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 567555fdc5..2cba42551e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -63,10 +63,11 @@ class AppGeneratorTest < GeneratorsTestCase assert_no_file "config/database.yml" end - def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given - run_generator ["--skip-activerecord"] - assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/ - end + # TODO: Bring this back using requires + # def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given + # run_generator ["--skip-activerecord"] + # assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/ + # end def test_prototype_and_test_unit_are_added_by_default run_generator diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb index a2c07ece75..311f19a28a 100644 --- a/railties/test/initializer/check_ruby_version_test.rb +++ b/railties/test/initializer/check_ruby_version_test.rb @@ -19,14 +19,14 @@ module InitializerTests def assert_rails_boots assert_nothing_raised "It appears that rails does not boot" do - require "rails" + require "rails/all" end end def assert_rails_does_not_boot $stderr = File.open("/dev/null", "w") assert_raises(SystemExit) do - require "rails" + require "rails/all" end end end diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb index 3bbf9617a0..bfb1887d11 100644 --- a/railties/test/initializer/path_test.rb +++ b/railties/test/initializer/path_test.rb @@ -7,14 +7,14 @@ module InitializerTests def setup build_app boot_rails - require "rails" + FileUtils.rm_rf("#{app_path}/config/environments") add_to_config <<-RUBY config.root = "#{app_path}" - config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record] config.after_initialize do ActionController::Base.session_store = nil end RUBY + use_frameworks [:action_controller, :action_view, :action_mailer, :active_record] require "#{app_path}/config/environment" @paths = Rails.application.config.paths end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index ee0a812b47..dc5fddb19d 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -207,6 +207,6 @@ Module.new do `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}` File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f| f.puts "require '#{environment}'" if require_environment - f.puts "require 'rails'" + f.puts "require 'rails/all'" end end diff --git a/railties/test/plugins/configuration_test.rb b/railties/test/plugins/configuration_test.rb index 5786316d1d..25bf24eb3b 100644 --- a/railties/test/plugins/configuration_test.rb +++ b/railties/test/plugins/configuration_test.rb @@ -5,27 +5,27 @@ module PluginsTest def setup build_app boot_rails - require "rails" + require "rails/all" end test "config is available to plugins" do - class Foo < Rails::Plugin ; end + class Foo < Rails::Railtie ; end assert_nil Foo.config.action_controller.foo end test "a config name is available for the plugin" do - class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end + class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end assert_equal "hello", Foo.config.foo.greetings end test "plugin configurations are available in the application" do - class Foo < Rails::Plugin ; config.foo.greetings = "hello" ; end + class Foo < Rails::Railtie ; config.foo.greetings = "hello" ; end require "#{app_path}/config/application" assert_equal "hello", AppTemplate::Application.config.foo.greetings end test "plugin config merges are deep" do - class Foo < Rails::Plugin ; config.foo.greetings = 'hello' ; end + class Foo < Rails::Railtie ; config.foo.greetings = 'hello' ; end class MyApp < Rails::Application config.foo.bar = "bar" end diff --git a/railties/test/plugins/framework_extension_test.rb b/railties/test/plugins/framework_extension_test.rb index a6c7b753f8..c920db77aa 100644 --- a/railties/test/plugins/framework_extension_test.rb +++ b/railties/test/plugins/framework_extension_test.rb @@ -7,13 +7,14 @@ module PluginsTest def setup build_app boot_rails - require "rails" + FileUtils.rm_rf("#{app_path}/config/environments") + require "rails/all" end test "rake_tasks block is executed when MyApp.load_tasks is called" do $ran_block = false - class MyPlugin < Rails::Plugin + class MyTie < Rails::Railtie rake_tasks do $ran_block = true end @@ -37,6 +38,7 @@ module PluginsTest def setup build_app boot_rails + FileUtils.rm_rf("#{app_path}/config/environments") end test "active_record extensions are applied to ActiveRecord" do |