diff options
124 files changed, 1608 insertions, 833 deletions
diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 7b03a7f9ff..5ecefe7c09 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,3 +1,5 @@ +require 'tmpdir' + require "active_support/core_ext/class" # Use the old layouts until actionmailer gets refactored require "action_controller/legacy/layout" @@ -224,9 +226,13 @@ module ActionMailer #:nodoc: # * <tt>:location</tt> - The location of the sendmail executable. Defaults to <tt>/usr/sbin/sendmail</tt>. # * <tt>:arguments</tt> - The command line arguments. Defaults to <tt>-i -t</tt>. # + # * <tt>file_settings</tt> - Allows you to override options for the <tt>:file</tt> delivery method. + # * <tt>:location</tt> - The directory into which emails will be written. Defaults to the application <tt>tmp/mails</tt>. + # # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered. # - # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, and <tt>:test</tt>. + # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:test</tt>, + # and <tt>:file</tt>. # # * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are, # but this can be turned off to help functional testing. @@ -279,6 +285,12 @@ module ActionMailer #:nodoc: } cattr_accessor :sendmail_settings + @@file_settings = { + :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + } + + cattr_accessor :file_settings + @@raise_delivery_errors = true cattr_accessor :raise_delivery_errors @@ -499,7 +511,7 @@ module ActionMailer #:nodoc: # ==== # TODO: Revisit this # template_exists = @parts.empty? - # template_exists ||= template_root.find_by_parts("#{mailer_name}/#{@template}") + # template_exists ||= template_root.find("#{mailer_name}/#{@template}") # @body = render_message(@template, @body) if template_exists # Finally, if there are other message parts and a textual body exists, @@ -556,6 +568,7 @@ module ActionMailer #:nodoc: @headers ||= {} @body ||= {} @mime_version = @@default_mime_version.dup if @@default_mime_version + @sent_on ||= Time.now end def render_template(template, body) @@ -585,7 +598,7 @@ module ActionMailer #:nodoc: if file prefix = mailer_name unless file =~ /\// - template = view_paths.find_by_parts(file, {:formats => formats}, prefix) + template = view_paths.find(file, {:formats => formats}, prefix) end layout = _pick_layout(layout, @@ -723,6 +736,14 @@ module ActionMailer #:nodoc: def perform_delivery_test(mail) deliveries << mail end + + def perform_delivery_file(mail) + FileUtils.mkdir_p file_settings[:location] + + (mail.to + mail.cc + mail.bcc).uniq.each do |to| + File.open(File.join(file_settings[:location], to), 'a') { |f| f.write(mail) } + end + end end Base.class_eval do diff --git a/actionmailer/test/delivery_method_test.rb b/actionmailer/test/delivery_method_test.rb index 0731512ea4..1b8c3ba523 100644 --- a/actionmailer/test/delivery_method_test.rb +++ b/actionmailer/test/delivery_method_test.rb @@ -7,6 +7,10 @@ class NonDefaultDeliveryMethodMailer < ActionMailer::Base self.delivery_method = :sendmail end +class FileDeliveryMethodMailer < ActionMailer::Base + self.delivery_method = :file +end + class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase def setup set_delivery_method :smtp @@ -49,3 +53,21 @@ class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end end +class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase + def setup + set_delivery_method :smtp + end + + def teardown + restore_delivery_method + end + + def test_should_be_the_set_delivery_method + assert_equal :file, FileDeliveryMethodMailer.delivery_method + end + + def test_should_default_location_to_the_tmpdir + assert_equal "#{Dir.tmpdir}/mails", ActionMailer::Base.file_settings[:location] + end +end + diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index f2a8c2303c..008ca498b1 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -18,7 +18,6 @@ class TestMailer < ActionMailer::Base @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @from = "system@loudthinking.com" - @sent_on = Time.local(2004, 12, 12) @body["recipient"] = recipient end @@ -357,12 +356,14 @@ class ActionMailerTest < Test::Unit::TestCase end def test_signed_up + Time.stubs(:now => Time.now) + expected = new_mail expected.to = @recipient expected.subject = "[Signed up] Welcome #{@recipient}" expected.body = "Hello there, \n\nMr. #{@recipient}" expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) + expected.date = Time.now created = nil assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) } @@ -888,6 +889,18 @@ EOF assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0] end + def test_file_delivery_should_create_a_file + ActionMailer::Base.delivery_method = :file + tmp_location = ActionMailer::Base.file_settings[:location] + + TestMailer.deliver_cc_bcc(@recipient) + assert File.exists? tmp_location + assert File.directory? tmp_location + assert File.exists? File.join(tmp_location, @recipient) + assert File.exists? File.join(tmp_location, 'nobody@loudthinking.com') + assert File.exists? File.join(tmp_location, 'root@loudthinking.com') + end + def test_recursive_multipart_processing fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7") mail = TMail::Mail.parse(fixture) diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb index 422c4c3298..6714185172 100644 --- a/actionpack/examples/very_simple.rb +++ b/actionpack/examples/very_simple.rb @@ -6,7 +6,7 @@ require "action_controller" class Kaigi < ActionController::Metal include AbstractController::Callbacks include ActionController::RackConvenience - include ActionController::Renderer + include ActionController::RenderingController include ActionController::Layouts include ActionView::Context @@ -47,4 +47,4 @@ app = Rack::Builder.new do map("/kaigi/alt") { run Kaigi.action(:alt) } end.to_app -Rack::Handler::Mongrel.run app, :Port => 3000
\ No newline at end of file +Rack::Handler::Mongrel.run app, :Port => 3000 diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index f020abaa45..cdeb55b915 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -2,15 +2,15 @@ require "active_support/core_ext/module/attr_internal" require "active_support/core_ext/module/delegation" module AbstractController - autoload :Base, "abstract_controller/base" - autoload :Benchmarker, "abstract_controller/benchmarker" - autoload :Callbacks, "abstract_controller/callbacks" - autoload :Helpers, "abstract_controller/helpers" - autoload :Layouts, "abstract_controller/layouts" - autoload :Logger, "abstract_controller/logger" - autoload :Renderer, "abstract_controller/renderer" + autoload :Base, "abstract_controller/base" + autoload :Benchmarker, "abstract_controller/benchmarker" + autoload :Callbacks, "abstract_controller/callbacks" + autoload :Helpers, "abstract_controller/helpers" + autoload :Layouts, "abstract_controller/layouts" + autoload :Logger, "abstract_controller/logger" + autoload :RenderingController, "abstract_controller/rendering_controller" # === Exceptions - autoload :ActionNotFound, "abstract_controller/exceptions" - autoload :DoubleRenderError, "abstract_controller/exceptions" - autoload :Error, "abstract_controller/exceptions" + autoload :ActionNotFound, "abstract_controller/exceptions" + autoload :DoubleRenderError, "abstract_controller/exceptions" + autoload :Error, "abstract_controller/exceptions" end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 5efa37fde3..04eaa02441 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -2,7 +2,7 @@ module AbstractController module Helpers extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_helpers) { Module.new } diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 038598a3b3..0063d54149 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -2,7 +2,7 @@ module AbstractController module Layouts extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_layout_conditions) { Hash.new } @@ -76,7 +76,7 @@ module AbstractController when nil self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def _layout(details) - if view_paths.find_by_parts?("#{_implied_layout_name}", details, "layouts") + if view_paths.exists?("#{_implied_layout_name}", details, "layouts") "#{_implied_layout_name}" else super @@ -89,16 +89,18 @@ module AbstractController end def render_to_body(options = {}) + # In the case of a partial with a layout, handle the layout + # here, and make sure the view does not try to handle it + layout = options.delete(:layout) if options.key?(:partial) + response = super - if options.key?(:partial) - # This is a little bit messy. We need to explicitly handle partial - # layouts here since the core lookup logic is in the view, but - # we need to determine the layout based on the controller - if options.key?(:layout) - layout = _layout_for_option(options[:layout], options[:_template].details) - response = layout.render(view_context, options[:locals]) { response } - end + # This is a little bit messy. We need to explicitly handle partial + # layouts here since the core lookup logic is in the view, but + # we need to determine the layout based on the controller + if layout + layout = _layout_for_option(layout, options[:_template].details) + response = layout.render(view_context, options[:locals] || {}) { response } end response @@ -131,7 +133,7 @@ module AbstractController def _find_layout(name, details) # TODO: Make prefix actually part of details in ViewPath#find_by_parts prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - view_paths.find_by_parts(name, details, prefix) + view_paths.find(name, details, prefix) end # Returns the default layout for this controller and a given set of details. diff --git a/actionpack/lib/abstract_controller/renderer.rb b/actionpack/lib/abstract_controller/rendering_controller.rb index da57d0ff4d..bb7891fbfd 100644 --- a/actionpack/lib/abstract_controller/renderer.rb +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -1,7 +1,7 @@ require "abstract_controller/logger" module AbstractController - module Renderer + module RenderingController extend ActiveSupport::Concern include AbstractController::Logger @@ -67,7 +67,7 @@ module AbstractController # # :api: plugin def render_to_string(options = {}) - AbstractController::Renderer.body_to_s(render_to_body(options)) + AbstractController::RenderingController.body_to_s(render_to_body(options)) end # Renders the template from an object. @@ -111,7 +111,7 @@ module AbstractController def _determine_template(options) name = (options[:_template_name] || action_name).to_s - options[:_template] ||= view_paths.find_by_parts( + options[:_template] ||= view_paths.find( name, { :formats => formats }, options[:_prefix], options[:_partial] ) end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 22c2c4f499..37ff618edd 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -7,10 +7,10 @@ module ActionController autoload :RackConvenience, "action_controller/metal/rack_convenience" autoload :Rails2Compatibility, "action_controller/metal/compatibility" autoload :Redirector, "action_controller/metal/redirector" - autoload :Renderer, "action_controller/metal/renderer" + autoload :RenderingController, "action_controller/metal/rendering_controller" autoload :RenderOptions, "action_controller/metal/render_options" - autoload :Renderers, "action_controller/metal/render_options" autoload :Rescue, "action_controller/metal/rescuable" + autoload :Responder, "action_controller/metal/responder" autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" autoload :Session, "action_controller/metal/session" @@ -69,4 +69,4 @@ require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/name_error' -require 'active_support/inflector'
\ No newline at end of file +require 'active_support/inflector' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9d9f735e27..61f1c715c8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -10,8 +10,8 @@ module ActionController include ActionController::HideActions include ActionController::UrlFor include ActionController::Redirector - include ActionController::Renderer - include ActionController::Renderers::All + include ActionController::RenderingController + include ActionController::RenderOptions::All include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackConvenience @@ -51,7 +51,7 @@ module ActionController def method_for_action(action_name) super || begin - if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) + if view_paths.exists?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) "default_render" end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 95cba0e411..4ef600bea0 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -36,8 +36,8 @@ module ActionController #:nodoc: def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: if perform_caching - if cache = read_fragment(name, options) - buffer.concat(cache) + if fragment_exist?(name,options) + buffer.concat(read_fragment(name, options)) else pos = buffer.length block.call diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb index 3f3d20b95f..43aea0eba2 100644 --- a/actionpack/lib/action_controller/legacy/layout.rb +++ b/actionpack/lib/action_controller/legacy/layout.rb @@ -191,7 +191,7 @@ module ActionController #:nodoc: def memoized_find_layout(layout, formats) #:nodoc: return layout if layout.nil? || layout.respond_to?(:render) prefix = layout.to_s =~ /layouts\// ? nil : "layouts" - view_paths.find_by_parts(layout.to_s, {:formats => formats}, prefix) + view_paths.find(layout.to_s, {:formats => formats}, prefix) end def find_layout(*args) diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 6d35137428..8575d30335 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -55,14 +55,15 @@ module ActionController elsif args.empty? raise ArgumentError, "too few arguments to head" end - options = args.extract_options! - status = args.shift || options.delete(:status) || :ok + options = args.extract_options! + status = args.shift || options.delete(:status) || :ok + location = options.delete(:location) options.each do |key, value| headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s end - render :nothing => true, :status => status + render :nothing => true, :status => status, :location => location end # Sets the etag and/or last_modified on the response and checks it against diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index 365351b421..cac529b1ae 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -158,7 +158,7 @@ module ActionController module Layouts extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController include AbstractController::Layouts module ClassMethods diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index f4a4007a43..c8d042acb5 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -177,106 +177,67 @@ module ActionController #:nodoc: # Be sure to check respond_with and respond_to documentation for more examples. # def respond_to(*mimes, &block) - options = mimes.extract_options! raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - resource = options.delete(:with) - responder = Responder.new - + collector = Collector.new mimes = collect_mimes_from_class_level if mimes.empty? - mimes.each { |mime| responder.send(mime) } - block.call(responder) if block_given? + mimes.each { |mime| collector.send(mime) } + block.call(collector) if block_given? + + if format = request.negotiate_mime(collector.order) + self.formats = [format.to_sym] - if format = request.negotiate_mime(responder.order) - respond_to_block_or_template_or_resource(format, resource, - options, &responder.response_for(format)) + if response = collector.response_for(format) + response.call + else + default_render + end else head :not_acceptable end end - # respond_with allows you to respond an action with a given resource. It - # requires that you set your class with a :respond_to method with the - # formats allowed: + # respond_with wraps a resource around a responder for default representation. + # First it invokes respond_to, if a response cannot be found (ie. no block + # for the request was given and template was not available), it instantiates + # an ActionController::Responder with the controller and resource. # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json + # ==== Example # - # def index - # @people = Person.find(:all) - # respond_with(@person) - # end + # def index + # @users = User.all + # respond_with(@users) # end # - # When a request comes with format :xml, the respond_with will first search - # for a template as person/index.xml, if the template is not available, it - # will see if the given resource responds to :to_xml. - # - # If neither are available, it will raise an error. + # It also accepts a block to be given. It's used to overwrite a default + # response: # - # Extra parameters given to respond_with are used when :to_format is invoked. - # This allows you to set status and location for several formats at the same - # time. Consider this restful controller response on create for both xml - # and json formats: + # def destroy + # @user = User.find(params[:id]) + # flash[:notice] = "User was successfully created." if @user.save # - # class PeopleController < ApplicationController - # respond_to :xml, :json - # - # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # respond_with(@person, :status => :ok, :location => person_url(@person)) - # else - # respond_with(@person.errors, :status => :unprocessable_entity) - # end + # respond_with(@user) do |format| + # format.html { render } # end # end # - # Finally, respond_with also accepts blocks, as in respond_to. Let's take - # the same controller and create action above and add common html behavior: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def create - # @person = Person.new(params[:person]) - # - # if @person.save - # options = { :status => :ok, :location => person_url(@person) } - # - # respond_with(@person, options) do |format| - # format.html { redirect_to options[:location] } - # end - # else - # respond_with(@person.errors, :status => :unprocessable_entity) do - # format.html { render :action => :new } - # end - # end - # end - # end + # All options given to respond_with are sent to the underlying responder, + # except for the option :responder itself. Since the responder interface + # is quite simple (it just needs to respond to call), you can even give + # a proc to it. # def respond_with(resource, options={}, &block) - respond_to(options.merge!(:with => resource), &block) + respond_to(&block) + rescue ActionView::MissingTemplate + (options.delete(:responder) || responder).call(self, resource, options) end - protected - - def respond_to_block_or_template_or_resource(format, resource, options) - self.formats = [format.to_sym] - return yield if block_given? - - begin - default_render - rescue ActionView::MissingTemplate => e - if resource && resource.respond_to?(:"to_#{format.to_sym}") - render options.merge(format.to_sym => resource) - else - raise e - end - end + def responder + ActionController::Responder end + protected + # Collect mimes declared in the class method respond_to valid for the # current action. # @@ -296,7 +257,7 @@ module ActionController #:nodoc: end end - class Responder #:nodoc: + class Collector #:nodoc: attr_accessor :order def initialize diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb index c6a965472f..0d69ca10df 100644 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -47,7 +47,7 @@ module ActionController end end - module Renderers + module RenderOptions module Json extend RenderOption register_renderer :json @@ -94,10 +94,10 @@ module ActionController module All extend ActiveSupport::Concern - include ActionController::Renderers::Json - include ActionController::Renderers::Js - include ActionController::Renderers::Xml - include ActionController::Renderers::RJS + include ActionController::RenderOptions::Json + include ActionController::RenderOptions::Js + include ActionController::RenderOptions::Xml + include ActionController::RenderOptions::RJS end end end diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb index 31ba7e582a..5b1be763ad 100644 --- a/actionpack/lib/action_controller/metal/renderer.rb +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -1,8 +1,8 @@ module ActionController - module Renderer + module RenderingController extend ActiveSupport::Concern - include AbstractController::Renderer + include AbstractController::RenderingController def process_action(*) self.formats = request.formats.map {|x| x.to_sym} @@ -53,9 +53,6 @@ module ActionController super end - def _render_partial(partial, options) - end - def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb new file mode 100644 index 0000000000..9ed99ca623 --- /dev/null +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -0,0 +1,181 @@ +module ActionController #:nodoc: + # Responder is responsible to expose a resource for different mime requests, + # usually depending on the HTTP verb. The responder is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@people) + # end + # end + # + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will create a responder, passing + # the controller and the resource and invoke :to_xml on it; + # + # 3) if the responder does not respond_to :to_xml, call to_format on it. + # + # === Builtin HTTP verb semantics + # + # Rails default responder holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using Rails default responder, a POST request for creating an object could + # be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user, :status => :created, :location => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } + # end + # end + # end + # + # The same happens for PUT and DELETE requests. + # + # === Nested resources + # + # You can given nested resource as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with([@project, @task]) + # end + # + # Giving an array of resources, you ensure that the responder will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Check polymorphic_url documentation for more examples. + # + class Responder + attr_reader :controller, :request, :format, :resource, :resource_location, :options + + def initialize(controller, resource, options={}) + @controller = controller + @request = controller.request + @format = controller.formats.first + @resource = resource.is_a?(Array) ? resource.last : resource + @resource_location = options[:location] || resource + @options = options + end + + delegate :head, :render, :redirect_to, :to => :controller + delegate :get?, :post?, :put?, :delete?, :to => :request + + # Undefine :to_json since it's defined on Object + undef_method :to_json + + # Initializes a new responder an invoke the proper format. If the format is + # not defined, call to_format. + # + def self.call(*args) + responder = new(*args) + method = :"to_#{responder.format}" + responder.respond_to?(method) ? responder.send(method) : responder.to_format + end + + # HTML format does not render the resource, it always attempt to render a + # template. + # + def to_html + if get? + render + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end + end + + # All others formats try to render the resource given instead. For this + # purpose a helper called display as a shortcut to render a resource with + # the current format. + # + def to_format + return render unless resourceful? + + if get? + display resource + elsif has_errors? + display resource.errors, :status => :unprocessable_entity + elsif post? + display resource, :status => :created, :location => resource_location + else + head :ok + end + end + + protected + + # Checks whether the resource responds to the current format or not. + # + def resourceful? + resource.respond_to?(:"to_#{format}") + end + + # display is just a shortcut to render a resource with the current format. + # + # display @user, :status => :ok + # + # For xml request is equivalent to: + # + # render :xml => @user, :status => :ok + # + # Options sent by the user are also used: + # + # respond_with(@user, :status => :created) + # display(@user, :status => :ok) + # + # Results in: + # + # render :xml => @user, :status => :created + # + def display(resource, given_options={}) + render given_options.merge!(options).merge!(format => resource) + end + + # Check if the resource has errors or not. + # + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end + + # By default, render the :edit action for html requests with failure, unless + # the verb is post. + # + def default_action + request.post? ? :new : :edit + end + end +end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index f0317c6e99..57318e8747 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -6,7 +6,7 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index 951ae1bee1..d3d78e3749 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -2,7 +2,7 @@ module ActionController #:nodoc: module Verification #:nodoc: extend ActiveSupport::Concern - include AbstractController::Callbacks, Session, Flash, Renderer + include AbstractController::Callbacks, Session, Flash, RenderingController # This module provides a class-level method for specifying that certain # actions are guarded against being called without certain prerequisites @@ -127,4 +127,4 @@ module ActionController #:nodoc: end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb index 159d869a54..2adf3575a7 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb @@ -50,6 +50,7 @@ module ActionController # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" # # ==== Options # @@ -70,6 +71,9 @@ module ActionController # record = Comment.new # polymorphic_url(record) # same as comments_url() # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() + # def polymorphic_url(record_or_hash_or_array, options = {}) if record_or_hash_or_array.kind_of?(Array) record_or_hash_or_array = record_or_hash_or_array.compact @@ -86,17 +90,19 @@ module ActionController else [ record_or_hash_or_array ] end - inflection = - case - when options[:action].to_s == "new" - args.pop - :singular - when record.respond_to?(:new_record?) && record.new_record? - args.pop - :plural - else - :singular - end + inflection = if options[:action].to_s == "new" + args.pop + :singular + elsif (record.respond_to?(:new_record?) && record.new_record?) || + (record.respond_to?(:destroyed?) && record.destroyed?) + args.pop + :plural + elsif record.is_a?(Class) + args.pop + :plural + else + :singular + end args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 9e696af83b..7932f01ebb 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -202,7 +202,7 @@ module ActionView #:nodoc: delegate :logger, :to => :controller, :allow_nil => true - delegate :find_by_parts, :to => :view_paths + delegate :find, :to => :view_paths include Context diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 4fd7f7d83c..3e6e62237d 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -278,9 +278,7 @@ module ActionView end %w(tag content_tag to_date_select_tag to_datetime_select_tag to_time_select_tag).each do |meth| - define_method meth do |*| - error_wrapping(super) - end + module_eval "def #{meth}(*) error_wrapping(super) end" end def error_wrapping(html_tag) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e126b35e90..1abe7775e0 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -263,7 +263,7 @@ module ActionView escape = options.key?("escape") ? options.delete("escape") : true content = html_escape(content) if escape - content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) + content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options) end # Creates a check box form input tag. diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 999d5b34fc..897a7cc348 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -215,7 +215,7 @@ module ActionView delimiter ||= (options[:delimiter] || defaults[:delimiter]) begin - rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision + rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision number_with_delimiter("%01.#{precision}f" % rounded_number, :separator => separator, :delimiter => delimiter) diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 074b475819..4001757a9b 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -33,12 +33,12 @@ module ActionView #:nodoc: super(*objs.map { |obj| self.class.type_cast(obj) }) end - def find_by_parts(path, details = {}, prefix = nil, partial = false) + def find(path, details = {}, prefix = nil, partial = false) # template_path = path.sub(/^\//, '') template_path = path each do |load_path| - if template = load_path.find_by_parts(template_path, details, prefix, partial) + if template = load_path.find(template_path, details, prefix, partial) return template end end @@ -48,11 +48,11 @@ module ActionView #:nodoc: raise ActionView::MissingTemplate.new(self, "#{prefix}/#{path} - #{details.inspect} - partial: #{!!partial}") end - def find_by_parts?(path, extension = nil, prefix = nil, partial = false) + def exists?(path, extension = nil, prefix = nil, partial = false) template_path = path.sub(/^\//, '') each do |load_path| - return true if template = load_path.find_by_parts(template_path, extension, prefix, partial) + return true if template = load_path.find(template_path, extension, prefix, partial) end false end @@ -62,7 +62,7 @@ module ActionView #:nodoc: template_path = original_template_path.sub(/^\//, '') each do |load_path| - if template = load_path.find_by_parts(template_path, format) + if template = load_path.find(template_path, format) return template # Try to find html version if the format is javascript elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"] diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 48cba9c02b..986d3af454 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -170,95 +170,117 @@ module ActionView # <%- end -%> # <% end %> module Partials - extend ActiveSupport::Memoizable extend ActiveSupport::Concern - included do - attr_accessor :_partial - end - - module ClassMethods - def _partial_names - @_partial_names ||= ActiveSupport::ConcurrentHash.new + class PartialRenderer + def self.partial_names + @partial_names ||= Hash.new {|h,k| h[k] = ActiveSupport::ConcurrentHash.new } end - end - def render_partial(options) - @assigns_added = false - # TODO: Handle other details here. - self.formats = options[:_details][:formats] - _render_partial(options) - end + def initialize(view_context, options, formats) + object = options[:partial] - def _render_partial(options, &block) #:nodoc: - options[:locals] ||= {} + @view, @formats = view_context, formats + @options = options || {} - path = partial = options[:partial] + if object.is_a?(String) + @path = object + elsif + @object = object + @path = partial_path unless collection + end - if partial.respond_to?(:to_ary) - return _render_partial_collection(partial, options, &block) - elsif !partial.is_a?(String) - options[:object] = object = partial - path = _partial_path(object) + @locals = options[:locals] || {} + @object ||= @options[:object] || @locals[:object] + @layout = options[:layout] end - _render_partial_object(_pick_partial_template(path), options, &block) - end + def render(&block) + template = find if @path - private - def _partial_path(object) - self.class._partial_names[[controller.class, object.class]] ||= begin - name = object.class.model_name - if controller_path && controller_path.include?("/") - File.join(File.dirname(controller_path), name.partial_path) - else - name.partial_path - end + if collection + render_collection(template, &block) + else + render_object(template, &block) end end - def _render_partial_template(template, locals, object, options = {}, &block) - options[:_template] = template - locals[:object] = locals[template.variable_name] = object - locals[options[:as]] = object if options[:as] + def render_template(template, &block) + @options[:_template] = template + @locals[:object] = @locals[template.variable_name] = @object + @locals[@options[:as]] = @object if @options[:as] - _render_single_template(template, locals, &block) + content = @view._render_single_template(template, @locals, &block) + return content if block_given? || !@layout + find(@layout).render(@view, @locals) { content } end - def _render_partial_object(template, options, &block) - if options.key?(:collection) - _render_partial_collection(options.delete(:collection), options, template, &block) - else - locals = (options[:locals] ||= {}) - object = options[:object] || locals[:object] || locals[template.variable_name] - - _render_partial_template(template, locals, object, options, &block) - end + def render_object(template, &block) + @object ||= @locals[template.variable_name] + render_template(template, &block) end - def _render_partial_collection(collection, options = {}, template = nil, &block) #:nodoc: - options[:_template] ||= template + def render_collection(passed_template = nil, &block) + @options[:_template] = passed_template return nil if collection.blank? - if options.key?(:spacer_template) - spacer = _render_partial(:partial => options[:spacer_template]) + if @options.key?(:spacer_template) + spacer = @view.render_partial( + :partial => @options[:spacer_template], :_details => @options[:_details]) end - locals, index = options[:locals] || {}, 0 + index = 0 - collection.map do |object| - tmp = template || _pick_partial_template(_partial_path(object)) - locals[tmp.counter_name] = index + collection.map do |@object| + @path = partial_path + template = passed_template || find + @locals[template.counter_name] = index index += 1 - _render_partial_template(tmp, locals, object, options, &block) + render_template(template, &block) end.join(spacer) end - def _pick_partial_template(partial_path) #:nodoc: - prefix = controller_path unless partial_path.include?(?/) - find_by_parts(partial_path, {:formats => formats}, prefix, true) + private + def collection + @collection ||= if @object.respond_to?(:to_ary) + @object + elsif @options.key?(:collection) + @options[:collection] || [] + end end + + def find(path = @path) + prefix = @view.controller.controller_path unless path.include?(?/) + @view.find(path, {:formats => @view.formats}, prefix, true) + end + + def partial_path(object = @object) + self.class.partial_names[@view.controller.class][object.class] ||= begin + return nil unless object.class.respond_to?(:model_name) + + name = object.class.model_name + path = @view.controller_path + if path && path.include?(?/) + File.join(File.dirname(path), name.partial_path) + else + name.partial_path + end + end + end + end + + def render_partial(options) + @assigns_added = false + # TODO: Handle other details here. + self.formats = options[:_details][:formats] if options[:_details] + _render_partial(options) + end + + def _render_partial(options, &block) #:nodoc: + PartialRenderer.new(self, options, formats).render(&block) + end + end end diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index 9c25fab6bb..c7afc56e3b 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -20,14 +20,13 @@ module ActionView if block_given? return concat(_render_partial(options.merge(:partial => layout), &block)) elsif options.key?(:partial) - layout = _pick_partial_template(layout) if layout - return _render_content(_render_partial(options), layout, options[:locals]) + return _render_partial(options) end - layout = find_by_parts(layout, {:formats => formats}) if layout + layout = find(layout, {:formats => formats}) if layout if file = options[:file] - template = find_by_parts(file, {:formats => formats}) + template = find(file, {:formats => formats}) _render_template(template, layout, :locals => options[:locals] || {}) elsif inline = options[:inline] _render_inline(inline, layout, options) diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index d15f53a11b..ebfc6cc8ce 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -10,7 +10,7 @@ module ActionView end # Normalizes the arguments and passes it on to find_template - def find_by_parts(*args) + def find(*args) find_all_by_parts(*args).first end diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb index 56ec6a6a31..9438a4dfc9 100644 --- a/actionpack/test/abstract_controller/abstract_controller_test.rb +++ b/actionpack/test/abstract_controller/abstract_controller_test.rb @@ -27,7 +27,7 @@ module AbstractController # Test Render mixin # ==== class RenderingController < AbstractController::Base - include Renderer + include ::AbstractController::RenderingController def _prefix() end @@ -65,8 +65,8 @@ module AbstractController self.response_body = render_to_string :_template_name => "naked_render.erb" end end - - class TestRenderer < ActiveSupport::TestCase + + class TestRenderingController < ActiveSupport::TestCase test "rendering templates works" do result = Me2.new.process(:index) assert_equal "Hello from index.erb", result.response_body @@ -139,10 +139,10 @@ module AbstractController private def self.layout(formats) begin - view_paths.find_by_parts(name.underscore, {:formats => formats}, "layouts") + view_paths.find(name.underscore, {:formats => formats}, "layouts") rescue ActionView::MissingTemplate begin - view_paths.find_by_parts("application", {:formats => formats}, "layouts") + view_paths.find("application", {:formats => formats}, "layouts") rescue ActionView::MissingTemplate end end diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb index 0a2535f834..e9a60c0307 100644 --- a/actionpack/test/abstract_controller/helper_test.rb +++ b/actionpack/test/abstract_controller/helper_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include Renderer + include AbstractController::RenderingController include Helpers def render(string) @@ -40,4 +40,4 @@ module AbstractController end end -end
\ No newline at end of file +end diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb index b28df7743f..42f73faa61 100644 --- a/actionpack/test/abstract_controller/layouts_test.rb +++ b/actionpack/test/abstract_controller/layouts_test.rb @@ -6,7 +6,7 @@ module AbstractControllerTests # Base controller for these tests class Base < AbstractController::Base - include AbstractController::Renderer + include AbstractController::RenderingController include AbstractController::Layouts self.view_paths = [ActionView::FixtureResolver.new( diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 2036d1eeb5..37f1f6dff8 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -45,6 +45,12 @@ class PolymorphicRoutesTest < ActionController::TestCase assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(@project) end end + + def test_with_class + with_test_routes do + assert_equal "http://example.com/projects", polymorphic_url(@project.class) + end + end def test_with_new_record with_test_routes do @@ -52,6 +58,13 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_destroyed_record + with_test_routes do + @project.destroy + assert_equal "http://example.com/projects", polymorphic_url(@project) + end + end + def test_with_record_and_action with_test_routes do assert_equal "http://example.com/projects/new", polymorphic_url(@project, :action => 'new') @@ -129,6 +142,27 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_nested_destroyed + with_test_routes do + @project.save + @task.destroy + assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task]) + end + end + + def test_with_nested_class + with_test_routes do + @project.save + assert_equal "http://example.com/projects/#{@project.id}/tasks", polymorphic_url([@project, @task.class]) + end + end + + def test_class_with_array_and_namespace + with_admin_test_routes do + assert_equal "http://example.com/admin/projects", polymorphic_url([:admin, @project.class]) + end + end + def test_new_with_array_and_namespace with_admin_test_routes do assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], :action => 'new') @@ -252,11 +286,24 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_irregular_plural_class + with_test_routes do + assert_equal "http://example.com/taxes", polymorphic_url(@tax.class) + end + end + def test_with_irregular_plural_new_record with_test_routes do assert_equal "http://example.com/taxes", polymorphic_url(@tax) end end + + def test_with_irregular_plural_destroyed_record + with_test_routes do + @tax.destroy + assert_equal "http://example.com/taxes", polymorphic_url(@tax) + end + end def test_with_irregular_plural_record_and_action with_test_routes do @@ -298,6 +345,12 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_class_with_irregular_plural_array_and_namespace + with_admin_test_routes do + assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax.class]) + end + end + def test_unsaved_with_irregular_plural_array_and_namespace with_admin_test_routes do assert_equal "http://example.com/admin/taxes", polymorphic_url([:admin, @tax]) @@ -395,4 +448,4 @@ class PolymorphicRoutesTest < ActionController::TestCase end end -end
\ No newline at end of file +end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index c286976315..68529cc8f7 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -625,6 +625,21 @@ class FragmentCachingTest < ActionController::TestCase assert !fragment_computed assert_equal 'generated till now -> fragment content', buffer end + + def test_fragment_for_logging + fragment_computed = false + + @controller.class.expects(:benchmark).with('Cached fragment exists?: views/expensive') + @controller.class.expects(:benchmark).with('Cached fragment miss: views/expensive') + @controller.class.expects(:benchmark).with('Cached fragment hit: views/expensive').never + + buffer = 'generated till now -> ' + @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } + + assert fragment_computed + assert_equal 'generated till now -> ', buffer + end + end class FunctionalCachingController < ActionController::Base diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 117f4ea4f0..8319b5c573 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'controller/fake_models' class RespondToController < ActionController::Base layout :set_layout @@ -471,22 +472,10 @@ class RespondToControllerTest < ActionController::TestCase end end -class RespondResource - undef_method :to_json - - def to_xml - "XML" - end - - def to_js - "JS" - end -end - class RespondWithController < ActionController::Base respond_to :html, :json respond_to :xml, :except => :using_defaults - respond_to :js, :only => :using_defaults + respond_to :js, :only => [ :using_defaults, :using_resource ] def using_defaults respond_to do |format| @@ -498,20 +487,27 @@ class RespondWithController < ActionController::Base respond_to(:js, :xml) end + def default_overwritten + respond_to do |format| + format.html { render :text => "HTML" } + end + end + def using_resource - respond_with(RespondResource.new) + respond_with(Customer.new("david", 13)) end - def using_resource_with_options - respond_with(RespondResource.new, :status => :unprocessable_entity) do |format| - format.js - end + def using_resource_with_parent + respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) end - def default_overwritten - respond_to do |format| - format.html { render :text => "HTML" } - end + def using_resource_with_status_and_location + respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created) + end + + def using_resource_with_responder + responder = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } + respond_with(Customer.new("david", 13), :responder => responder) end protected @@ -527,7 +523,7 @@ class InheritedRespondWithController < RespondWithController respond_to :xml, :json def index - respond_with(RespondResource.new) do |format| + respond_with(Customer.new("david", 13)) do |format| format.json { render :text => "JSON" } end end @@ -540,6 +536,11 @@ class RespondWithControllerTest < ActionController::TestCase super ActionController::Base.use_accept_header = true @request.host = "www.example.com" + + ActionController::Routing::Routes.draw do |map| + map.resources :customers + map.resources :quiz_stores, :has_many => :customers + end end def teardown @@ -576,11 +577,17 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "<p>Hello world!</p>\n", @response.body end + def test_default_overwritten + get :default_overwritten + assert_equal "text/html", @response.content_type + assert_equal "HTML", @response.body + end + def test_using_resource - @request.accept = "text/html" + @request.accept = "text/javascript" get :using_resource - assert_equal "text/html", @response.content_type - assert_equal "Hello world!", @response.body + assert_equal "text/javascript", @response.content_type + assert_equal '$("body").visualEffect("highlight");', @response.body @request.accept = "application/xml" get :using_resource @@ -593,24 +600,114 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_with_options + def test_using_resource_for_post_with_html + post :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "http://www.example.com/customers/13", @response.location + assert @response.redirect? + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "New world!\n", @response.body + assert_nil @response.location + end + + def test_using_resource_for_post_with_xml @request.accept = "application/xml" - get :using_resource_with_options + + post :using_resource assert_equal "application/xml", @response.content_type - assert_equal 422, @response.status + assert_equal 201, @response.status assert_equal "XML", @response.body + assert_equal "http://www.example.com/customers/13", @response.location - @request.accept = "text/javascript" - get :using_resource_with_options - assert_equal "text/javascript", @response.content_type + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "application/xml", @response.content_type assert_equal 422, @response.status - assert_equal "JS", @response.body + assert_equal errors.to_xml, @response.body + assert_nil @response.location end - def test_default_overwritten - get :default_overwritten + def test_using_resource_for_put_with_html + put :using_resource assert_equal "text/html", @response.content_type - assert_equal "HTML", @response.body + assert_equal 302, @response.status + assert_equal "http://www.example.com/customers/13", @response.location + assert @response.redirect? + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + put :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "Edit world!\n", @response.body + assert_nil @response.location + end + + def test_using_resource_for_put_with_xml + @request.accept = "application/xml" + + put :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal " ", @response.body + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + put :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal errors.to_xml, @response.body + assert_nil @response.location + end + + def test_using_resource_for_delete_with_html + Customer.any_instance.stubs(:destroyed?).returns(true) + delete :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "http://www.example.com/customers", @response.location + end + + def test_using_resource_for_delete_with_xml + Customer.any_instance.stubs(:destroyed?).returns(true) + @request.accept = "application/xml" + delete :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal " ", @response.body + end + + def test_using_resource_with_parent_for_get + @request.accept = "application/xml" + get :using_resource_with_parent + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_equal "XML", @response.body + end + + def test_using_resource_with_parent_for_post + @request.accept = "application/xml" + + post :using_resource_with_parent + assert_equal "application/xml", @response.content_type + assert_equal 201, @response.status + assert_equal "XML", @response.body + assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + post :using_resource + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal errors.to_xml, @response.body + assert_nil @response.location end def test_clear_respond_to @@ -628,6 +725,29 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "XML", @response.body end + def test_no_double_render_is_raised + @request.accept = "text/html" + assert_raise ActionView::MissingTemplate do + get :using_resource + end + end + + def test_using_resource_with_status_and_location + @request.accept = "text/html" + post :using_resource_with_status_and_location + assert @response.redirect? + assert_equal "http://test.host/", @response.location + + @request.accept = "application/xml" + get :using_resource_with_status_and_location + assert_equal 201, @response.status + end + + def test_using_resource_with_responder + get :using_resource_with_responder + assert_equal "Resource name is david", @response.body + end + def test_not_acceptable @request.accept = "application/xml" get :using_defaults @@ -642,14 +762,12 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal 406, @response.status @request.accept = "text/javascript" - get :using_resource + get :default_overwritten assert_equal 406, @response.status end end class AbstractPostController < ActionController::Base - respond_to :html, :iphone - self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/" end @@ -658,7 +776,7 @@ class PostController < AbstractPostController around_filter :with_iphone def index - respond_to # It will use formats declared above + respond_to(:html, :iphone) end protected diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 947ffa9ea6..9546fdb50d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -458,6 +458,10 @@ class TestController < ActionController::Base head :location => "/foo" end + def head_with_location_object + head :location => Customer.new("david", 1) + end + def head_with_symbolic_status head :status => params[:status].intern end @@ -618,6 +622,7 @@ class TestController < ActionController::Base end private + def determine_layout case action_name when "hello_world", "layout_test", "rendering_without_layout", @@ -1084,6 +1089,18 @@ class RenderTest < ActionController::TestCase assert_response :ok end + def test_head_with_location_object + ActionController::Routing::Routes.draw do |map| + map.resources :customers + map.connect ':controller/:action/:id' + end + + get :head_with_location_object + assert @response.body.blank? + assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] + assert_response :ok + end + def test_head_with_custom_header get :head_with_custom_header assert @response.body.blank? diff --git a/actionpack/test/controller/render_xml_test.rb b/actionpack/test/controller/render_xml_test.rb index 052b4f0b52..139f55d8bd 100644 --- a/actionpack/test/controller/render_xml_test.rb +++ b/actionpack/test/controller/render_xml_test.rb @@ -11,7 +11,7 @@ class TestController < ActionController::Base def render_with_object_location customer = Customer.new("Some guy", 1) - render :xml => "<customer/>", :location => customer_url(customer), :status => :created + render :xml => "<customer/>", :location => customer, :status => :created end def render_with_to_xml @@ -78,4 +78,4 @@ class RenderTest < ActionController::TestCase get :implicit_content_type, :format => 'atom' assert_equal Mime::ATOM, @response.content_type end -end
\ No newline at end of file +end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index fb83dba395..5f9ae6292c 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -1,3 +1,4 @@ +# encoding: utf-8 require 'abstract_unit' require 'controller/fake_controllers' require 'active_support/dependencies' @@ -1179,7 +1180,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian - token.force_encoding("UTF-8") if token.respond_to?(:force_encoding) escaped_token = CGI::escape(token) assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) diff --git a/actionpack/test/fixtures/respond_with/edit.html.erb b/actionpack/test/fixtures/respond_with/edit.html.erb new file mode 100644 index 0000000000..ae82dfa4fc --- /dev/null +++ b/actionpack/test/fixtures/respond_with/edit.html.erb @@ -0,0 +1 @@ +Edit world! diff --git a/actionpack/test/fixtures/respond_with/new.html.erb b/actionpack/test/fixtures/respond_with/new.html.erb new file mode 100644 index 0000000000..96c8f1b88b --- /dev/null +++ b/actionpack/test/fixtures/respond_with/new.html.erb @@ -0,0 +1 @@ +New world! diff --git a/actionpack/test/fixtures/respond_with/using_resource.html.erb b/actionpack/test/fixtures/respond_with/using_resource.html.erb deleted file mode 100644 index 6769dd60bd..0000000000 --- a/actionpack/test/fixtures/respond_with/using_resource.html.erb +++ /dev/null @@ -1 +0,0 @@ -Hello world!
\ No newline at end of file diff --git a/actionpack/test/fixtures/respond_with/using_resource.js.rjs b/actionpack/test/fixtures/respond_with/using_resource.js.rjs new file mode 100644 index 0000000000..737c175a4e --- /dev/null +++ b/actionpack/test/fixtures/respond_with/using_resource.js.rjs @@ -0,0 +1 @@ +page[:body].visual_effect :highlight diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index c6726432ec..0faf8f3f9a 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -4,9 +4,27 @@ class Customer < Struct.new(:name, :id) extend ActiveModel::Naming include ActiveModel::Conversion + undef_method :to_json + def to_param id.to_s end + + def to_xml + "XML" + end + + def to_js + "JS" + end + + def errors + [] + end + + def destroyed? + false + end end class BadCustomer < Customer @@ -24,4 +42,8 @@ module Quiz id.to_s end end + + class Store < Question + end end + diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 57b740032e..85a97d570c 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -88,6 +88,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("111.00", number_with_precision(111, :precision => 2)) assert_equal("111.235", number_with_precision("111.2346")) assert_equal("31.83", number_with_precision("31.825", :precision => 2)) + assert_equal("3268", number_with_precision((32.675 * 100.00), :precision => 0)) assert_equal("112", number_with_precision(111.50, :precision => 0)) assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 0b7d6d9094..3da3d9d8cc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -12,7 +12,7 @@ module ActiveRecord self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT # Undefine id so it can be used as an attribute name - undef_method :id + undef_method(:id) if method_defined?(:id) end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 06bd2dab95..2eb2699949 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2494,6 +2494,11 @@ module ActiveRecord #:nodoc: @new_record || false end + # Returns true if this object has been destroyed, otherwise returns false. + def destroyed? + @destroyed || false + end + # :call-seq: # save(perform_validation = true) # @@ -2544,6 +2549,7 @@ module ActiveRecord #:nodoc: # options, use <tt>#destroy</tt>. def delete self.class.delete(id) unless new_record? + @destroyed = true freeze end @@ -2554,6 +2560,7 @@ module ActiveRecord #:nodoc: arel_table(true).where(arel_table[self.class.primary_key].eq(id)).delete end + @destroyed = true freeze end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 7631f3ec35..d5d4808074 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -393,6 +393,16 @@ module ActiveRecord down(migrations_path, finish ? finish.version : 0) end + def forward(migrations_path, steps=1) + migrator = self.new(:up, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + return unless start_index + + finish = migrator.migrations[start_index + steps] + up(migrations_path, finish ? finish.version : 0) + end + def up(migrations_path, target_version = nil) self.new(:up, migrations_path, target_version).migrate end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 12808194f9..8009b1457c 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -120,7 +120,13 @@ class AdapterTest < ActiveRecord::TestCase def test_foreign_key_violations_are_translated_to_specific_exception unless @connection.adapter_name == 'SQLite' assert_raises(ActiveRecord::InvalidForeignKey) do - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end end end end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index ab6f752243..784c484178 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -293,7 +293,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object c = Client.new("firm_id" => 1) - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_forgetting_the_load_when_foreign_key_enters_late @@ -301,7 +302,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nil c.firm_with_basic_id c.firm_id = 1 - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_field_name_same_as_foreign_key diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4cf49be668..811ebfbe3f 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -813,7 +813,12 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_many_using_primary_key expected = Firm.find(1).clients_using_primary_key.sort_by &:name - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + # Oracle adapter truncates alias to 30 characters + if current_adapter?(:OracleAdapter) + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' + else + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + end assert_no_queries do assert_equal expected, firm.clients_using_primary_key end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 9c915ab010..1bce45865f 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -284,12 +284,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_creation_respects_hash_condition - post = categories(:general).post_with_conditions.build(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + post = categories(:general).post_with_conditions.build(:body => ' ') assert post.save assert_equal 'Yet Another Testing Title', post.title - another_post = categories(:general).post_with_conditions.create(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + another_post = categories(:general).post_with_conditions.create(:body => ' ') assert !another_post.new_record? assert_equal 'Yet Another Testing Title', another_post.title diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 15919e2289..a3d92c3bdb 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -24,28 +24,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.each {|f| } end + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.find(:first).clients.count + assert_equal 2, Firm.find(:first, :order => "id").clients.count end def test_counting - assert_equal 2, Firm.find(:first).plain_clients.count + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count end def test_counting_with_empty_hash_conditions - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {}) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {}) end def test_counting_with_single_conditions - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"]) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"]) end def test_counting_with_single_hash - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"}) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"}) end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:name) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name) end def test_counting_with_association_limit @@ -55,12 +56,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.find(:first).clients.length + assert_equal 2, Firm.find(:first, :order => "id").clients.length end def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size + assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size end end @@ -115,52 +116,53 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_triple_equality - assert !(Array === Firm.find(:first).clients) - assert Firm.find(:first).clients === Array + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert !(Array === Firm.find(:first, :order => "id").clients) + assert Firm.find(:first, :order => "id").clients === Array end def test_finding_default_orders - assert_equal "Summit", Firm.find(:first).clients.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name end def test_finding_using_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") first_client = firm.clients_using_sql.first assert_not_nil first_client assert_equal "Microsoft", first_client.name assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.find(:first).clients_using_sql.size + assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size end def test_counting_using_sql - assert_equal 1, Firm.find(:first).clients_using_counter_sql.size - assert Firm.find(:first).clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size - assert !Firm.find(:first).clients_using_zero_counter_sql.any? + assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size + assert Firm.find(:first, :order => "id").clients_using_counter_sql.any? + assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size + assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any? end def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size + assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size end def test_counting_using_finder_sql @@ -183,7 +185,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -203,7 +205,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_finder_sql.find("2") assert_kind_of Client, client @@ -219,7 +221,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length end @@ -264,24 +266,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all_sanitized - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") summit = firm.clients.find(:all, :conditions => "name = 'Summit'") assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"]) assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }]) end def test_find_first - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first) - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") + assert_equal firm.clients.first, firm.clients.find(:first, :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id") end def test_find_first_sanitized - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") end def test_find_in_collection @@ -341,7 +344,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.plain_clients.create! end end @@ -731,7 +734,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_destroy_dependent_when_deleted_from_association - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.size client = firm.clients.first @@ -798,7 +802,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -812,7 +816,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1104,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b1060d01af..9da7fc2639 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -14,7 +14,9 @@ require 'models/citation' class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books + fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, + # Reload edges table from fixtures as otherwise repeated test was failing + :edges def test_has_many assert authors(:david).categories.include?(categories(:general)) @@ -343,14 +345,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_polymorphic_with_source_type - assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts + # added sort by ID as otherwise Oracle select sometimes returned rows in different order + assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id) end def test_eager_has_many_polymorphic_with_source_type tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) desired = posts(:welcome, :thinking) assert_no_queries do - assert_equal desired, tag_with_include.tagged_posts + # added sort by ID as otherwise test using JRuby was failing as array elements were in different order + assert_equal desired.sort_by(&:id), tag_with_include.tagged_posts.sort_by(&:id) end assert_equal 5, tag_with_include.taggings.length end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab8768ea3e..055590da0a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -75,13 +75,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_false topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 0 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 1 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + end assert topic.is_test? end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index ddca5e962d..271086af8e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -154,7 +154,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_fails_for_invalid_belongs_to - assert log = AuditLog.create(:developer_id => 0, :message => "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message => " ") log.developer = Developer.new assert !log.developer.valid? @@ -164,7 +165,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_succeeds_for_invalid_belongs_to_with_validate_false - assert log = AuditLog.create(:developer_id => 0, :message=> "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message=> " ") log.unvalidated_developer = Developer.new assert !log.unvalidated_developer.valid? @@ -666,7 +668,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.save(false) - assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] + else + assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + end end def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth @@ -678,7 +685,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.save(false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] - assert_equal ['', '', '', ''], values + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil, nil], values + else + assert_equal ['', '', '', ''], values + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -756,7 +768,12 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @ship.pirate.catchphrase = '' @ship.name = '' @ship.save(false) - assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] + else + assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -837,11 +854,20 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).each { |child| child.name = '' } assert @pirate.save(false) - assert_equal ['', '', ''], [ - @pirate.reload.catchphrase, - @pirate.send(@association_name).first.name, - @pirate.send(@association_name).last.name - ] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + else + assert_equal ['', '', ''], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + end end def test_should_validation_the_associated_models_on_create diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index d5b0d58b8a..8434b8efe9 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1271,6 +1271,23 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Topic.find(1).new_record?, false end + def test_destroyed_returns_boolean + developer = Developer.new + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.first + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.last + assert_equal developer.destroyed?, false + developer.delete + assert_equal developer.destroyed?, true + end + def test_clone topic = Topic.find(1) cloned_topic = nil diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 8edbdbd20f..855b4c60ae 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -298,7 +298,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_expression - assert_equal '636', Account.sum("2 * credit_limit") + # Oracle adapter returns floating point value 636.0 after SUM + if current_adapter?(:OracleAdapter) + assert_equal 636, Account.sum("2 * credit_limit") + else + assert_equal '636', Account.sum("2 * credit_limit") + end end def test_count_with_from_option diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index 6274d5250f..c689e97d83 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -6,7 +6,14 @@ class DatabaseStatementsTest < ActiveRecord::TestCase end def test_insert_should_return_the_inserted_id - id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if current_adapter?(:OracleAdapter) + sequence_name = "accounts_seq" + id_value = @connection.next_sequence_value(sequence_name) + id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) + else + id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + end assert_not_nil id end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 600a9fe451..3de07797d4 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -156,10 +156,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_all_with_limit - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2) - - assert_equal(2, entrants.size) - assert_equal(entrants(:first).name, entrants.first.name) + assert_equal(2, Entrant.find(:all, :limit => 2).size) + assert_equal(0, Entrant.find(:all, :limit => 0).size) end def test_find_all_with_prepared_limit_and_offset @@ -168,22 +166,23 @@ class FinderTest < ActiveRecord::TestCase assert_equal(2, entrants.size) assert_equal(entrants(:second).name, entrants.first.name) + assert_equal 3, Entrant.count entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) assert_equal(1, entrants.size) assert_equal(entrants(:third).name, entrants.first.name) end - def test_find_all_with_limit_and_offset_and_multiple_orderings - developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1) - assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name} - end + def test_find_all_with_limit_and_offset_and_multiple_order_clauses + first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 + second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - def test_find_with_limit_and_condition - developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7) - assert_equal(1, developers.size) - assert_equal("fixture_3", developers.first.name) + assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } end + def test_find_with_group developers = Developer.find(:all, :group => "salary", :select => "salary") assert_equal 4, developers.size @@ -978,40 +977,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end - def test_find_all_with_limit - first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5 - assert_equal 5, first_five_developers.length - assert_equal 'David', first_five_developers.first.name - assert_equal 'fixture_5', first_five_developers.last.name - - no_developers = Developer.find :all, :order => 'id ASC', :limit => 0 - assert_equal 0, no_developers.length - end - - def test_find_all_with_limit_and_offset - first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0 - second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3 - last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8 - - assert_equal 3, first_three_developers.length - assert_equal 3, second_three_developers.length - assert_equal 2, last_two_developers.length - - assert_equal 'David', first_three_developers.first.name - assert_equal 'fixture_4', second_three_developers.first.name - assert_equal 'fixture_9', last_two_developers.first.name - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } - end - def test_find_all_with_join developers_on_project_one = Developer.find( :all, @@ -1027,7 +992,7 @@ class FinderTest < ActiveRecord::TestCase def test_joins_dont_clobber_id first = Firm.find( :first, - :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id', + :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', :conditions => 'companies.id = 1' ) assert_equal 1, first.id diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 8580d67e5f..73e51fbd91 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -137,7 +137,8 @@ class InheritanceTest < ActiveRecord::TestCase def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" assert_equal "I am a client", Client.find(:all).first.name - assert_equal "37signals", Firm.find(:all).first.name + # Order by added as otherwise Oracle tests were failing because of different order of results + assert_equal "37signals", Firm.find(:all, :order => "id").first.name end def test_alt_update_all_within_inheritance diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index e2bb17c37f..99af7d2986 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -11,13 +11,23 @@ class InvalidDateTest < Test::Unit::TestCase valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) - assert_equal(topic.last_read, Date.new(*date_src)) + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Date.new(*date_src)) + else + assert_equal(topic.last_read, Date.new(*date_src)) + end end invalid_dates.each do |date_src| assert_nothing_raised do topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s}) - assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + else + assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + end end end end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index d4e63ce2fd..6dec474f7d 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -379,6 +379,7 @@ class NestedScopingTest < ActiveRecord::TestCase poor_jamis = developers(:poor_jamis) Developer.with_scope(:find => { :conditions => "salary < 100000" }) do Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do + # Oracle adapter does not generated space after asc therefore trailing space removed from regex assert_sql /ORDER BY id asc/ do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 215b5a427a..f0f21615e0 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -446,18 +446,22 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal Date, bob.favorite_day.class end - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM - assert_equal DateTime::ITALY, bob.moment_of_truth.start + # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column + # therefore no timezone change is done afterwards when default timezone is changed + unless current_adapter?(:OracleAdapter) + # Test DateTime column and defaults, including timezone. + # FIXME: moment of truth may be Time on 64-bit platforms. + if bob.moment_of_truth.is_a?(DateTime) + + with_env_tz 'US/Eastern' do + assert_equal DateTime.local_offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + # US/Eastern is -5 hours from GMT + assert_equal Rational(-5, 24), bob.moment_of_truth.offset + assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end end end @@ -571,7 +575,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.connection.create_table(:hats) do |table| table.column :hat_name, :string, :default => nil end - exception = if current_adapter?(:PostgreSQLAdapter) + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) ActiveRecord::StatementInvalid else ActiveRecord::ActiveRecordError @@ -625,7 +629,13 @@ if ActiveRecord::Base.connection.supports_migrations? table.column :hat_size, :integer table.column :hat_style, :string, :limit => 100 end - ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + # Oracle index names should be 30 or less characters + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true, + :name => 'index_hats_on_hat_style_size' + else + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + end assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } ensure @@ -727,19 +737,20 @@ if ActiveRecord::Base.connection.supports_migrations? def test_change_column Person.connection.add_column 'people', 'age', :integer - old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + label = "test_change_column Columns" + old_columns = Person.connection.columns(Person.table_name, label) assert old_columns.find { |c| c.name == 'age' and c.type == :integer } assert_nothing_raised { Person.connection.change_column "people", "age", :string } - new_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + new_columns = Person.connection.columns(Person.table_name, label) assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer } assert new_columns.find { |c| c.name == 'age' and c.type == :string } - old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + old_columns = Topic.connection.columns(Topic.table_name, label) assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } - new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + new_columns = Topic.connection.columns(Topic.table_name, label) assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } @@ -783,7 +794,12 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } - assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" } + else + assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + end ensure Person.connection.drop_table :testings rescue nil end @@ -799,7 +815,12 @@ if ActiveRecord::Base.connection.supports_migrations? person_klass.reset_column_information assert_equal 99, person_klass.columns_hash["wealth"].default assert_equal false, person_klass.columns_hash["wealth"].null - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + else + assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + end # change column default to see that column doesn't lose its not null definition person_klass.connection.change_column_default "testings", "wealth", 100 @@ -1054,7 +1075,12 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_migrator_db_has_no_schema_migrations_table - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + # Oracle adapter raises error if semicolon is present as last character + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + else + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + end assert_nothing_raised do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) end @@ -1110,6 +1136,17 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal(0, ActiveRecord::Migrator.current_version) end + def test_migrator_forward + ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) + assert_equal(1, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2) + assert_equal(3, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + end + def test_schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" @@ -1412,6 +1449,8 @@ if ActiveRecord::Base.connection.supports_migrations? def string_column if current_adapter?(:PostgreSQLAdapter) "character varying(255)" + elsif current_adapter?(:OracleAdapter) + 'VARCHAR2(255)' else 'varchar(255)' end @@ -1420,6 +1459,8 @@ if ActiveRecord::Base.connection.supports_migrations? def integer_column if current_adapter?(:MysqlAdapter) 'int(11)' + elsif current_adapter?(:OracleAdapter) + 'NUMBER(38)' else 'integer' end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 3cb6b01e80..330ba7189f 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -154,7 +154,8 @@ class NamedScopeTest < ActiveRecord::TestCase assert !authors(:david).posts.ranked_by_comments.limit(5).empty? assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) - assert_equal authors(:david).posts.ranked_by_comments.limit(5), authors(:david).posts.top(5) + # Oracle sometimes sorts differently if WHERE condition is changed + assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) assert_equal Post.ranked_by_comments.limit(5), Post.top(5) end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f31275163d..d033c1e760 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -4,6 +4,8 @@ require "models/ship" require "models/bird" require "models/parrot" require "models/treasure" +require "models/man" +require "models/interest" require 'active_support/hash_with_indifferent_access' module AssertRaiseWithMessage @@ -470,6 +472,41 @@ module NestedAttributesOnACollectionAssociationTests assert Pirate.reflect_on_association(@association_name).options[:autosave] end + def test_validate_presence_of_parent__works_with_inverse_of + Man.accepts_nested_attributes_for(:interests) + assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of] + assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of] + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + assert_difference 'Man.count' do + assert_difference 'Interest.count', 2 do + man = Man.create!(:name => 'John', + :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert_equal 2, man.interests.count + end + end + end + end + + def test_validate_presence_of_parent__fails_without_inverse_of + Man.accepts_nested_attributes_for(:interests) + Man.reflect_on_association(:interests).options.delete(:inverse_of) + Interest.reflect_on_association(:man).options.delete(:inverse_of) + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + 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? + end + end + # restore :inverse_of + Man.reflect_on_association(:interests).options[:inverse_of] = :man + Interest.reflect_on_association(:man).options[:inverse_of] = :interests + end + private def association_setter diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index f90a66d1dc..2af6a56b6a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -50,7 +50,12 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_does_not_wrap_string_results_in_arrays Task.cache do - assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + # Oracle adapter returns count() as Fixnum or Float + if current_adapter?(:OracleAdapter) + assert Task.connection.select_value("SELECT count(*) AS count_all FROM tasks").is_a?(Numeric) + else + assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + end end end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9612b0beb6..4f8e20b3ba 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -114,6 +114,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_6.*:limit => 6}, output assert_match %r{c_int_7.*:limit => 7}, output assert_match %r{c_int_8.*:limit => 8}, output + elsif current_adapter?(:OracleAdapter) + assert_match %r{c_int_5.*:limit => 5}, output + assert_match %r{c_int_6.*:limit => 6}, output + assert_match %r{c_int_7.*:limit => 7}, output + assert_match %r{c_int_8.*:limit => 8}, output else assert_match %r{c_int_5.*:limit => 8}, output assert_match %r{c_int_6.*:limit => 8}, output @@ -193,6 +198,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_keeps_large_precision_integer_columns_as_decimal output = standard_dump - assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers + if current_adapter?(:OracleAdapter) + assert_match %r{t.integer\s+"atoms_in_universe",\s+:precision => 38,\s+:scale => 0}, output + else + assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + end end end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index b1203c12ed..278a7a6a06 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -3,6 +3,9 @@ require "cases/helper" require 'models/topic' require 'models/reply' require 'models/owner' +require 'models/pet' +require 'models/man' +require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics, :owners @@ -98,4 +101,24 @@ class AssociationValidationTest < ActiveRecord::TestCase end end end + + def test_validates_presence_of_belongs_to_association__parent_is_new_record + repair_validations(Interest) do + # Note that Interest and Man have the :inverse_of option set + Interest.validates_presence_of(:man) + man = Man.new(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + + def test_validates_presence_of_belongs_to_association__existing_parent + repair_validations(Interest) do + Interest.validates_presence_of(:man) + man = Man.create!(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + end diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 0954b27f87..c8183dc0fb 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,27 +1,68 @@ +# gem "rsim-activerecord-oracle_enhanced-adapter" +# gem "activerecord-oracle_enhanced-adapter", ">=1.2.1" +# uses local copy of oracle_enhanced adapter +$:.unshift("../../oracle-enhanced/lib") +require 'active_record/connection_adapters/oracle_enhanced_adapter' +# gem "activerecord-jdbc-adapter" +# require 'active_record/connection_adapters/jdbc_adapter' + +# otherwise failed with silence_warnings method missing exception +require 'active_support/core_ext/kernel/reporting' + print "Using Oracle\n" require_dependency 'models/course' require 'logger' -ActiveRecord::Base.logger = Logger.new STDOUT -ActiveRecord::Base.logger.level = Logger::WARN +# ActiveRecord::Base.logger = Logger.new STDOUT +# ActiveRecord::Base.logger.level = Logger::WARN +ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB'] || 'activerecord_unittest' +db = ENV['ARUNIT_DB_NAME'] = 'orcl' ActiveRecord::Base.configurations = { 'arunit' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit', :password => 'arunit', - :database => db, + :emulate_oracle_adapter => true }, 'arunit2' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit2', :password => 'arunit2', - :database => db + :emulate_oracle_adapter => true } } ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' + +# ActiveRecord::Base.connection.execute %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} +# ActiveRecord::Base.connection.execute %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil + +# for assert_queries test helper +ActiveRecord::Base.connection.class.class_eval do + IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im] + + def select_with_query_record(sql, name = nil, return_column_names = false) + $queries_executed ||= [] + $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r } + select_without_query_record(sql, name, return_column_names) + end + + alias_method_chain :select, :query_record +end + +# For JRuby Set default $KCODE to UTF8 +$KCODE = "UTF8" if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 22168468a6..1c05e523e0 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -73,12 +73,16 @@ class Firm < Company has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true - has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account", :order => "id" has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete end class DependentFirm < Company - has_one :account, :foreign_key => "firm_id", :dependent => :nullify + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id" has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify end diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 3502943f3a..1b9d8107f8 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,4 +1,12 @@ -# used for OracleSynonymTest, see test/synonym_test_oci.rb +# used for OracleSynonymTest, see test/synonym_test_oracle.rb # class Subject < ActiveRecord::Base + protected + # added initialization of author_email_address in the same way as in Topic class + # as otherwise synonym test was failing + def after_initialize + if self.new_record? + self.author_email_address = 'test@test.com' + end + end end diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb index 2d87f34625..3314687445 100644 --- a/activerecord/test/schema/oracle_specific_schema.rb +++ b/activerecord/test/schema/oracle_specific_schema.rb @@ -2,6 +2,10 @@ ActiveRecord::Schema.define do execute "drop table test_oracle_defaults" rescue nil execute "drop sequence test_oracle_defaults_seq" rescue nil + execute "drop sequence companies_nonstd_seq" rescue nil + execute "drop synonym subjects" rescue nil + execute "drop table defaults" rescue nil + execute "drop sequence defaults_seq" rescue nil execute <<-SQL create table test_oracle_defaults ( @@ -16,4 +20,27 @@ create table test_oracle_defaults ( create sequence test_oracle_defaults_seq minvalue 10000 SQL + execute "create sequence companies_nonstd_seq minvalue 10000" + + execute "create synonym subjects for topics" + + execute <<-SQL + CREATE TABLE defaults ( + id integer not null, + modified_date date default sysdate, + modified_date_function date default sysdate, + fixed_date date default to_date('2004-01-01', 'YYYY-MM-DD'), + modified_time date default sysdate, + modified_time_function date default sysdate, + fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), + char1 varchar2(1) default 'Y', + char2 varchar2(50) default 'a varchar field', + char3 clob default 'a text field', + positive_integer integer default 1, + negative_integer integer default -1, + decimal_number number(3,2) default 2.78 + ) + SQL + execute "create sequence defaults_seq minvalue 10000" + end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 1e47cdbaf6..5f60d5e137 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -104,7 +104,13 @@ ActiveRecord::Schema.define do create_table :comments, :force => true do |t| t.integer :post_id, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type end @@ -279,7 +285,12 @@ ActiveRecord::Schema.define do t.decimal :my_house_population, :precision => 2, :scale => 0 t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78 t.float :temperature - t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + # Oracle supports precision up to 38 + if current_adapter?(:OracleAdapter) + t.decimal :atoms_in_universe, :precision => 38, :scale => 0 + else + t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + end end create_table :orders, :force => true do |t| @@ -350,7 +361,13 @@ ActiveRecord::Schema.define do create_table :posts, :force => true do |t| t.integer :author_id t.string :title, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type t.integer :comments_count, :default => 0 t.integer :taggings_count, :default => 0 @@ -423,7 +440,13 @@ ActiveRecord::Schema.define do t.datetime :written_on t.time :bonus_time t.date :last_read - t.text :content + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :content, :limit => 4000 + else + t.text :content + end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 t.integer :parent_id diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index fb3fde59d6..99d4b8f2ca 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -134,7 +134,7 @@ module ActiveResource def http http = Net::HTTP.new(@site.host, @site.port) http.use_ssl = @site.is_a?(URI::HTTPS) - http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl + http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl? http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used. http end diff --git a/activesupport/lib/active_support/json/backends/yaml.rb b/activesupport/lib/active_support/json/backends/yaml.rb index 667016f45d..92dd31cfbc 100644 --- a/activesupport/lib/active_support/json/backends/yaml.rb +++ b/activesupport/lib/active_support/json/backends/yaml.rb @@ -34,11 +34,9 @@ module ActiveSupport pos = scanner.pos elsif quoting == char if json[pos..scanner.pos-2] =~ DATE_REGEX - # found a date, track the exact positions of the quotes so we can remove them later. - # oh, and increment them for each current mark, each one is an extra padded space that bumps - # the position in the final YAML output - total_marks = marks.size - times << pos+total_marks << scanner.pos+total_marks + # found a date, track the exact positions of the quotes so we can + # overwrite them with spaces later. + times << pos << scanner.pos end quoting = false end @@ -64,7 +62,12 @@ module ActiveSupport output = [] left_pos.each_with_index do |left, i| scanner.pos = left.succ - output << scanner.peek(right_pos[i] - scanner.pos + 1).gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do + chunk = scanner.peek(right_pos[i] - scanner.pos + 1) + # overwrite the quotes found around the dates with spaces + while times.size > 0 && times[0] <= right_pos[i] + chunk[times.shift - scanner.pos - 1] = ' ' + end + chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do ustr = $1 if ustr.start_with?('u') [ustr[1..-1].to_i(16)].pack("U") @@ -74,10 +77,10 @@ module ActiveSupport ustr end end + output << chunk end output = output * " " - times.each { |i| output[i-1] = ' ' } output.gsub!(/\\\//, '/') output end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 09fd0d09ba..4129a4fab6 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -33,7 +33,13 @@ class TestJSONDecoding < ActiveSupport::TestCase %q({"a": "\u003cunicode\u0020escape\u003e"}) => {"a" => "<unicode escape>"}, %q({"a": "\\\\u0020skip double backslashes"}) => {"a" => "\\u0020skip double backslashes"}, %q({"a": "\u003cbr /\u003e"}) => {'a' => "<br />"}, - %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]} + %q({"b":["\u003ci\u003e","\u003cb\u003e","\u003cu\u003e"]}) => {'b' => ["<i>","<b>","<u>"]}, + # test combination of dates and escaped or unicode encoded data in arrays + %q([{"d":"1970-01-01", "s":"\u0020escape"},{"d":"1970-01-01", "s":"\u0020escape"}]) => + [{'d' => Date.new(1970, 1, 1), 's' => ' escape'},{'d' => Date.new(1970, 1, 1), 's' => ' escape'}], + %q([{"d":"1970-01-01","s":"http:\/\/example.com"},{"d":"1970-01-01","s":"http:\/\/example.com"}]) => + [{'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}, + {'d' => Date.new(1970, 1, 1), 's' => 'http://example.com'}] } # load the default JSON backend diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb index 64ec808ee4..c97c61507a 100644 --- a/railties/lib/generators.rb +++ b/railties/lib/generators.rb @@ -11,7 +11,7 @@ end $:.unshift(File.dirname(__FILE__)) -require 'vendor/thor-0.11.3/lib/thor' +require 'vendor/thor-0.11.5/lib/thor' require 'generators/base' require 'generators/named_base' @@ -83,6 +83,34 @@ module Rails @@options ||= DEFAULT_OPTIONS.dup end + # Get paths only from loaded rubygems. In other words, to use rspec + # generators, you first have to ensure that rspec gem was already loaded. + # + def self.rubygems_generators_paths + paths = [] + return paths unless defined?(Gem) + + Gem.loaded_specs.each do |name, spec| + generator_path = File.join(spec.full_gem_path, "lib/generators") + paths << generator_path if File.exist?(generator_path) + end + + paths + end + + # If RAILS_ROOT is defined, add vendor/gems, vendor/plugins and lib/generators + # paths. + # + def self.rails_root_generators_paths + paths = [] + if defined?(RAILS_ROOT) + paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] + paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] + paths << File.join(RAILS_ROOT, "lib", "generators") + end + paths + end + # Hold configured generators fallbacks. If a plugin developer wants a # generator group to fallback to another group in case of missing generators, # they can add a fallback. @@ -108,30 +136,25 @@ module Rails # Generators load paths used on lookup. The lookup happens as: # - # 1) builtin generators - # 2) frozen gems generators - # 3) rubygems gems generators (not available yet) - # 4) plugin generators - # 5) lib generators - # 6) ~/rails/generators + # 1) lib generators + # 2) vendor/plugin generators + # 3) vendor/gems generators + # 4) ~/rails/generators + # 5) rubygems generators + # 6) builtin generators # - # TODO Add Rubygems generators (depends on dependencies system rework) # TODO Remove hardcoded paths for all, except (1). # - def self.load_path - @@load_path ||= begin - paths = [] - paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) - if defined?(RAILS_ROOT) - paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "*", "lib", "generators")] - paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] - paths << File.join(RAILS_ROOT, "lib", "generators") - end + def self.load_paths + @@load_paths ||= begin + paths = self.rails_root_generators_paths paths << File.join(Thor::Util.user_home, ".rails", "generators") + paths += self.rubygems_generators_paths + paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) paths end end - load_path # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. + load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. # Receives a namespace and tries different combinations to find a generator. # @@ -204,8 +227,8 @@ module Rails puts "Builtin: #{rails.join(', ')}." # Load paths and remove builtin - paths, others = load_path.dup, [] - paths.shift + paths, others = load_paths.dup, [] + paths.pop paths.each do |path| tail = [ "*", "*", "*_generator.rb" ] @@ -242,7 +265,6 @@ module Rails # def self.invoke_fallbacks_for(name, base) return nil unless base && fallbacks[base.to_sym] - invoked_fallbacks = [] Array(fallbacks[base.to_sym]).each do |fallback| @@ -283,7 +305,7 @@ module Rails generators_path.uniq! generators_path = "{#{generators_path.join(',')}}" - self.load_path.each do |path| + self.load_paths.each do |path| Dir[File.join(path, generators_path, name)].each do |file| begin require file diff --git a/railties/lib/generators/action_orm.rb b/railties/lib/generators/active_model.rb index 69cf227fd7..1a849a0e02 100644 --- a/railties/lib/generators/action_orm.rb +++ b/railties/lib/generators/active_model.rb @@ -1,6 +1,6 @@ module Rails module Generators - # ActionORM is a class to be implemented by each ORM to allow Rails to + # ActiveModel is a class to be implemented by each ORM to allow Rails to # generate customized controller code. # # The API has the same methods as ActiveRecord, but each method returns a @@ -8,22 +8,22 @@ module Rails # # For example: # - # ActiveRecord::Generators::ActionORM.find(Foo, "params[:id]") + # ActiveRecord::Generators::ActiveModel.find(Foo, "params[:id]") # #=> "Foo.find(params[:id])" # - # Datamapper::Generators::ActionORM.find(Foo, "params[:id]") + # Datamapper::Generators::ActiveModel.find(Foo, "params[:id]") # #=> "Foo.get(params[:id])" # - # On initialization, the ActionORM accepts the instance name that will + # On initialization, the ActiveModel accepts the instance name that will # receive the calls: # - # builder = ActiveRecord::Generators::ActionORM.new "@foo" + # builder = ActiveRecord::Generators::ActiveModel.new "@foo" # builder.save #=> "@foo.save" # - # The only exception in ActionORM for ActiveRecord is the use of self.build + # The only exception in ActiveModel for ActiveRecord is the use of self.build # instead of self.new. # - class ActionORM + class ActiveModel attr_reader :name def initialize(name) diff --git a/railties/lib/generators/active_record.rb b/railties/lib/generators/active_record.rb index 64bee3904e..924b70881a 100644 --- a/railties/lib/generators/active_record.rb +++ b/railties/lib/generators/active_record.rb @@ -1,6 +1,6 @@ require 'generators/named_base' require 'generators/migration' -require 'generators/action_orm' +require 'generators/active_model' require 'active_record' module ActiveRecord @@ -20,7 +20,7 @@ module ActiveRecord end end - class ActionORM < Rails::Generators::ActionORM #:nodoc: + class ActiveModel < Rails::Generators::ActiveModel #:nodoc: def self.all(klass) "#{klass}.all" end diff --git a/railties/lib/generators/named_base.rb b/railties/lib/generators/named_base.rb index 699b8ed651..9632e6806c 100644 --- a/railties/lib/generators/named_base.rb +++ b/railties/lib/generators/named_base.rb @@ -124,18 +124,18 @@ module Rails protected - # Loads the ORM::Generators::ActionORM class. This class is responsable + # Loads the ORM::Generators::ActiveModel class. This class is responsable # to tell scaffold entities how to generate an specific method for the - # ORM. Check Rails::Generators::ActionORM for more information. + # ORM. Check Rails::Generators::ActiveModel for more information. # def orm_class @orm_class ||= begin # Raise an error if the class_option :orm was not defined. unless self.class.class_options[:orm] - raise "You need to have :orm as class option to invoke orm_class and orm_instance" + raise "You need to have :orm as class option to invoke orm_class and orm_instance" end - action_orm = "#{options[:orm].to_s.classify}::Generators::ActionORM" + action_orm = "#{options[:orm].to_s.classify}::Generators::ActiveModel" # If the orm was not loaded, try to load it at "generators/orm", # for example "generators/active_record" or "generators/sequel". @@ -152,7 +152,7 @@ module Rails end end - # Initialize ORM::Generators::ActionORM to access instance methods. + # Initialize ORM::Generators::ActiveModel to access instance methods. # def orm_instance(name=file_name) @orm_instance ||= @orm_class.new(name) diff --git a/railties/lib/generators/rails/app/app_generator.rb b/railties/lib/generators/rails/app/app_generator.rb index c8044d13b1..c80a344e0d 100644 --- a/railties/lib/generators/rails/app/app_generator.rb +++ b/railties/lib/generators/rails/app/app_generator.rb @@ -49,7 +49,7 @@ module Rails::Generators self.destination_root = File.expand_path(app_path, destination_root) empty_directory '.' - app_name # Sets the app name + set_default_accessors! FileUtils.cd(destination_root) end @@ -164,9 +164,9 @@ module Rails::Generators end def apply_rails_template - apply options[:template] if options[:template] + apply rails_template if rails_template rescue Thor::Error, LoadError, Errno::ENOENT => e - raise Error, "The template [#{options[:template]}] could not be loaded. Error: #{e}" + raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end def freeze? @@ -175,6 +175,21 @@ module Rails::Generators protected + attr_accessor :rails_template + + def set_default_accessors! + app_name # Cache app name + + self.rails_template = case options[:template] + when /^http:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) + else + options[:template] + end + end + # Define file as an alias to create_file for backwards compatibility. # def file(*args, &block) diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 0d4d658315..23a3a73a7f 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -101,8 +101,12 @@ namespace :db do ActiveRecord::Base.configurations.each_value do |config| # Skip entries that don't have a database key next unless config['database'] - # Only connect to local databases - local_database?(config) { drop_database(config) } + begin + # Only connect to local databases + local_database?(config) { drop_database(config) } + rescue Exception => e + puts "Couldn't drop #{config['database']} : #{e.inspect}" + end end end end @@ -172,6 +176,13 @@ namespace :db do Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby end + desc 'Pushes the schema to the next version. Specify the number of steps with STEP=n' + task :forward => :environment do + step = ENV['STEP'] ? ENV['STEP'].to_i : 1 + ActiveRecord::Migrator.forward('db/migrate/', step) + Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + end + desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' task :reset => [ 'db:drop', 'db:setup' ] diff --git a/railties/lib/tasks/routes.rake b/railties/lib/tasks/routes.rake index 39b7139167..abbf3258c1 100644 --- a/railties/lib/tasks/routes.rake +++ b/railties/lib/tasks/routes.rake @@ -1,6 +1,7 @@ -desc 'Print out all defined routes in match order, with names.' +desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do - routes = ActionController::Routing::Routes.routes.collect do |route| + all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes + routes = all_routes.collect do |route| name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s verb = route.conditions[:method].to_s.upcase segs = route.segments.inject("") { |str,s| str << s.to_s } @@ -14,4 +15,4 @@ task :routes => :environment do routes.each do |r| puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}" end -end
\ No newline at end of file +end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb deleted file mode 100644 index c846e9ce74..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/error.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Thor - # Thor::Error is raised when it's caused by the user invoking the task and - # only errors that inherit from it are rescued. - # - # So, for example, if the developer declares a required argument after an - # option, it should raise an ::ArgumentError and not ::Thor::ArgumentError, - # because it was caused by the developer and not the "final user". - # - class Error < StandardError #:nodoc: - end - - # Raised when a task was not found. - # - class UndefinedTaskError < Error #:nodoc: - end - - # Raised when a task was found, but not invoked properly. - # - class InvocationError < Error #:nodoc: - end - - class RequiredArgumentMissingError < InvocationError #:nodoc: - end - - class MalformattedArgumentError < InvocationError #:nodoc: - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb deleted file mode 100644 index d1a7b1c673..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks.rb +++ /dev/null @@ -1,4 +0,0 @@ -# This only loads all tasks inside tasks. -Dir[File.join(File.dirname(__FILE__), "tasks", "*.rb")].each do |task| - require task -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb deleted file mode 100644 index 6b20ff1634..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/install.rb +++ /dev/null @@ -1,35 +0,0 @@ -class Thor - # Creates an install task. - # - # ==== Parameters - # spec<Gem::Specification> - # - # ==== Options - # :dir - The directory where the package is hold before installation. Defaults to ./pkg. - # - def self.install_task(spec, options={}) - package_task(spec, options) - tasks['install'] = Thor::InstallTask.new(spec, options) - end - - class InstallTask < Task - attr_accessor :spec, :config - - def initialize(gemspec, config={}) - super(:install, "Install the gem", "install", {}) - @spec = gemspec - @config = { :dir => File.join(Dir.pwd, "pkg") }.merge(config) - end - - def run(instance, args=[]) - null, sudo, gem = RUBY_PLATFORM =~ /mswin|mingw/ ? ['NUL', '', 'gem.bat'] : - ['/dev/null', 'sudo', 'gem'] - - old_stderr, $stderr = $stderr.dup, File.open(null, "w") - instance.invoke(:package) - $stderr = old_stderr - - system %{#{sudo} #{Gem.ruby} -S #{gem} install #{config[:dir]}/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources} - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb deleted file mode 100644 index 603d61b4ab..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/package.rb +++ /dev/null @@ -1,31 +0,0 @@ -require "fileutils" - -class Thor - # Creates a package task. - # - # ==== Parameters - # spec<Gem::Specification> - # - # ==== Options - # :dir - The package directory. Defaults to ./pkg. - # - def self.package_task(spec, options={}) - tasks['package'] = Thor::PackageTask.new(spec, options) - end - - class PackageTask < Task - attr_accessor :spec, :config - - def initialize(gemspec, config={}) - super(:package, "Build a gem package", "package", {}) - @spec = gemspec - @config = {:dir => File.join(Dir.pwd, "pkg")}.merge(config) - end - - def run(instance, args=[]) - FileUtils.mkdir_p(config[:dir]) - Gem::Builder.new(spec).build - FileUtils.mv(spec.file_name, File.join(config[:dir], spec.file_name)) - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb b/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb deleted file mode 100644 index c7d00968e8..0000000000 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/tasks/spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require "fileutils" - -class Thor - # Creates a spec task. - # - # ==== Parameters - # files<Array> - Array of files to spec - # - # ==== Options - # :name - The name of the task. It can be rcov or spec. Spec is the default. - # :rcov - A hash with rcov specific options. - # :rcov_dir - Where rcov reports should be printed. - # :verbose - Sets the default value for verbose, although it can be specified - # also through the command line. - # - # All other options are added to rspec. - # - def self.spec_task(files, options={}) - name = (options.delete(:name) || 'spec').to_s - tasks[name] = Thor::SpecTask.new(name, files, options) - end - - class SpecTask < Task - attr_accessor :name, :files, :rcov_dir, :rcov_config, :spec_config - - def initialize(name, files, config={}) - options = { :verbose => Thor::Option.parse(:verbose, config.delete(:verbose) || false) } - super(name, "#{name.capitalize} task", name, options) - - @name = name - @files = files.map{ |f| %["#{f}"] }.join(" ") - @rcov_dir = config.delete(:rdoc_dir) || File.join(Dir.pwd, 'coverage') - @rcov_config = config.delete(:rcov) || {} - @spec_config = { :format => 'specdoc', :color => true }.merge(config) - end - - def run(instance, args=[]) - rcov_opts = Thor::Options.to_switches(rcov_config) - spec_opts = Thor::Options.to_switches(spec_config) - - require 'rbconfig' - cmd = RbConfig::CONFIG['ruby_install_name'] << " " - - if rcov? - FileUtils.rm_rf(rcov_dir) - cmd << "-S #{where('rcov')} -o #{rcov_dir} #{rcov_opts} " - end - - cmd << [where('spec'), rcov? ? " -- " : nil, files, spec_opts].join(" ") - - puts cmd if instance.options.verbose? - system(cmd) - exit($?.exitstatus) - end - - private - - def rcov? - name == "rcov" - end - - def where(file) - ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| - file_with_path = File.join(path, file) - next unless File.exist?(file_with_path) && File.executable?(file_with_path) - return File.expand_path(file_with_path) - end - end - end -end diff --git a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc index 544dde8c02..dba25b7205 100644 --- a/railties/lib/vendor/thor-0.11.3/CHANGELOG.rdoc +++ b/railties/lib/vendor/thor-0.11.5/CHANGELOG.rdoc @@ -1,9 +1,11 @@ == TODO * Improve spec coverage for Thor::Runner -* Improve help output to list shorthand switches, too -== Current +== 0.11.x, released 2009-07-01 + +* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on + Thor classes. * BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore since it wrong behavior to the invocation system. diff --git a/railties/lib/vendor/thor-0.11.3/LICENSE b/railties/lib/vendor/thor-0.11.5/LICENSE index 98722da459..98722da459 100644 --- a/railties/lib/vendor/thor-0.11.3/LICENSE +++ b/railties/lib/vendor/thor-0.11.5/LICENSE diff --git a/railties/lib/vendor/thor-0.11.3/README.markdown b/railties/lib/vendor/thor-0.11.5/README.rdoc index a1d7259775..f1106f02b6 100644 --- a/railties/lib/vendor/thor-0.11.3/README.markdown +++ b/railties/lib/vendor/thor-0.11.5/README.rdoc @@ -1,5 +1,4 @@ -thor -==== += thor Map options to a class. Simply create a class with the appropriate annotations and have options automatically map to functions and parameters. @@ -34,26 +33,18 @@ That gets converted to: App.new.install("myname") # with {'force' => true} as options hash -1. Inherit from Thor to turn a class into an option mapper -2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list -3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description -4. Provide any additional options that will be available the instance method options. - -Types for `method_options` --------------------------- - -<dl> - <dt><code>:boolean</code></dt> - <dd>is parsed as --option or --option=true</dd> - <dt><code>:string</code></dt> - <dd>is parsed as --option=VALUE</dd> - <dt><code>:numeric</code></dt> - <dd>is parsed as --option=N</dd> - <dt><code>:array</code></dt> - <dd>is parsed as --option=one two three</dd> - <dt><code>:hash</code></dt> - <dd>is parsed as --option=key:value key:value key:value</dd> -</dl> +1. Inherit from Thor to turn a class into an option mapper +2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list +3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description +4. Provide any additional options that will be available the instance method options. + +== Types for <tt>method_options</tt> + +* :boolean - is parsed as <tt>--option</tt> or <tt>--option=true</tt> +* :string - is parsed as <tt>--option=VALUE</tt> +* :numeric - is parsed as <tt>--option=N</tt> +* :array - is parsed as <tt>--option=one two three</tt> +* :hash - is parsed as <tt>--option=name:string age:integer</tt> Besides, method_option allows a default value to be given, examples: @@ -66,9 +57,9 @@ Besides, method_option allows a default value to be given, examples: method_options :threshold => 3.0 #=> Creates a numeric option with default value 3.0 -You can also supply :option => :required to mark an option as required. The +You can also supply <tt>:option => :required</tt> to mark an option as required. The type is assumed to be string. If you want a required hash with default values -as option, you can use `method_option` which uses a more declarative style: +as option, you can use <tt>method_option</tt> which uses a more declarative style: method_option :attributes, :type => :hash, :default => {}, :required => true @@ -91,8 +82,7 @@ You can supply as many aliases as you want. NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead. -Namespaces ----------- +== Namespaces By default, your Thor tasks are invoked using Ruby namespace. In the example above, tasks are invoked as: @@ -124,8 +114,7 @@ And then your tasks hould be invoked as: thor myapp:install name --force -Invocations ------------ +== Invocations Thor comes with a invocation-dependency system as well which allows a task to be invoked only once. For example: @@ -158,8 +147,7 @@ The output is "1 2 3", which means that the three task was invoked only once. You can even invoke tasks from another class, so be sure to check the documentation. -Thor::Group ------------ +== Thor::Group Thor has a special class called Thor::Group. The main difference to Thor class is that it invokes all tasks at once. The example above could be rewritten in @@ -185,10 +173,10 @@ When invoked: thor counter -It prints "1 2 3" as well. Notice you should described (desc) only the class -and not each task anymore. Thor::Group is a great tool to create generators, -since you can define several steps which are invoked in the order they are -defined (Thor::Group is the tool use in generators in Rails 3.0). +It prints "1 2 3" as well. Notice you should describe (using the method <tt>desc</tt>) +only the class and not each task anymore. Thor::Group is a great tool to create +generators, since you can define several steps which are invoked in the order they +are defined (Thor::Group is the tool use in generators in Rails 3.0). Besides, Thor::Group can parse arguments and options as Thor tasks: @@ -218,17 +206,17 @@ The counter above expects one parameter and has the folling outputs: thor counter 11 # Prints "11 12 13" -You can also give options to Thor::Group, but instead of using `method_option` and -`method_options`, you should use `class_option` and `class_options`. Both argument -and class_options methods are available to Thor class as well. +You can also give options to Thor::Group, but instead of using <tt>method_option</tt> +and <tt>method_options</tt>, you should use <tt>class_option</tt> and <tt>class_options</tt>. +Both argument and class_options methods are available to Thor class as well. -Actions -------- +== Actions Thor comes with several actions which helps with script and generator tasks. You -might be familiar with them since some came from Rails Templates. They are: `say`, -`ask`, `yes?`, `no?`, `add_file`, `remove_file`, `copy_file`, `template`, -`directory`, `inside`, `run`, `inject_into_file` and a couple more. +might be familiar with them since some came from Rails Templates. They are: +<tt>say</tt>, <tt>ask</tt>, <tt>yes?</tt>, <tt>no?</tt>, <tt>add_file</tt>, +<tt>remove_file</tt>, <tt>copy_file</tt>, <tt>template</tt>, <tt>directory</tt>, +<tt>inside</tt>, <tt>run</tt>, <tt>inject_into_file</tt> and a couple more. To use them, you just need to include Thor::Actions in your Thor classes: @@ -241,7 +229,6 @@ Some actions like copy file requires that a class method called source_root is defined in your class. This is the directory where your templates should be placed. Be sure to check the documentation. -License -------- +== License See MIT LICENSE. diff --git a/railties/lib/vendor/thor-0.11.3/bin/rake2thor b/railties/lib/vendor/thor-0.11.5/bin/rake2thor index 50c7410d80..50c7410d80 100755 --- a/railties/lib/vendor/thor-0.11.3/bin/rake2thor +++ b/railties/lib/vendor/thor-0.11.5/bin/rake2thor diff --git a/railties/lib/vendor/thor-0.11.3/bin/thor b/railties/lib/vendor/thor-0.11.5/bin/thor index eaf849fb4a..eaf849fb4a 100755 --- a/railties/lib/vendor/thor-0.11.3/bin/thor +++ b/railties/lib/vendor/thor-0.11.5/bin/thor diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor.rb b/railties/lib/vendor/thor-0.11.5/lib/thor.rb index f65455cdda..8dfcfd4c5b 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor.rb @@ -101,8 +101,7 @@ class Thor # :required - If the argument is required or not. # :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, :boolean or :default. - # Default accepts arguments as booleans (--switch) or as strings (--switch=VALUE). + # :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. # @@ -160,39 +159,43 @@ class Thor raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task shell.say "Usage:" - shell.say " #{banner(task, options[:namespace])}" + shell.say " #{banner(task, options[:namespace], false)}" shell.say - class_options_help(shell, "Class") + 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 << if task.short_description - "\n # #{task.short_description}\n" - else - "\n" - end + item = [ banner(task, options[:namespace]) ] + item << "# #{task.short_description}" if task.short_description + item << " " end + options[:ident] ||= 2 if options[:short] - shell.print_table(list) + shell.print_list(list, :ident => options[:ident]) else shell.say "Tasks:" - shell.print_table(list) - class_options_help(shell, "Class") + shell.print_list(list, :ident => options[:ident]) end + + 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 + + class_options_help(shell, "Class") unless options[:short] end end protected # The banner for this class. You can customize it if you are invoking the - # thor class by another means which is not the Thor::Runner. It receives - # the task that is going to be invoked and if the namespace should be - # displayed. + # thor class by another ways which is not the Thor::Runner. It receives + # 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) #:nodoc: - task.formatted_usage(self, namespace) + def banner(task, namespace=true, show_options=true) + task.formatted_usage(self, namespace, show_options) end def baseclass #:nodoc: diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb index b8cfde1940..1d09dc38ae 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions.rb @@ -43,13 +43,11 @@ class Thor # 3) Parents source paths # def source_paths_for_search - @source_paths_for_search ||= begin - paths = [] - paths += self.source_paths - paths << self.source_root if self.respond_to?(:source_root) - paths += from_superclass(:source_paths, []) - paths - end + paths = [] + paths += self.source_paths + paths << self.source_root if self.respond_to?(:source_root) + paths += from_superclass(:source_paths, []) + paths end end @@ -60,8 +58,7 @@ class Thor # It also accepts :force, :skip and :pretend to set the behavior # and the respective option. # - # destination_root<String>:: The root directory needed for some actions. It's also known - # as destination root. + # destination_root<String>:: The root directory needed for some actions. # def initialize(args=[], options={}, config={}) self.behavior = case config[:behavior].to_s @@ -80,7 +77,7 @@ class Thor # Wraps an action object and call it accordingly to the thor class behavior. # - def action(instance) + def action(instance) #:nodoc: if behavior == :revoke instance.revoke! else @@ -110,18 +107,23 @@ class Thor remove_dot ? (path[2..-1] || '') : path end + # Holds source paths in instance so they can be manipulated. + # + def source_paths + @source_paths ||= self.class.source_paths_for_search + end + # 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) - paths = self.class.source_paths_for_search - paths.each do |source| + source_paths.each do |source| source_file = File.expand_path(file, File.join(source, relative_root)) return source_file if File.exists?(source_file) end - if paths.empty? + if source_paths.empty? raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " << "you can define a source_root in your class." else @@ -205,7 +207,7 @@ class Thor end say_status :run, desc, config.fetch(:verbose, true) - `#{command}` unless options[:pretend] + system(command) unless options[:pretend] end # Executes a ruby script (taking into account WIN32 platform quirks). diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb index 8f6badee27..8f6badee27 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/create_file.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/create_file.rb diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb index e33639f4e5..be5eb822ac 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/directory.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/directory.rb @@ -3,7 +3,7 @@ require 'thor/actions/empty_directory' class Thor module Actions - # Copies interactively the files from source directory to root directory. + # Copies recursively the files from source directory to root directory. # If any of the files finishes with .tt, it's considered to be a template # and is placed in the destination without the extension .tt. If any # empty directory is found, it's copied and all .empty_directory files are diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb index 03c1fe4af1..03c1fe4af1 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/empty_directory.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/empty_directory.rb diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb index 74c157ba8c..74c157ba8c 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/file_manipulation.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/file_manipulation.rb diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb index 089bd894e4..66dd1f5fc1 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/actions/inject_into_file.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/actions/inject_into_file.rb @@ -34,7 +34,7 @@ class Thor action InjectIntoFile.new(self, destination, data, config) end - class InjectIntoFile < EmptyDirectory + class InjectIntoFile < EmptyDirectory #:nodoc: attr_reader :flag, :replacement def initialize(base, destination, data, config) diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb index 0bdcc1f4d5..0fa87f8162 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/base.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/base.rb @@ -8,7 +8,10 @@ require 'thor/task' require 'thor/util' class Thor + # Shortcuts for help. HELP_MAPPINGS = %w(-h -? --help -D) + + # Thor methods that should not be overwritten by the user. THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root action add_file create_file in_root inside run run_ruby_script) @@ -78,7 +81,6 @@ class Thor # Whenever a class inherits from Thor or Thor::Group, we should track the # class and the file on Thor::Base. This is the method responsable for it. - # Also adds the source root to the source paths if the klass respond to it. # def register_klass_file(klass) #:nodoc: file = caller[1].match(/(.*):\d+/)[1] @@ -246,7 +248,8 @@ class Thor # Returns the tasks for this Thor class. # # ==== Returns - # OrderedHash:: An ordered hash with this class tasks. + # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task + # objects as values. # def tasks @tasks ||= Thor::CoreExt::OrderedHash.new @@ -255,7 +258,8 @@ class Thor # Returns the tasks for this Thor class and all subclasses. # # ==== Returns - # OrderedHash + # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task + # objects as values. # def all_tasks @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new) @@ -334,7 +338,7 @@ class Thor def namespace(name=nil) case name when nil - @namespace ||= Thor::Util.constant_to_namespace(self, false) + @namespace ||= Thor::Util.namespace_from_thor_class(self, false) else @namespace = name.to_s end @@ -342,7 +346,7 @@ class Thor # Default way to start generators from the command line. # - def start(given_args=ARGV, config={}) #:nodoc: + def start(given_args=ARGV, config={}) config[:shell] ||= Thor::Base.shell.new yield rescue Thor::Error => e @@ -360,7 +364,7 @@ class Thor # 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 lamda used to print values. The lambda + # The second is by returning a lambda used to print values. The lambda # requires two options: the group name and the array of options. # def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc: @@ -377,24 +381,14 @@ class Thor options.each do |option| item = [ option.usage(padding) ] - - item << if option.description - "# #{option.description}" - else - "" - end + item.push(option.description ? "# #{option.description}" : "") list << item list << [ "", "# Default: #{option.default}" ] if option.show_default? end unless list.empty? - if group_name - shell.say "#{group_name} options:" - else - shell.say "Options:" - end - + shell.say(group_name ? "#{group_name} options:" : "Options:") shell.print_table(list, :ident => 2) shell.say "" end @@ -412,7 +406,7 @@ class Thor # Raises an error if the word given is a Thor reserved word. # - def is_thor_reserved_word?(word, type) + def is_thor_reserved_word?(word, type) #:nodoc: return false unless THOR_RESERVED_WORDS.include?(word.to_s) raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}" end @@ -423,7 +417,7 @@ class Thor # name<Symbol>:: The name of the argument. # options<Hash>:: Described in both class_option and method_option. # - def build_option(name, options, scope) + def build_option(name, options, scope) #:nodoc: scope[name] = Thor::Option.new(name, options[:desc], options[:required], options[:type], options[:default], options[:banner], options[:group], options[:aliases]) @@ -437,7 +431,7 @@ class Thor # ==== Parameters # Hash[Symbol => Object] # - def build_options(options, scope) + def build_options(options, scope) #:nodoc: options.each do |key, value| scope[key] = Thor::Option.parse(key, value) end @@ -447,7 +441,7 @@ class Thor # class, just return it, otherwise dup it and add the fresh copy to the # current task hash. # - def find_and_refresh_task(name) + def find_and_refresh_task(name) #:nodoc: task = if task = tasks[name.to_s] task elsif task = all_tasks[name.to_s] @@ -465,7 +459,7 @@ class Thor end # Fire this callback whenever a method is added. Added methods are - # tracked as tasks if the requirements set by valid_task? are valid. + # tracked as tasks by invoking the create_task method. # def method_added(meth) meth = meth.to_s @@ -486,7 +480,7 @@ class Thor end # Retrieves a value from superclass. If it reaches the baseclass, - # returns nil. + # returns default. # def from_superclass(method, default=nil) if self == baseclass || !superclass.respond_to?(method, true) diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb index 3213961fe4..78bc5cf4bf 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/hash_with_indifferent_access.rb @@ -1,5 +1,5 @@ class Thor - module CoreExt + module CoreExt #:nodoc: # A hash with indifferent access and magic predicates. # @@ -9,7 +9,7 @@ class Thor # hash['foo'] #=> 'bar' # hash.foo? #=> true # - class HashWithIndifferentAccess < ::Hash + class HashWithIndifferentAccess < ::Hash #:nodoc: def initialize(hash={}) super() diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb index 5e4ad5609f..27fea5bb35 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/core_ext/ordered_hash.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/core_ext/ordered_hash.rb @@ -1,6 +1,4 @@ -require 'forwardable' - -class Thor #:nodoc: +class Thor module CoreExt #:nodoc: if RUBY_VERSION >= '1.9' diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb new file mode 100644 index 0000000000..f9b31a35d1 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/error.rb @@ -0,0 +1,27 @@ +class Thor + # Thor::Error is raised when it's caused by wrong usage of thor classes. Those + # errors have their backtrace supressed and are nicely shown to the user. + # + # Errors that are caused by the developer, like declaring a method which + # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we + # ensure that developer errors are shown with full backtrace. + # + class Error < StandardError + end + + # Raised when a task was not found. + # + class UndefinedTaskError < Error + end + + # Raised when a task was found, but not invoked properly. + # + class InvocationError < Error + end + + class RequiredArgumentMissingError < InvocationError + end + + class MalformattedArgumentError < InvocationError + end +end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb index 1be1c35ba5..1e59df2313 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/group.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/group.rb @@ -221,9 +221,9 @@ class Thor::Group protected # The banner for this class. You can customize it if you are invoking the - # thor class by another means which is not the Thor::Runner. + # thor class by another ways which is not the Thor::Runner. # - def banner #:nodoc: + def banner "#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}" end @@ -244,7 +244,7 @@ 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) + def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc: shell.padding += 1 result = if block_given? diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb index 34e7a4b911..c0388dd863 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/invocation.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/invocation.rb @@ -11,7 +11,7 @@ class Thor def prepare_for_invocation(key, name) #:nodoc: case name when Symbol, String - Thor::Util.namespace_to_thor_class(name.to_s, false) + Thor::Util.namespace_to_thor_class_and_task(name.to_s, false) else name end @@ -96,20 +96,8 @@ class Thor task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array) args, opts, config = nil, args, opts if args.is_a?(Hash) - object, task = _prepare_for_invocation(name, task) - if object.is_a?(Class) - klass = object - - stored_args, stored_opts, stored_config = @_initializer - args ||= stored_args.dup - opts ||= stored_opts.dup - - config ||= {} - config = stored_config.merge(_shared_configuration).merge!(config) - instance = klass.new(args, opts, config) - else - klass, instance = object.class, object - end + object, task = _prepare_for_invocation(name, task) + klass, instance = _initialize_klass_with_initializer(object, args, opts, config) method_args = [] current = @_invocations[klass] @@ -134,7 +122,7 @@ class Thor # Configuration values that are shared between invocations. # - def _shared_configuration + def _shared_configuration #:nodoc: { :invocations => @_invocations } end @@ -154,13 +142,13 @@ class Thor # If the object was not set, use self and use the name as task. object, task = self, name unless object - return object, _validate_klass_and_task(object, task) + return object, _validate_task(object, task) end # Check if the object given is a Thor class object and get a task object # for it. # - def _validate_klass_and_task(object, task) #:nodoc: + def _validate_task(object, task) #:nodoc: klass = object.is_a?(Class) ? object : object.class raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base @@ -168,5 +156,23 @@ class Thor task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task) task end + + # Initialize klass using values stored in the @_initializer. + # + def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc: + if object.is_a?(Class) + klass = object + + stored_args, stored_opts, stored_config = @_initializer + args ||= stored_args.dup + opts ||= stored_opts.dup + + config ||= {} + config = stored_config.merge(_shared_configuration).merge!(config) + [ klass, klass.new(args, opts, config) ] + else + [ object.class, object ] + end + end end end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb index 57a3f6e1a5..57a3f6e1a5 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser.rb diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb index 2d7f4dbafb..aa8ace4719 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/argument.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/argument.rb @@ -1,5 +1,5 @@ class Thor - class Argument + class Argument #:nodoc: VALID_TYPES = [ :numeric, :hash, :array, :string ] attr_reader :name, :description, :required, :type, :default, :banner diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb index 9a2262d6f7..fb5d965e06 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/arguments.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/arguments.rb @@ -1,5 +1,5 @@ class Thor - class Arguments + class Arguments #:nodoc: NUMERIC = /(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb index 5c43f6b18f..9e40ec73fa 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/option.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/option.rb @@ -1,5 +1,5 @@ class Thor - class Option < Argument + class Option < Argument #:nodoc: attr_reader :aliases, :group VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb index 01c86b7b27..75092308b5 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/parser/options.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/parser/options.rb @@ -2,7 +2,7 @@ class Thor # This is a modified version of Daniel Berger's Getopt::Long class, licensed # under Ruby's license. # - class Options < Arguments + class Options < Arguments #:nodoc: LONG_RE = /^(--\w+[-\w+]*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i diff --git a/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb new file mode 100644 index 0000000000..3ab6bb21f5 --- /dev/null +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/rake_compat.rb @@ -0,0 +1,67 @@ +require 'rake' + +class Thor + # Adds a compatibility layer to your Thor classes which allows you to use + # rake package tasks. For example, to use rspec rake tasks, one can do: + # + # require 'thor/rake_compat' + # + # class Default < Thor + # include Thor::RakeCompat + # + # Spec::Rake::SpecTask.new(:spec) do |t| + # t.spec_opts = ['--options', "spec/spec.opts"] + # t.spec_files = FileList['spec/**/*_spec.rb'] + # end + # end + # + module RakeCompat + def self.rake_classes + @rake_classes ||= [] + end + + def self.included(base) + # Hack. Make rakefile point to invoker, so rdoc task is generated properly. + Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1]) + self.rake_classes << base + end + end +end + +class Object #:nodoc: + alias :rake_task :task + alias :rake_namespace :namespace + + def task(*args, &block) + task = rake_task(*args, &block) + + if klass = Thor::RakeCompat.rake_classes.last + non_namespaced_name = task.name.split(':').last + + description = non_namespaced_name + description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ') + description.strip! + + klass.desc description, task.comment || non_namespaced_name + klass.class_eval <<-METHOD + def #{non_namespaced_name}(#{task.arg_names.join(', ')}) + Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')}) + end + METHOD + end + + task + end + + def namespace(name, &block) + if klass = Thor::RakeCompat.rake_classes.last + const_name = Thor::Util.camel_case(name.to_s).to_sym + klass.const_set(const_name, Class.new(Thor)) + new_klass = klass.const_get(const_name) + Thor::RakeCompat.rake_classes << new_klass + end + + rake_namespace(name, &block) + Thor::RakeCompat.rake_classes.pop + end +end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb index 6782c61dec..3639ac0aa9 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/runner.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/runner.rb @@ -4,7 +4,7 @@ require 'yaml' require 'digest/md5' require 'pathname' -class Thor::Runner < Thor +class Thor::Runner < Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update # Override Thor#help so it can give information about any class and any method. @@ -12,8 +12,9 @@ class Thor::Runner < Thor def help(meth=nil) if meth && !self.respond_to?(meth) initialize_thorfiles(meth) - klass, task = Thor::Util.namespace_to_thor_class(meth) - klass.start(["-h", task].compact, :shell => self.shell) # send mapping -h because it works with Thor::Group too + klass, task = Thor::Util.namespace_to_thor_class_and_task(meth) + # Send mapping -h because it works with Thor::Group too + klass.start(["-h", task].compact, :shell => self.shell) else super end @@ -25,12 +26,12 @@ class Thor::Runner < Thor def method_missing(meth, *args) meth = meth.to_s initialize_thorfiles(meth) - klass, task = Thor::Util.namespace_to_thor_class(meth) + klass, task = Thor::Util.namespace_to_thor_class_and_task(meth) args.unshift(task) if task klass.start(args, :shell => shell) end - desc "install NAME", "Install a Thor file into your system tasks, optionally named for future updates" + desc "install NAME", "Install an optionally named Thor file into your system tasks" method_options :as => :string, :relative => :boolean def install(name) initialize_thorfiles @@ -76,7 +77,7 @@ class Thor::Runner < Thor thor_yaml[as] = { :filename => Digest::MD5.hexdigest(name + as), :location => location, - :namespaces => Thor::Util.namespaces_in_contents(contents, base) + :namespaces => Thor::Util.namespaces_in_content(contents, base) } save_yaml(thor_yaml) @@ -130,8 +131,7 @@ class Thor::Runner < Thor display_klasses(true, klasses) end - desc "list [SEARCH]", - "List the available thor tasks (--substring means SEARCH anywhere in the namespace)" + desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)" method_options :substring => :boolean, :group => :string, :all => :boolean def list(search="") initialize_thorfiles @@ -269,6 +269,10 @@ class Thor::Runner < Thor end unless klasses.empty? + klasses.dup.each do |klass| + klasses -= Thor::Util.thor_classes_in(klass) + end + klasses.each { |k| display_tasks(k) } else say "\033[1;34mNo Thor tasks available\033[0m" @@ -285,7 +289,7 @@ class Thor::Runner < Thor say shell.set_color(base, color, true) say "-" * base.length - klass.help(shell, :short => true, :namespace => true) + klass.help(shell, :short => true, :ident => 0, :namespace => true) end end end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb index 7ed4a24bfb..0d3f4d5951 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell.rb @@ -64,7 +64,7 @@ class Thor # Allow shell to be shared between invocations. # - def _shared_configuration + def _shared_configuration #:nodoc: super.merge!(:shell => self.shell) end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb index e294c87567..3c02e47c33 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/basic.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/basic.rb @@ -11,9 +11,9 @@ class Thor @base, @padding = nil, 0 end - # Do not allow padding to be less than zero. + # Sets the output padding, not allowing less than zero values. # - def padding=(value) #:nodoc: + def padding=(value) @padding = [0, value].max end @@ -52,7 +52,7 @@ class Thor # in log_status, avoiding the message from being shown. If a Symbol is # given in log_status, it's used as the color. # - def say_status(status, message, log_status=true) #:nodoc: + def say_status(status, message, log_status=true) return if quiet? || log_status == false spaces = " " * (padding + 1) color = log_status.is_a?(Symbol) ? log_status : :green @@ -80,17 +80,21 @@ class Thor # # ==== Parameters # list<Array[String, String, ...]> - # mode<Symbol>:: Can be :rows or :inline. Defaults to :rows. # - def print_list(list, mode=:rows) + # ==== 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? - content = case mode + ident = " " * (options[:ident] || 0) + content = case options[:mode] when :inline last = list.pop "#{list.join(", ")}, and #{last}" else # rows - list.join("\n") + ident + list.join("\n#{ident}") end $stdout.puts content @@ -103,7 +107,6 @@ class Thor # # ==== Options # ident<Integer>:: Ident the first column by ident value. - # emphasize_last<Boolean>:: When true, add a different behavior to the last column. # def print_table(table, options={}) return if table.empty? @@ -131,7 +134,7 @@ class Thor # # ==== Parameters # destination<String>:: the destination file to solve conflicts - # block<Proc>:: an optional proc that returns the value to be used in diff + # block<Proc>:: an optional block that returns the value to be used in diff # def file_collision(destination) return true if @always_force @@ -164,19 +167,20 @@ class Thor # wrong, you can always raise an exception. If you raise a Thor::Error, it # will be rescued and wrapped in the method below. # - def error(statement) #:nodoc: + def error(statement) $stderr.puts statement end - # Apply color to the given string with optional bold. + # Apply color to the given string with optional bold. Disabled in the + # Thor::Shell::Basic class. # - def set_color(string, color, bold=false) + def set_color(string, color, bold=false) #:nodoc: string end protected - def is?(value) + def is?(value) #:nodoc: value = value.to_s if value.size == 1 @@ -186,7 +190,7 @@ class Thor end end - def file_collision_help + def file_collision_help #:nodoc: <<HELP Y - yes, overwrite n - no, do not overwrite @@ -197,17 +201,17 @@ h - help, show this help HELP end - def show_diff(destination, content) + def show_diff(destination, content) #:nodoc: diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u' Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp| temp.write content temp.rewind - say `#{diff_cmd} "#{destination}" "#{temp.path}"` + system %(#{diff_cmd} "#{destination}" "#{temp.path}") end end - def quiet? + def quiet? #:nodoc: base && base.options[:quiet] end diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb index be7995146a..24704f7885 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/shell/color.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/shell/color.rb @@ -2,7 +2,8 @@ require 'thor/shell/basic' class Thor module Shell - # Set color in the output. Got color values from HighLine. + # Inherit from Thor::Shell::Basic and add set_color behavior. Check + # Thor::Shell::Basic to see all available methods. # class Color < Basic # Embed in a String to clear all previous ANSI sequences. @@ -44,9 +45,10 @@ class Thor # Set the terminal's background ANSI color to white. ON_WHITE = "\e[47m" - # Set color by using a string or one of the defined constants. Based - # on Highline implementation. CLEAR is automatically be embedded to - # the end of the returned String. + # Set color by using a string or one of the defined constants. If a third + # option is set to true, it also adds bold to the string. This is based + # on Highline implementation and it automatically appends CLEAR to the end + # of the returned String. # def set_color(string, color, bold=false) color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) @@ -59,7 +61,7 @@ class Thor # Overwrite show_diff to show diff with colors if Diff::LCS is # available. # - def show_diff(destination, content) + 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") content = content.to_s.split("\n") @@ -72,7 +74,7 @@ class Thor end end - def output_diff_line(diff) + def output_diff_line(diff) #:nodoc: case diff.action when '-' say "- #{diff.old_element.chomp}", :red, true @@ -89,7 +91,7 @@ class Thor # Check if Diff::LCS is loaded. If it is, use it to create pretty output # for diff. # - def diff_lcs_loaded? + def diff_lcs_loaded? #:nodoc: return true if defined?(Diff::LCS) return @diff_lcs_loaded unless @diff_lcs_loaded.nil? diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/task.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb index 92c0776c04..23d35b883c 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/task.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/task.rb @@ -12,7 +12,7 @@ class Thor super(name.to_s, description, usage, options || {}) end - def initialize_copy(other) + def initialize_copy(other) #:nodoc: super(other) self.options = other.options.dup if other.options end @@ -36,11 +36,19 @@ class Thor # Returns the formatted usage. If a class is given, the class arguments are # injected in the usage. # - def formatted_usage(klass=nil, namespace=false) + def formatted_usage(klass=nil, namespace=false, show_options=true) formatted = '' - formatted << "#{klass.namespace.gsub(/^default/,'')}:" if klass && namespace + + formatted = if namespace.is_a?(String) + "#{namespace}:" + elsif klass && namespace + "#{klass.namespace.gsub(/^default/,'')}:" + else + "" + end + formatted << formatted_arguments(klass) - formatted << " #{formatted_options}" + formatted << " #{formatted_options}" if show_options formatted.strip! formatted end @@ -67,20 +75,20 @@ class Thor # Given a target, checks if this class name is not a private/protected method. # - def public_method?(instance) + def public_method?(instance) #:nodoc: collection = instance.private_methods + instance.protected_methods !(collection).include?(name.to_s) && !(collection).include?(name.to_sym) # For Ruby 1.9 end # Clean everything that comes from the Thor gempath and remove the caller. # - def sans_backtrace(backtrace, caller) + def sans_backtrace(backtrace, caller) #:nodoc: dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/ saned = backtrace.reject { |frame| frame =~ dirname } saned -= caller end - def parse_argument_error(instance, e, caller) + def parse_argument_error(instance, e, caller) #:nodoc: backtrace = sans_backtrace(e.backtrace, caller) if backtrace.empty? && e.message =~ /wrong number of arguments/ @@ -95,7 +103,7 @@ class Thor end end - def parse_no_method_error(instance, e) + def parse_no_method_error(instance, e) #:nodoc: if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ raise UndefinedTaskError, "The #{instance.class.namespace} namespace " << "doesn't have a '#{name}' task" diff --git a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb index 26db24aadb..4938dc4aca 100644 --- a/railties/lib/vendor/thor-0.11.3/lib/thor/util.rb +++ b/railties/lib/vendor/thor-0.11.5/lib/thor/util.rb @@ -1,14 +1,14 @@ require 'rbconfig' class Thor - module Sandbox; end + module Sandbox #:nodoc: + end # This module holds several utilities: # # 1) Methods to convert thor namespaces to constants and vice-versa. # - # Thor::Utils.constant_to_namespace(Foo::Bar::Baz) #=> "foo:bar:baz" - # Thor::Utils.namespace_to_constant("foo:bar:baz") #=> Foo::Bar::Baz + # Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz" # # 2) Loading thor files and sandboxing: # @@ -43,15 +43,15 @@ class Thor # ==== Returns # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" # - def self.constant_to_namespace(constant, remove_default=true) + def self.namespace_from_thor_class(constant, remove_default=true) constant = constant.to_s.gsub(/^Thor::Sandbox::/, "") constant = snake_case(constant).squeeze(":") constant.gsub!(/^default/, '') if remove_default constant end - # Given the contents, evaluate it inside the sandbox and returns the thor - # classes defined in the sandbox. + # Given the contents, evaluate it inside the sandbox and returns the + # namespaces defined in the sandbox. # # ==== Parameters # contents<String> @@ -59,7 +59,7 @@ class Thor # ==== Returns # Array[Object] # - def self.namespaces_in_contents(contents, file=__FILE__) + def self.namespaces_in_content(contents, file=__FILE__) old_constants = Thor::Base.subclasses.dup Thor::Base.subclasses.clear @@ -73,6 +73,14 @@ class Thor new_constants end + # Returns the thor classes declared inside the given class. + # + def self.thor_classes_in(klass) + Thor::Base.subclasses.select do |subclass| + klass.constants.include?(subclass.name.gsub("#{klass.name}::", '')) + end + end + # Receives a string and convert it to snake case. SnakeCase returns snake_case. # # ==== Parameters @@ -87,6 +95,19 @@ class Thor return $+.downcase end + # Receives a string and convert it to camel case. camel_case returns CamelCase. + # + # ==== Parameters + # String + # + # ==== Returns + # String + # + def self.camel_case(str) + return str if str !~ /_/ && str =~ /[A-Z]+.*/ + str.split('_').map { |i| i.capitalize }.join + end + # Receives a namespace and tries to retrieve a Thor or Thor::Group class # from it. It first searches for a class using the all the given namespace, # if it's not found, removes the highest entry and searches for the class @@ -115,7 +136,7 @@ class Thor # Thor::Error:: raised if the namespace evals to a class which does not # inherit from Thor or Thor::Group. # - def self.namespace_to_thor_class(namespace, raise_if_nil=true) + def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true) klass, task_name = Thor::Util.find_by_namespace(namespace), nil if klass.nil? && namespace.include?(?:) @@ -156,7 +177,7 @@ class Thor yaml.each do |k, v| next unless v[:constants] && v[:namespaces].nil? yaml_changed = true - yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.constant_to_namespace(c)} + yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)} end yaml_changed @@ -214,7 +235,7 @@ class Thor # Return the path to the ruby interpreter taking into account multiple # installations and windows extensions. # - def self.ruby_command #:nodoc: + def self.ruby_command @ruby_command ||= begin ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']) ruby << Config::CONFIG['EXEEXT'] diff --git a/railties/test/fixtures/lib/template.rb b/railties/test/fixtures/lib/template.rb new file mode 100644 index 0000000000..c14a1a8784 --- /dev/null +++ b/railties/test/fixtures/lib/template.rb @@ -0,0 +1 @@ +say "It works from file!" diff --git a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb index 191bdbf2fc..191bdbf2fc 100644 --- a/railties/test/fixtures/vendor/gems/mspec/lib/generators/mspec_generator.rb +++ b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb diff --git a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb index 6aa7cb052e..6aa7cb052e 100644 --- a/railties/test/fixtures/vendor/gems/wrong/lib/generators/wrong_generator.rb +++ b/railties/test/fixtures/vendor/gems/gems/wrong/lib/generators/wrong_generator.rb diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c794a2ade6..19e41c15c8 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -131,6 +131,11 @@ class AppGeneratorTest < GeneratorsTestCase assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/ end + def test_template_from_dir_pwd + FileUtils.cd(RAILS_ROOT) + assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"]) + end + def test_template_raises_an_error_with_invalid_path content = capture(:stderr){ run_generator(["-m", "non/existant/path"]) } assert_match /The template \[.*\] could not be loaded/, content diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 011bd518f8..9444a9ed4b 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -20,7 +20,7 @@ class GeneratorsTestCase < Test::Unit::TestCase def destination_root @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__), - '..', '..', 'fixtures', 'tmp')) + '..', 'fixtures', 'tmp')) end def setup diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 024ea439ef..834e43e776 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -99,7 +99,7 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase def test_error_is_shown_if_orm_does_not_provide_interface error = capture(:stderr){ run_generator ["User", "--orm=unknown"] } - assert_equal "Could not load Unknown::Generators::ActionORM, skipping controller. " << + assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " << "Error: no such file to load -- generators/unknown.\n", error end |