diff options
189 files changed, 3040 insertions, 1252 deletions
@@ -31,6 +31,11 @@ platforms :mri_18 do gem 'ruby-prof' end +platforms :mri_19 do + # TODO: Remove the conditional when ruby-debug19 supports Ruby >= 1.9.3 + gem "ruby-debug19" if RUBY_VERSION < "1.9.3" +end + platforms :ruby do gem 'json' gem 'yajl-ruby' diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 9c073dce1f..167c1da9c5 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -2,6 +2,10 @@ * No changes +*Rails 3.0.2 (unreleased)* + +* No changes + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 2d133ca0b2..db02beee35 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -23,6 +23,10 @@ * Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. [Yehuda Katz, Carl Lerche] +*Rails 3.0.2 (unreleased)* + +* The helper number_to_currency accepts a new :negative_format option to be able to configure how to render negative amounts. [Don Wilson] + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 7b0d80614d..f169ab7c3a 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -81,27 +81,29 @@ module AbstractController class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 # Append a before, after or around filter. See _insert_callbacks # for details on the allowed parameters. - def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options} - set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before_filter, name, options) - end # end - end # end + def #{filter}_filter(*names, &blk) + _insert_callbacks(names, blk) do |name, options| + options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} + set_callback(:process_action, :#{filter}, name, options) + end + end # Prepend a before, after or around filter. See _insert_callbacks # for details on the allowed parameters. - def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) - end # end - end # end + def prepend_#{filter}_filter(*names, &blk) + _insert_callbacks(names, blk) do |name, options| + options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after} + set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) + end + end # Skip a before, after or around filter. See _insert_callbacks # for details on the allowed parameters. - def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options) - end # end - end # end + def skip_#{filter}_filter(*names, &blk) + _insert_callbacks(names, blk) do |name, options| + skip_callback(:process_action, :#{filter}, name, options) + end + end # *_filter is the same as append_*_filter alias_method :append_#{filter}_filter, :#{filter}_filter diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index c6d4c6d936..f7dd0dcb69 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -227,7 +227,7 @@ module ActionController #:nodoc: "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? if response = retrieve_response_from_mimes(&block) - options = resources.extract_options! + options = resources.size == 1 ? {} : resources.extract_options! options.merge!(:default_response => response) (options.delete(:responder) || self.class.responder).call(self, resources, options) end diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index 4b8c452d50..f4efeb33ba 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -14,18 +14,9 @@ module ActionController cookies.write(@_response) end @_response.prepare! - set_test_assigns ret end - def set_test_assigns - @assigns = {} - (instance_variable_names - self.class.protected_instance_variables).each do |var| - name, value = var[1..-1], instance_variable_get(var) - @assigns[name] = value - end - end - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 0c26071379..2b2f647d32 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -411,8 +411,9 @@ module ActionController @controller.request = @request @controller.params.merge!(parameters) build_request_uri(action, parameters) - Base.class_eval { include Testing } + @controller.class.class_eval { include Testing } @controller.process_with_new_base_test(@request, @response) + @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {} @request.session.delete('flash') if @request.session['flash'].blank? @response end @@ -448,7 +449,7 @@ module ActionController def build_request_uri(action, parameters) unless @request.env["PATH_INFO"] - options = @controller.__send__(:url_options).merge(parameters) + options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters options.update( :only_path => true, :action => action, diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 55a3166f6d..08f30e068d 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -4,6 +4,7 @@ require 'strscan' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/string/access' +require 'active_support/inflector' require 'action_dispatch/http/headers' module ActionDispatch @@ -44,8 +45,24 @@ module ActionDispatch @env.key?(key) end - HTTP_METHODS = %w(get head put post delete options) - HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h } + # List of HTTP request methods from the following RFCs: + # Hypertext Transfer Protocol -- HTTP/1.1 (http://www.ietf.org/rfc/rfc2616.txt) + # HTTP Extensions for Distributed Authoring -- WEBDAV (http://www.ietf.org/rfc/rfc2518.txt) + # Versioning Extensions to WebDAV (http://www.ietf.org/rfc/rfc3253.txt) + # Ordered Collections Protocol (WebDAV) (http://www.ietf.org/rfc/rfc3648.txt) + # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (http://www.ietf.org/rfc/rfc3744.txt) + # Web Distributed Authoring and Versioning (WebDAV) SEARCH (http://www.ietf.org/rfc/rfc5323.txt) + # PATCH Method for HTTP (http://www.ietf.org/rfc/rfc5789.txt) + RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) + RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) + RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) + RFC3648 = %w(ORDERPATCH) + RFC3744 = %w(ACL) + RFC5323 = %w(SEARCH) + RFC5789 = %w(PATCH) + + HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789 + HTTP_METHOD_LOOKUP = Hash.new { |h, m| h[m] = m.underscore.to_sym if HTTP_METHODS.include?(m) } # Returns the HTTP \method that the application should see. # In the case where the \method was overridden by a middleware diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 2eb2af7a11..63a22ad105 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,6 +1,7 @@ require 'erb' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/blank' +require 'active_support/inflector' module ActionDispatch module Routing @@ -186,8 +187,8 @@ module ActionDispatch def request_method_condition if via = @options[:via] - via = Array(via).map { |m| m.to_s.upcase } - { :request_method => Regexp.union(*via) } + via = Array(via).map { |m| m.to_s.dasherize.upcase } + { :request_method => %r[^#{via.join('|')}$] } else { } end diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb index f91a48e16c..08a8408f25 100644 --- a/actionpack/lib/action_dispatch/routing/route.rb +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -30,7 +30,8 @@ module ActionDispatch if method = conditions[:request_method] case method when Regexp - method.source.upcase + source = method.source.upcase + source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source else method.to_s.upcase end diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index c56ebc6438..16f3674164 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -22,7 +22,7 @@ module ActionDispatch end def cookies - @request.cookies.merge(@response.cookies) + HashWithIndifferentAccess.new(@request.cookies.merge(@response.cookies)) end def redirect_to_url diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index a728fde748..db5f5f301a 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -878,7 +878,7 @@ module ActionView end def join_asset_file_contents(paths) - paths.collect { |path| File.read(asset_file_path!(path)) }.join("\n\n") + paths.collect { |path| File.read(asset_file_path!(path, true)) }.join("\n\n") end def write_asset_file_contents(joined_asset_path, asset_paths) @@ -896,8 +896,10 @@ module ActionView File.join(config.assets_dir, path.split('?').first) end - def asset_file_path!(path) - unless is_uri?(path) + def asset_file_path!(path, error_if_file_is_uri = false) + if is_uri?(path) + raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri + else absolute_path = asset_file_path(path) raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path) return absolute_path diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 0401e6a09b..c88bd1efd5 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/string/output_safety' module ActionView # = Action View Capture Helper @@ -38,7 +39,7 @@ module ActionView value = nil buffer = with_output_buffer { value = yield(*args) } if string = buffer.presence || value and string.is_a?(String) - string + ERB::Util.html_escape string end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index c1214bc44e..875ec9b77b 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -882,6 +882,8 @@ module ActionView # Returns the separator for a given datetime component def separator(type) case type + when :year + @options[:discard_year] ? "" : @options[:date_separator] when :month @options[:discard_month] ? "" : @options[:date_separator] when :day diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index d6e175c7e8..8c300ec745 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -112,6 +112,7 @@ module ActionView # <%= f.text_field :version %><br /> # <%= f.label :author, 'Author' %>: # <%= f.text_field :author %><br /> + # <%= f.submit %> # <% end %> # # There, +form_for+ is able to generate the rest of RESTful form @@ -129,6 +130,7 @@ module ActionView # Last name : <%= f.text_field :last_name %><br /> # Biography : <%= f.text_area :biography %><br /> # Admin? : <%= f.check_box :admin %><br /> + # <%= f.submit %> # <% end %> # # There, the argument is a symbol or string with the name of the @@ -160,6 +162,7 @@ module ActionView # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> + # <%= f.submit %> # <% end %> # # This also works for the methods in FormOptionHelper and DateHelper that @@ -269,8 +272,9 @@ module ActionView # <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> - # <%= text_area :person, :biography %> - # <%= check_box_tag "person[admin]", @person.company.admin? %> + # <%= f.text_area :biography %> + # <%= f.check_box :admin %> + # <%= f.submit %> # <% end %> # # In this case, if you use this: @@ -294,10 +298,9 @@ module ActionView # # If you don't need to attach a form to a model instance, then check out # FormTagHelper#form_tag. - def form_for(record, options = nil, &proc) + def form_for(record, options = {}, &proc) raise ArgumentError, "Missing block" unless block_given? - options ||= {} options[:html] ||= {} case record @@ -348,6 +351,8 @@ module ActionView # <%= fields_for @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> + # + # <%= f.submit %> # <% end %> # # ...or if you have an object that needs to be represented as a different @@ -409,6 +414,7 @@ module ActionView # Street : <%= address_fields.text_field :street %> # Zip code: <%= address_fields.text_field :zip_code %> # <% end %> + # ... # <% end %> # # When address is already an association on a Person you can use @@ -438,6 +444,7 @@ module ActionView # ... # Delete: <%= address_fields.check_box :_destroy %> # <% end %> + # ... # <% end %> # # ==== One-to-many @@ -467,6 +474,7 @@ module ActionView # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> + # ... # <% end %> # # It's also possible to specify the instance to be used: @@ -480,6 +488,7 @@ module ActionView # <% end %> # <% end %> # <% end %> + # ... # <% end %> # # Or a collection to be used: @@ -489,6 +498,7 @@ module ActionView # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> + # ... # <% end %> # # When projects is already an association on Person you can use @@ -518,6 +528,7 @@ module ActionView # <%= person_form.fields_for :projects do |project_fields| %> # Delete: <%= project_fields.check_box :_destroy %> # <% end %> + # ... # <% end %> def fields_for(record, record_object = nil, options = nil, &block) capture(instantiate_builder(record, record_object, options, &block), &block) @@ -1206,6 +1217,7 @@ module ActionView self.multipart = true @template.file_field(@object_name, method, objectify_options(options)) end + # Add the submit button for the given form. When no value is given, it checks # if the object is a new resource or not to create the proper label: # @@ -1278,11 +1290,11 @@ module ActionView if association.respond_to?(:persisted?) association = [association] if @object.send(association_name).is_a?(Array) - elsif !association.is_a?(Array) + elsif !association.respond_to?(:to_ary) association = @object.send(association_name) end - if association.is_a?(Array) + if association.respond_to?(:to_ary) explicit_child_index = options[:child_index] output = ActiveSupport::SafeBuffer.new association.each do |child| diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 1488e72c6e..a9400c347f 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -15,7 +15,7 @@ module ActionView # unchanged if can't be converted into a valid number. module NumberHelper - DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :unit => "$", :separator => ".", :delimiter => ",", + DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",", :precision => 2, :significant => false, :strip_insignificant_zeros => false } # Raised when argument +number+ param given to the helpers is invalid and @@ -81,15 +81,18 @@ module ActionView # in the +options+ hash. # # ==== Options - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:precision</tt> - Sets the level of precision (defaults to 2). - # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$"). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>:format</tt> - Sets the format of the output string (defaults to "%u%n"). The field types are: - # - # %u The currency unit - # %n The number + # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). + # * <tt>:precision</tt> - Sets the level of precision (defaults to 2). + # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$"). + # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). + # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n"). + # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt> + # for the number. + # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending + # an hyphen to the formatted number given by <tt>:format</tt>). + # Accepts the same fields than <tt>:format</tt>, except + # <tt>%n</tt> is here the absolute value of the number. # # ==== Examples # number_to_currency(1234567890.50) # => $1,234,567,890.50 @@ -97,6 +100,8 @@ module ActionView # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506 # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 € # + # number_to_currency(1234567890.50, :negative_format => "(%u%n)") + # # => ($1,234,567,890.51) # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "") # # => £1234567890,50 # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") @@ -110,11 +115,17 @@ module ActionView currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {}) defaults = DEFAULT_CURRENCY_VALUES.merge(defaults).merge!(currency) + defaults[:negative_format] = "-" + options[:format] if options[:format] options = defaults.merge!(options) unit = options.delete(:unit) format = options.delete(:format) + if number.to_f < 0 + format = options.delete(:negative_format) + number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '') + end + begin value = number_with_precision(number, options.merge(:raise => true)) format.gsub(/%n/, value).gsub(/%u/, unit).html_safe diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index c580397cad..317479ad4c 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -90,13 +90,14 @@ module ActionView def collection if @options.key?(:collection) - @options[:collection] || [] + collection = @options[:collection] + collection.respond_to?(:to_ary) ? collection.to_ary : [] end end def collection_from_object if @object.respond_to?(:to_ary) - @object + @object.to_ary end end @@ -163,4 +164,4 @@ module ActionView [variable, variable_counter] end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index b827610456..0803114f44 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -15,6 +15,7 @@ module ActionView super(value.to_s) end alias :append= :<< + alias :safe_append= :safe_concat end class Template @@ -40,7 +41,11 @@ module ActionView end def add_expr_escaped(src, code) - src << '@output_buffer.append= ' << escaped_expr(code) << ';' + if code =~ BLOCK_EXPR + src << "@output_buffer.safe_append= " << code + else + src << "@output_buffer.safe_concat((" << code << ").to_s);" + end end def add_postamble(src) diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index f5811a1530..7c595d1b89 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -27,6 +27,12 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest head :ok end + def renew + env["rack.session.options"][:renew] = true + session[:foo] = "baz" + head :ok + end + def rescue_action(e) raise end end @@ -64,6 +70,20 @@ class ActiveRecordStoreTest < ActionDispatch::IntegrationTest end end end + + define_method("test_renewing_with_#{class_name}_store") do + with_store class_name do + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + + get '/renew' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + end + end + end end def test_getting_nil_session_value diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 3a8a37d967..68febf425d 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -78,7 +78,8 @@ class FilterTest < ActionController::TestCase end class RenderingController < ActionController::Base - before_filter :render_something_else + before_filter :before_filter_rendering + after_filter :unreached_after_filter def show @ran_action = true @@ -86,9 +87,59 @@ class FilterTest < ActionController::TestCase end private - def render_something_else + def before_filter_rendering + @ran_filter ||= [] + @ran_filter << "before_filter_rendering" render :inline => "something else" end + + def unreached_after_filter + @ran_filter << "unreached_after_filter_after_render" + end + end + + class RenderingForPrependAfterFilterController < RenderingController + prepend_after_filter :unreached_prepend_after_filter + + private + def unreached_prepend_after_filter + @ran_filter << "unreached_preprend_after_filter_after_render" + end + end + + class BeforeFilterRedirectionController < ActionController::Base + before_filter :before_filter_redirects + after_filter :unreached_after_filter + + def show + @ran_action = true + render :inline => "ran show action" + end + + def target_of_redirection + @ran_target_of_redirection = true + render :inline => "ran target_of_redirection action" + end + + private + def before_filter_redirects + @ran_filter ||= [] + @ran_filter << "before_filter_redirects" + redirect_to(:action => 'target_of_redirection') + end + + def unreached_after_filter + @ran_filter << "unreached_after_filter_after_redirection" + end + end + + class BeforeFilterRedirectionForPrependAfterFilterController < BeforeFilterRedirectionController + prepend_after_filter :unreached_prepend_after_filter_after_redirection + + private + def unreached_prepend_after_filter_after_redirection + @ran_filter << "unreached_prepend_after_filter_after_redirection" + end end class ConditionalFilterController < ActionController::Base @@ -625,6 +676,32 @@ class FilterTest < ActionController::TestCase assert !assigns["ran_action"] end + def test_before_filter_rendering_breaks_filtering_chain_for_after_filter + response = test_process(RenderingController) + assert_equal %w( before_filter_rendering ), assigns["ran_filter"] + assert !assigns["ran_action"] + end + + def test_before_filter_redirects_breaks_filtering_chain_for_after_filter + response = test_process(BeforeFilterRedirectionController) + assert_response :redirect + assert_equal "http://test.host/filter_test/before_filter_redirection/target_of_redirection", redirect_to_url + assert_equal %w( before_filter_redirects ), assigns["ran_filter"] + end + + def test_before_filter_rendering_breaks_filtering_chain_for_preprend_after_filter + response = test_process(RenderingForPrependAfterFilterController) + assert_equal %w( before_filter_rendering ), assigns["ran_filter"] + assert !assigns["ran_action"] + end + + def test_before_filter_redirects_breaks_filtering_chain_for_preprend_after_filter + response = test_process(BeforeFilterRedirectionForPrependAfterFilterController) + assert_response :redirect + assert_equal "http://test.host/filter_test/before_filter_redirection_for_prepend_after_filter/target_of_redirection", redirect_to_url + assert_equal %w( before_filter_redirects ), assigns["ran_filter"] + end + def test_filters_with_mixed_specialization_run_in_order assert_nothing_raised do response = test_process(MixedSpecializationController, 'bar') diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index a898ef76e5..b6ce9f7d34 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -487,6 +487,10 @@ class RespondWithController < ActionController::Base respond_with(resource) end + def using_hash_resource + respond_with({:result => resource}) + end + def using_resource_with_block respond_with(resource) do |format| format.csv { render :text => "CSV" } @@ -587,6 +591,18 @@ class RespondWithControllerTest < ActionController::TestCase end end + def test_using_hash_resource + @request.accept = "application/xml" + get :using_hash_resource + assert_equal "application/xml", @response.content_type + assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<hash>\n <name>david</name>\n</hash>\n", @response.body + + @request.accept = "application/json" + get :using_hash_resource + assert_equal "application/json", @response.content_type + assert_equal %Q[{"result":["david",13]}], @response.body + end + def test_using_resource_with_block @request.accept = "*/*" get :using_resource_with_block diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 44922cecff..543c02b2c5 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -39,4 +39,11 @@ module BareMetalTest assert_equal 404, status end end + + class BareControllerTest < ActionController::TestCase + test "GET index" do + get :index + assert_equal "Hello world", @response.body + end + end end diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index d31193a063..9899036fe8 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -9,6 +9,7 @@ module RenderTemplate "locals.html.erb" => "The secret is <%= secret %>", "xml_template.xml.builder" => "xml.html do\n xml.p 'Hello'\nend", "with_raw.html.erb" => "Hello <%=raw '<strong>this is raw</strong>' %>", + "with_implicit_raw.html.erb"=> "Hello <%== '<strong>this is also raw</strong>' %>", "test/with_json.html.erb" => "<%= render :template => 'test/with_json.json' %>", "test/with_json.json.erb" => "<%= render :template => 'test/final' %>", "test/final.json.erb" => "{ final: json }", @@ -51,6 +52,10 @@ module RenderTemplate render :template => "with_raw" end + def with_implicit_raw + render :template => "with_implicit_raw" + end + def with_error render :template => "test/with_error" end @@ -99,6 +104,11 @@ module RenderTemplate assert_body "Hello <strong>this is raw</strong>" assert_status 200 + + get :with_implicit_raw + + assert_body "Hello <strong>this is also raw</strong>" + assert_status 200 end test "rendering a template with renders another template with other format that renders other template in the same format" do diff --git a/actionpack/test/dispatch/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index faeae91f6b..5ec7f12cc1 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -94,6 +94,16 @@ class CookiesTest < ActionController::TestCase cookies.delete(:user_name, :domain => :all) head :ok end + + def symbol_key + cookies[:user_name] = "david" + head :ok + end + + def string_key + cookies['user_name'] = "david" + head :ok + end end tests TestController @@ -291,6 +301,14 @@ class CookiesTest < ActionController::TestCase assert_cookie_header "user_name=; domain=.nextangle.com; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT" end + def test_cookies_hash_is_indifferent_access + [:symbol_key, :string_key].each do |cookie_key| + get cookie_key + assert_equal "david", cookies[:user_name] + assert_equal "david", cookies['user_name'] + end + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5c188a60c7..0ac8c249cb 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2255,3 +2255,34 @@ class TestDefaultScope < ActionDispatch::IntegrationTest end end +class TestHttpMethods < ActionDispatch::IntegrationTest + RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) + RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) + RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) + RFC3648 = %w(ORDERPATCH) + RFC3744 = %w(ACL) + RFC5323 = %w(SEARCH) + RFC5789 = %w(PATCH) + + def simple_app(response) + lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [response] ] } + end + + setup do + s = self + @app = ActionDispatch::Routing::RouteSet.new + + @app.draw do + (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method| + match '/' => s.simple_app(method), :via => method.underscore.to_sym + end + end + end + + (RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC5789).each do |method| + test "request method #{method.underscore} can be matched" do + get '/', nil, 'REQUEST_METHOD' => method + assert_equal method, @response.body + end + end +end diff --git a/actionpack/test/fixtures/layouts/_partial_and_yield.erb b/actionpack/test/fixtures/layouts/_partial_and_yield.erb new file mode 100644 index 0000000000..74cc428ffa --- /dev/null +++ b/actionpack/test/fixtures/layouts/_partial_and_yield.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'test/partial' %> +<%= yield %> diff --git a/actionpack/test/fixtures/layouts/_yield_only.erb b/actionpack/test/fixtures/layouts/_yield_only.erb new file mode 100644 index 0000000000..37f0bddbd7 --- /dev/null +++ b/actionpack/test/fixtures/layouts/_yield_only.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/actionpack/test/fixtures/layouts/_yield_with_params.erb b/actionpack/test/fixtures/layouts/_yield_with_params.erb new file mode 100644 index 0000000000..68e6557fb8 --- /dev/null +++ b/actionpack/test/fixtures/layouts/_yield_with_params.erb @@ -0,0 +1 @@ +<%= yield 'Yield!' %> diff --git a/actionpack/test/fixtures/layouts/yield_with_render_partial_inside.erb b/actionpack/test/fixtures/layouts/yield_with_render_partial_inside.erb new file mode 100644 index 0000000000..74cc428ffa --- /dev/null +++ b/actionpack/test/fixtures/layouts/yield_with_render_partial_inside.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'test/partial' %> +<%= yield %> diff --git a/actionpack/test/fixtures/test/_partial_with_layout.erb b/actionpack/test/fixtures/test/_partial_with_layout.erb new file mode 100644 index 0000000000..2a50c834fe --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_with_layout.erb @@ -0,0 +1,2 @@ +<%= render :partial => 'test/partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Bar!' } %> +partial with layout diff --git a/actionpack/test/fixtures/test/_partial_with_layout_block_content.erb b/actionpack/test/fixtures/test/_partial_with_layout_block_content.erb new file mode 100644 index 0000000000..65dafd93a8 --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_with_layout_block_content.erb @@ -0,0 +1,4 @@ +<%= render :layout => 'test/layout_for_partial', :locals => { :name => 'Bar!' } do %> + Content from inside layout! +<% end %> +partial with layout diff --git a/actionpack/test/fixtures/test/_partial_with_layout_block_partial.erb b/actionpack/test/fixtures/test/_partial_with_layout_block_partial.erb new file mode 100644 index 0000000000..444197a7d0 --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_with_layout_block_partial.erb @@ -0,0 +1,4 @@ +<%= render :layout => 'test/layout_for_partial', :locals => { :name => 'Bar!' } do %> + <%= render 'test/partial' %> +<% end %> +partial with layout diff --git a/actionpack/test/fixtures/test/_partial_with_partial.erb b/actionpack/test/fixtures/test/_partial_with_partial.erb new file mode 100644 index 0000000000..ee0d5037b6 --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_with_partial.erb @@ -0,0 +1,2 @@ +<%= render 'test/partial' %> +partial with partial diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index dba632e6df..ae0c38184d 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -184,3 +184,13 @@ module Blog end end end + +class ArelLike + def to_ary + true + end + def each + a = Array.new(2) { |id| Comment.new(id + 1) } + a.each { |i| yield i } + end +end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 3abcdfbc1e..caf1c694c8 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -727,6 +727,17 @@ class AssetTagHelperTest < ActionView::TestCase assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) end + def test_caching_javascript_include_tag_when_caching_on_and_javascript_file_is_uri + ENV["RAILS_ASSET_ID"] = "" + config.perform_caching = true + + assert_raise(Errno::ENOENT) { + javascript_include_tag('bank', 'robber', 'https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.js', :cache => true) + } + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) + end + def test_caching_javascript_include_tag_when_caching_off_and_missing_javascript_file ENV["RAILS_ASSET_ID"] = "" config.perform_caching = false diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 8f81076299..03050485fa 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -28,6 +28,16 @@ class CaptureHelperTest < ActionView::TestCase assert_nil @av.capture { 1 } end + def test_capture_escapes_html + string = @av.capture { '<em>bar</em>' } + assert_equal '<em>bar</em>', string + end + + def test_capture_doesnt_escape_twice + string = @av.capture { '<em>bar</em>'.html_safe } + assert_equal '<em>bar</em>', string + end + def test_content_for assert ! content_for?(:title) content_for :title, 'title' diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 0cf7885772..55c384e68f 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -1584,6 +1584,47 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " }) end + def test_date_select_with_separator_and_order + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", { :order => [:day, :month, :year], :date_separator => " / " }) + end + + def test_date_select_with_separator_and_order_and_year_discarded + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n} + expected << "</select>\n" + + expected << " / " + + expected << %{<select id="post_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n} + expected << "</select>\n" + expected << %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + + assert_dom_equal expected, date_select("post", "written_on", { :order => [:day, :month, :year], :discard_year => true, :date_separator => " / " }) + end + def test_date_select_with_default_prompt @post = Post.new @post.written_on = Date.new(2004, 6, 15) diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index acb6e7aa64..2c60096475 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1230,6 +1230,27 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_arel_like + @post.comments = ArelLike.new + + form_for(@post) do |f| + concat f.text_field(:title) + concat f.fields_for(:comments, @post.comments) { |cf| + concat cf.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + end + + assert_dom_equal expected, output_buffer + end + def test_nested_fields_for_with_existing_records_on_a_supplied_nested_attributes_collection_different_from_record_one comments = Array.new(2) { |id| Comment.new(id + 1) } @post.comments = [] diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb index c82ead663f..5df09b4d3b 100644 --- a/actionpack/test/template/number_helper_i18n_test.rb +++ b/actionpack/test/template/number_helper_i18n_test.rb @@ -7,7 +7,7 @@ class NumberHelperTest < ActionView::TestCase I18n.backend.store_translations 'ts', :number => { :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, - :currency => { :format => { :unit => '&$', :format => '%u - %n', :precision => 2 } }, + :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } }, :human => { :format => { :precision => 2, @@ -43,11 +43,14 @@ class NumberHelperTest < ActionView::TestCase def test_number_to_i18n_currency assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) + assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts')) + assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u")) end def test_number_to_currency_with_clean_i18n_settings clean_i18n do assert_equal("$10.00", number_to_currency(10)) + assert_equal("-$10.00", number_to_currency(-10)) end end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index dcdf28ddd5..ab127521ad 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -45,11 +45,15 @@ class NumberHelperTest < ActionView::TestCase def test_number_to_currency assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50)) assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506)) + assert_equal("-$1,234,567,890.50", number_to_currency(-1234567890.50)) + assert_equal("-$ 1,234,567,890.50", number_to_currency(-1234567890.50, {:format => "%u %n"})) + assert_equal("($1,234,567,890.50)", number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"})) assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0})) assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1})) assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50")) assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) + assert_equal("1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) end def test_number_to_percentage diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 17bb610b6a..d2bf45a63a 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -247,6 +247,51 @@ module RenderTestCases @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside") end + def test_render_with_layout_which_renders_another_partial + assert_equal %(partial html\nHello world!\n), + @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside") + end + + def test_render_layout_with_block_and_yield + assert_equal %(Content from block!\n), + @view.render(:layout => "layouts/yield_only") { "Content from block!" } + end + + def test_render_layout_with_block_and_yield_with_params + assert_equal %(Yield! Content from block!\n), + @view.render(:layout => "layouts/yield_with_params") { |param| "#{param} Content from block!" } + end + + def test_render_layout_with_block_which_renders_another_partial_and_yields + assert_equal %(partial html\nContent from block!\n), + @view.render(:layout => "layouts/partial_and_yield") { "Content from block!" } + end + + def test_render_partial_and_layout_without_block_with_locals + assert_equal %(Before (Foo!)\npartial html\nAfter), + @view.render(:partial => 'test/partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + end + + def test_render_partial_and_layout_without_block_with_locals_and_rendering_another_partial + assert_equal %(Before (Foo!)\npartial html\npartial with partial\n\nAfter), + @view.render(:partial => 'test/partial_with_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + end + + def test_render_layout_with_a_nested_render_layout_call + assert_equal %(Before (Foo!)\nBefore (Bar!)\npartial html\nAfter\npartial with layout\n\nAfter), + @view.render(:partial => 'test/partial_with_layout', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + end + + def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_partial + assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n partial html\n\nAfterpartial with layout\n\nAfter), + @view.render(:partial => 'test/partial_with_layout_block_partial', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + end + + def test_render_layout_with_a_nested_render_layout_call_using_block_with_render_content + assert_equal %(Before (Foo!)\nBefore (Bar!)\n\n Content from inside layout!\n\nAfterpartial with layout\n\nAfter), + @view.render(:partial => 'test/partial_with_layout_block_content', :layout => 'test/layout_for_partial', :locals => { :name => 'Foo!'}) + end + def test_render_with_nested_layout assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n), @view.render(:file => "test/nested_layout.erb", :layout => "layouts/yield") diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index bc2548e06c..4a8cea36d4 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -263,12 +263,7 @@ class UrlHelperTest < ActiveSupport::TestCase assert_equal "<strong>Showing</strong>", link_to_unless(true, "Showing", url_hash) { |name| - "<strong>#{name}</strong>" - } - - assert_equal "<strong>Showing</strong>", - link_to_unless(true, "Showing", url_hash) { |name| - "<strong>#{name}</strong>" + "<strong>#{name}</strong>".html_safe } assert_equal "test", diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG index e93d84ab59..4e963c77b0 100644 --- a/activemodel/CHANGELOG +++ b/activemodel/CHANGELOG @@ -2,6 +2,10 @@ * No changes +*Rails 3.0.2 (unreleased)* + +* No changes + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 44e3e64a9e..c1c5640616 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -98,10 +98,10 @@ module ActiveModel def define_attr_method(name, value=nil, &block) sing = singleton_class sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 - if method_defined?(:original_#{name}) - undef :original_#{name} + if method_defined?(:'original_#{name}') + undef :'original_#{name}' end - alias_method :original_#{name}, :#{name} + alias_method :'original_#{name}', :'#{name}' eorb if block_given? sing.send :define_method, name, &block @@ -274,8 +274,8 @@ module ActiveModel method_name = matcher.method_name(attr_name) generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - if method_defined?(:#{method_name}) - undef :#{method_name} + if method_defined?(:'#{method_name}') + undef :'#{method_name}' end def #{method_name}(*args) send(:#{matcher.method_missing_target}, '#{attr_name}', *args) diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializeration/xml_serialization_test.rb index ff786658b7..cc19d322b3 100644 --- a/activemodel/test/cases/serializeration/xml_serialization_test.rb +++ b/activemodel/test/cases/serializeration/xml_serialization_test.rb @@ -70,6 +70,13 @@ class XmlSerializationTest < ActiveModel::TestCase assert_match %r{<CreatedAt}, @xml end + test "should allow lower-camelized tags" do + @xml = @contact.to_xml :root => 'xml_contact', :camelize => :lower + assert_match %r{^<xmlContact>}, @xml + assert_match %r{</xmlContact>$}, @xml + assert_match %r{<createdAt}, @xml + end + test "should allow skipped types" do @xml = @contact.to_xml :skip_types => true assert_match %r{<age>25</age>}, @xml diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d3530e0da9..11eb47917d 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -18,6 +18,35 @@ [Aaron Patterson] +*Rails 3.0.2 (unreleased)* + +* reorder is deprecated in favor of except(:order).order(...) [Santiago Pastorino] + +* except is now AR public API + + Model.order('name').except(:order).order('salary') + + generates: + + SELECT * FROM models ORDER BY salary + + [Santiago Pastorino] + +* The following code: + + Model.limit(10).scoping { Model.count } + + now generates the following SQL: + + SELECT COUNT(*) FROM models LIMIT 10 + + This may not return what you want. Instead, you may with to do something + like this: + + Model.limit(10).scoping { Model.all.size } + + [Aaron Patterson] + *Rails 3.0.1 (October 15, 2010)* * Introduce a fix for CVE-2010-3993 diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index b8a980b1fb..b1df24844a 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -23,6 +23,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 2.0.0') + s.add_dependency('arel', '~> 2.0.2') s.add_dependency('tzinfo', '~> 0.3.23') end diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 16206c1056..8cd7389005 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -6,7 +6,7 @@ module ActiveRecord def clear_aggregation_cache #:nodoc: self.class.reflect_on_all_aggregations.to_a.each do |assoc| instance_variable_set "@#{assoc.name}", nil - end unless self.new_record? + end if self.persisted? end # Active Record implements aggregation through a macro-like class method called +composed_of+ diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index dc466eafc2..08a4ebfd7e 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -118,7 +118,7 @@ module ActiveRecord def clear_association_cache #:nodoc: self.class.reflect_on_all_associations.to_a.each do |assoc| instance_variable_set "@#{assoc.name}", nil - end unless self.new_record? + end if self.persisted? end private @@ -1839,8 +1839,6 @@ module ActiveRecord @join_parts = [JoinBase.new(base, joins)] @associations = {} @reflections = [] - @base_records_hash = {} - @base_records_in_order = [] @table_aliases = Hash.new(0) @table_aliases[base.table_name] = 1 build(associations) @@ -1874,15 +1872,18 @@ module ActiveRecord end def instantiate(rows) - rows.each_with_index do |row, i| - primary_id = join_base.record_id(row) - unless @base_records_hash[primary_id] - @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row)) - end - construct(@base_records_hash[primary_id], @associations, join_associations.dup, row) - end - remove_duplicate_results!(join_base.active_record, @base_records_in_order, @associations) - return @base_records_in_order + primary_key = join_base.aliased_primary_key + parents = {} + + records = rows.map { |model| + primary_id = model[primary_key] + parent = parents[primary_id] ||= join_base.instantiate(model) + construct(parent, @associations, join_associations.dup, model) + parent + }.uniq + + remove_duplicate_results!(join_base.active_record, records, @associations) + records end def remove_duplicate_results!(base, records, associations) @@ -1982,10 +1983,13 @@ module ActiveRecord def construct(parent, associations, join_parts, row) case associations when Symbol, String + name = associations.to_s + join_part = join_parts.detect { |j| - j.reflection.name.to_s == associations.to_s && + j.reflection.name.to_s == name && j.parent_table_name == parent.class.table_name } - raise(ConfigurationError, "No such association") if join_part.nil? + + raise(ConfigurationError, "No such association") unless join_part join_parts.delete(join_part) construct_association(parent, join_part, row) @@ -1995,13 +1999,7 @@ module ActiveRecord end when Hash associations.sort_by { |k,_| k.to_s }.each do |name, assoc| - join_part = join_parts.detect{ |j| - j.reflection.name.to_s == name.to_s && - j.parent_table_name == parent.class.table_name } - raise(ConfigurationError, "No such association") if join_part.nil? - - association = construct_association(parent, join_part, row) - join_parts.delete(join_part) + association = construct(parent, name, join_parts, row) construct(association, assoc, join_parts, row) if association end else diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 896e18af01..5b34ac907c 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -75,6 +75,7 @@ module ActiveRecord find(:first, *args) else load_target unless loaded? + args = args[1..-1] if args.first.kind_of?(Hash) && args.first.empty? @target.first(*args) end end @@ -120,13 +121,13 @@ module ActiveRecord # Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically. def <<(*records) result = true - load_target if @owner.new_record? + load_target unless @owner.persisted? transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) add_record_to_target_with_callbacks(record) do |r| - result &&= insert_record(record) unless @owner.new_record? + result &&= insert_record(record) if @owner.persisted? end end end @@ -285,12 +286,12 @@ module ActiveRecord # This method is abstract in the sense that it relies on # +count_records+, which is a method descendants have to provide. def size - if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) + if !@owner.persisted? || (loaded? && !@reflection.options[:uniq]) @target.size elsif !loaded? && @reflection.options[:group] load_target.size elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) - unsaved_records = @target.select { |r| r.new_record? } + unsaved_records = @target.reject { |r| r.persisted? } unsaved_records.size + count_records else count_records @@ -357,7 +358,7 @@ module ActiveRecord def include?(record) return false unless record.is_a?(@reflection.klass) - return include_in_memory?(record) if record.new_record? + return include_in_memory?(record) unless record.persisted? load_target if @reflection.options[:finder_sql] && !loaded? return @target.include?(record) if loaded? exists?(record) @@ -372,7 +373,7 @@ module ActiveRecord end def load_target - if !@owner.new_record? || foreign_key_present + if @owner.persisted? || foreign_key_present begin if !loaded? if @target.is_a?(Array) && @target.any? @@ -513,7 +514,7 @@ module ActiveRecord transaction do records.each { |record| callback(:before_remove, record) } - old_records = records.reject { |r| r.new_record? } + old_records = records.select { |r| r.persisted? } yield(records, old_records) records.each { |record| callback(:after_remove, record) } end @@ -538,14 +539,14 @@ module ActiveRecord end def ensure_owner_is_not_new - if @owner.new_record? + unless @owner.persisted? raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" end end def fetch_first_or_last_using_find?(args) - args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || - @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer)) + (args.first.kind_of?(Hash) && !args.first.empty?) || !(loaded? || !@owner.persisted? || @reflection.options[:finder_sql] || + !@target.all? { |record| record.persisted? } || args.first.kind_of?(Integer)) end def include_in_memory?(record) diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 0c12c3737d..09bba213ce 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -53,7 +53,7 @@ module ActiveRecord alias_method :proxy_respond_to?, :respond_to? alias_method :proxy_extend, :extend delegate :to_param, :to => :proxy_target - instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ } + instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ } def initialize(owner, reflection) @owner, @reflection = owner, reflection @@ -175,10 +175,10 @@ module ActiveRecord # If the association is polymorphic the type of the owner is also set. def set_belongs_to_association_for(record) if @reflection.options[:as] - record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? + record["#{@reflection.options[:as]}_id"] = @owner.id if @owner.persisted? record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s else - unless @owner.new_record? + if @owner.persisted? primary_key = @reflection.options[:primary_key] || :id record[@reflection.primary_key_name] = @owner.send(primary_key) end @@ -252,7 +252,7 @@ module ActiveRecord def load_target return nil unless defined?(@loaded) - if !loaded? and (!@owner.new_record? || foreign_key_present) + if !loaded? and (@owner.persisted? || foreign_key_present) @target = find_target end diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 34b6cd5576..b624951cd9 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -14,7 +14,7 @@ module ActiveRecord counter_cache_name = @reflection.counter_cache_column if record.nil? - if counter_cache_name && !@owner.new_record? + if counter_cache_name && @owner.persisted? @reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name] end @@ -22,13 +22,13 @@ module ActiveRecord else raise_on_type_mismatch(record) - if counter_cache_name && !@owner.new_record? && record.id != @owner[@reflection.primary_key_name] + if counter_cache_name && @owner.persisted? && record.id != @owner[@reflection.primary_key_name] @reflection.klass.increment_counter(counter_cache_name, record.id) @reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name] end @target = (AssociationProxy === record ? record.target : record) - @owner[@reflection.primary_key_name] = record_id(record) unless record.new_record? + @owner[@reflection.primary_key_name] = record_id(record) if record.persisted? @updated = true end diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 1fc9aba5cf..da742fa668 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -34,7 +34,7 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if record.new_record? + unless record.persisted? if force record.save! else diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 437c8b1fd6..79c229d9c4 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -59,7 +59,7 @@ module ActiveRecord end def insert_record(record, force = true, validate = true) - if record.new_record? + unless record.persisted? if force record.save! else @@ -68,8 +68,7 @@ module ActiveRecord end through_association = @owner.send(@reflection.through_reflection.name) - through_record = through_association.create!(construct_join_attributes(record)) - through_association.proxy_target << through_record + through_association.create!(construct_join_attributes(record)) end # TODO - add dependent option support diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 17901387e9..e6e037441f 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -30,18 +30,18 @@ module ActiveRecord if dependent? && !dont_save case @reflection.options[:dependent] when :delete - @target.delete unless @target.new_record? + @target.delete if @target.persisted? @owner.clear_association_cache when :destroy - @target.destroy unless @target.new_record? + @target.destroy if @target.persisted? @owner.clear_association_cache when :nullify @target[@reflection.primary_key_name] = nil - @target.save unless @owner.new_record? || @target.new_record? + @target.save if @owner.persisted? && @target.persisted? end else @target[@reflection.primary_key_name] = nil - @target.save unless @owner.new_record? || @target.new_record? + @target.save if @owner.persisted? && @target.persisted? end end @@ -56,7 +56,7 @@ module ActiveRecord set_inverse_instance(obj, @owner) @loaded = true - unless @owner.new_record? or obj.nil? or dont_save + unless !@owner.persisted? or obj.nil? or dont_save return (obj.save ? self : false) else return (obj.nil? ? nil : self) @@ -113,7 +113,7 @@ module ActiveRecord if replace_existing replace(record, true) else - record[@reflection.primary_key_name] = @owner.id unless @owner.new_record? + record[@reflection.primary_key_name] = @owner.id if @owner.persisted? self.target = record set_inverse_instance(record, @owner) end diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 7f28abf464..6e98f7dffb 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -21,7 +21,7 @@ module ActiveRecord if current_object new_value ? current_object.update_attributes(construct_join_attributes(new_value)) : current_object.destroy elsif new_value - if @owner.new_record? + unless @owner.persisted? self.target = new_value through_association = @owner.send(:association_instance_get, @reflection.through_reflection.name) through_association.build(construct_join_attributes(new_value)) diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 82d94b848a..75ae06f5e9 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -3,10 +3,11 @@ module ActiveRecord module PrimaryKey extend ActiveSupport::Concern - # Returns this record's primary key value wrapped in an Array - # or nil if the record is a new_record? + # Returns this record's primary key value wrapped in an Array or nil if + # the record is not persisted? or has just been destroyed. def to_key - new_record? ? nil : [ id ] + key = send(self.class.primary_key) + [key] if key end module ClassMethods diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 0b89a49896..cb5bc06580 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -217,7 +217,7 @@ module ActiveRecord # Returns whether or not this record has been changed in any way (including whether # any of its nested autosave associations are likewise changed) def changed_for_autosave? - new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave? + !persisted? || changed? || marked_for_destruction? || nested_records_changed_for_autosave? end private @@ -231,7 +231,7 @@ module ActiveRecord elsif autosave association.target.find_all { |record| record.changed_for_autosave? } else - association.target.find_all { |record| record.new_record? } + association.target.find_all { |record| !record.persisted? } end end @@ -257,7 +257,7 @@ module ActiveRecord # +reflection+. def validate_collection_association(reflection) if association = association_instance_get(reflection.name) - if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]) + if records = associated_records_to_validate_or_save(association, !persisted?, reflection.options[:autosave]) records.each { |record| association_valid?(reflection, record) } end end @@ -286,7 +286,7 @@ module ActiveRecord # Is used as a before_save callback to check while saving a collection # association whether or not the parent was a new record before saving. def before_save_collection_association - @new_record_before_save = new_record? + @new_record_before_save = !persisted? true end @@ -308,7 +308,7 @@ module ActiveRecord if autosave && record.marked_for_destruction? association.destroy(record) - elsif autosave != false && (@new_record_before_save || record.new_record?) + elsif autosave != false && (@new_record_before_save || !record.persisted?) if autosave saved = association.send(:insert_record, record, false, false) else @@ -343,7 +343,7 @@ module ActiveRecord association.destroy else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id - if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave) + if autosave != false && (!persisted? || !association.persisted? || association[reflection.primary_key_name] != key || autosave) association[reflection.primary_key_name] = key saved = association.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave @@ -363,7 +363,7 @@ module ActiveRecord if autosave && association.marked_for_destruction? association.destroy elsif autosave != false - saved = association.save(:validate => !autosave) if association.new_record? || autosave + saved = association.save(:validate => !autosave) if !association.persisted? || autosave if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 06a388cd21..f588475bbf 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -204,7 +204,7 @@ module ActiveRecord #:nodoc: # # # No 'Winter' tag exists # winter = Tag.find_or_initialize_by_name("Winter") - # winter.new_record? # true + # winter.persisted? # false # # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of # a list of parameters. @@ -1368,7 +1368,7 @@ MSG def initialize(attributes = nil) @attributes = attributes_from_column_definition @attributes_cache = {} - @new_record = true + @persisted = false @readonly = false @destroyed = false @marked_for_destruction = false @@ -1403,7 +1403,7 @@ MSG clear_aggregation_cache clear_association_cache @attributes_cache = {} - @new_record = true + @persisted = false ensure_proper_type populate_with_current_scope_attributes @@ -1422,7 +1422,8 @@ MSG def init_with(coder) @attributes = coder['attributes'] @attributes_cache, @previously_changed, @changed_attributes = {}, {}, {} - @new_record = @readonly = @destroyed = @marked_for_destruction = false + @readonly = @destroyed = @marked_for_destruction = false + @persisted = true _run_find_callbacks _run_initialize_callbacks end @@ -1463,7 +1464,7 @@ MSG # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) def cache_key case - when new_record? + when !persisted? "#{self.class.model_name.cache_key}/new" when timestamp = self[:updated_at] "#{self.class.model_name.cache_key}/#{id}-#{timestamp.to_s(:number)}" @@ -1584,8 +1585,9 @@ MSG # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id. def ==(comparison_object) comparison_object.equal?(self) || - (comparison_object.instance_of?(self.class) && - comparison_object.id == id && !comparison_object.new_record?) + persisted? && + (comparison_object.instance_of?(self.class) && + comparison_object.id == id) end # Delegates to == @@ -1630,7 +1632,7 @@ MSG # Returns the contents of the record as a nicely formatted string. def inspect attributes_as_nice_string = self.class.column_names.collect { |name| - if has_attribute?(name) || new_record? + if has_attribute?(name) || !persisted? "#{name}: #{attribute_for_inspect(name)}" end }.compact.join(", ") diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6178130c00..ee9a0af35c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -50,7 +50,7 @@ module ActiveRecord # Executes +sql+ statement in the context of this connection using # +binds+ as the bind substitutes. +name+ is logged along with # the executed +sql+ statement. - def exec(sql, name = 'SQL', binds = []) + def exec_query(sql, name = 'SQL', binds = []) end # Returns the last auto-generated ID from the affected table. diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 7d47d06ae1..ce2352486b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -326,7 +326,7 @@ module ActiveRecord @statements.clear end - def exec(sql, name = 'SQL', binds = []) + def exec_query(sql, name = 'SQL', binds = []) log(sql, name) do result = nil @@ -708,7 +708,7 @@ module ActiveRecord def select(sql, name = nil, binds = []) @connection.query_with_result = true - rows = exec(sql, name, binds).to_a + rows = exec_query(sql, name, binds).to_a @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped rows end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 58c0f85dc2..ccc5085b84 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -398,7 +398,7 @@ module ActiveRecord # Set the authorized user for this session def session_auth=(user) clear_cache! - exec "SET SESSION AUTHORIZATION #{user}" + exec_query "SET SESSION AUTHORIZATION #{user}" end # REFERENTIAL INTEGRITY ==================================== @@ -532,7 +532,7 @@ module ActiveRecord Arel.sql("$#{current_values.length + 1}") end - def exec(sql, name = 'SQL', binds = []) + def exec_query(sql, name = 'SQL', binds = []) return exec_no_cache(sql, name) if binds.empty? log(sql, name) do @@ -714,8 +714,8 @@ module ActiveRecord # Returns the list of all column definitions for a table. def columns(table_name, name = nil) # Limit, precision, and scale are all handled by the superclass. - column_definitions(table_name).collect do |name, type, default, notnull| - PostgreSQLColumn.new(name, default, type, notnull == 'f') + column_definitions(table_name).collect do |column_name, type, default, notnull| + PostgreSQLColumn.new(column_name, default, type, notnull == 'f') end end @@ -932,12 +932,12 @@ module ActiveRecord # requires that the ORDER BY include the distinct column. # # distinct("posts.id", "posts.created_at desc") - def distinct(columns, order_by) #:nodoc: - return "DISTINCT #{columns}" if order_by.blank? + def distinct(columns, orders) #:nodoc: + return "DISTINCT #{columns}" if orders.empty? # Construct a clean list of column names from the ORDER BY clause, removing # any ASC/DESC modifiers - order_columns = order_by.split(',').collect { |s| s.split.first } + order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s } order_columns.delete_if { |c| c.blank? } order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } @@ -1040,7 +1040,7 @@ module ActiveRecord # Executes a SELECT query and returns the results, performing any data type # conversions that are required to be performed here instead of in PostgreSQLColumn. def select(sql, name = nil, binds = []) - exec(sql, name, binds).to_a + exec_query(sql, name, binds).to_a end def select_raw(sql, name = nil) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index fc4d84a4f2..d76fc4103e 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -143,7 +143,7 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== - def exec(sql, name = nil, binds = []) + def exec_query(sql, name = nil, binds = []) log(sql, name) do # Don't cache statements without bind values @@ -186,7 +186,7 @@ module ActiveRecord alias :create :insert_sql def select_rows(sql, name = nil) - exec(sql, name).rows + exec_query(sql, name).rows end def begin_db_transaction #:nodoc: @@ -210,7 +210,7 @@ module ActiveRecord WHERE type = 'table' AND NOT name = 'sqlite_sequence' SQL - exec(sql, name).map do |row| + exec_query(sql, name).map do |row| row['name'] end end @@ -222,12 +222,12 @@ module ActiveRecord end def indexes(table_name, name = nil) #:nodoc: - exec("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row| IndexDefinition.new( table_name, row['name'], row['unique'] != 0, - exec("PRAGMA index_info('#{row['name']}')").map { |col| + exec_query("PRAGMA index_info('#{row['name']}')").map { |col| col['name'] }) end @@ -241,11 +241,11 @@ module ActiveRecord end def remove_index!(table_name, index_name) #:nodoc: - exec "DROP INDEX #{quote_column_name(index_name)}" + exec_query "DROP INDEX #{quote_column_name(index_name)}" end def rename_table(name, new_name) - exec "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" + exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end # See: http://www.sqlite.org/lang_altertable.html @@ -282,7 +282,7 @@ module ActiveRecord def change_column_null(table_name, column_name, null, default = nil) unless null || default.nil? - exec("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end alter_table(table_name) do |definition| definition[column_name].null = null @@ -314,7 +314,7 @@ module ActiveRecord protected def select(sql, name = nil, binds = []) #:nodoc: - result = exec(sql, name, binds) + result = exec_query(sql, name, binds) columns = result.columns.map { |column| column.sub(/^"?\w+"?\./, '') } @@ -399,11 +399,11 @@ module ActiveRecord quoted_columns = columns.map { |col| quote_column_name(col) } * ',' quoted_to = quote_table_name(to) - exec("SELECT * FROM #{quote_table_name(from)}").each do |row| + exec_query("SELECT * FROM #{quote_table_name(from)}").each do |row| sql = "INSERT INTO #{quoted_to} (#{quoted_columns}) VALUES (" sql << columns.map {|col| quote row[column_mappings[col]]} * ', ' sql << ')' - exec sql + exec_query sql end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index b6f87a57b8..bf626301f1 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -109,7 +109,7 @@ module ActiveRecord def destroy #:nodoc: return super unless locking_enabled? - unless new_record? + if persisted? lock_col = self.class.locking_column previous_value = send(lock_col).to_i diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 9ad6a2baf7..d900831e13 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -47,7 +47,7 @@ module ActiveRecord # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns # the locked record. def lock!(lock = true) - reload(:lock => lock) unless new_record? + reload(:lock => lock) if persisted? self end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index aca91c907d..0c3392263a 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -323,7 +323,7 @@ module ActiveRecord (options[:update_only] || record.id.to_s == attributes['id'].to_s) assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) - elsif attributes['id'] + elsif attributes['id'].present? raise_nested_attributes_record_not_found(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 707c1a05be..594a2214bb 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -4,7 +4,7 @@ module ActiveRecord # Returns true if this object hasn't been saved yet -- that is, a record # for the object doesn't exist in the data store yet; otherwise, returns false. def new_record? - @new_record + !@persisted end # Returns true if this object has been destroyed, otherwise returns false. @@ -15,7 +15,7 @@ module ActiveRecord # Returns if the record is persisted, i.e. it's not a new record and it was # not destroyed. def persisted? - !(new_record? || destroyed?) + @persisted && !destroyed? end # Saves the model. @@ -94,8 +94,9 @@ module ActiveRecord became = klass.new became.instance_variable_set("@attributes", @attributes) became.instance_variable_set("@attributes_cache", @attributes_cache) - became.instance_variable_set("@new_record", new_record?) + became.instance_variable_set("@persisted", persisted?) became.instance_variable_set("@destroyed", destroyed?) + became.type = klass.name unless self.class.descends_from_active_record? became end @@ -240,7 +241,7 @@ module ActiveRecord private def create_or_update raise ReadOnlyRecord if readonly? - result = new_record? ? create : update + result = persisted? ? update : create result != false end @@ -269,7 +270,7 @@ module ActiveRecord self.id ||= new_id - @new_record = false + @persisted = true id end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 868fd6c3ff..dfe255ad7c 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -14,7 +14,7 @@ module ActiveRecord config.active_record = ActiveSupport::OrderedOptions.new config.app_generators.orm :active_record, :migration => true, - :timestamps => true + :timestamps => true config.app_middleware.insert_after "::ActionDispatch::Callbacks", "ActiveRecord::QueryCache" diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index fe1ef2e2e3..4192456447 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -171,14 +171,16 @@ module ActiveRecord def exists?(id = nil) id = id.id if ActiveRecord::Base === id + relation = select(primary_key).limit(1) + case id when Array, Hash - where(id).exists? + relation = relation.where(id) else - relation = select(primary_key).limit(1) relation = relation.where(primary_key.eq(id)) if id - relation.first ? true : false end + + relation.first ? true : false end protected @@ -222,7 +224,7 @@ module ActiveRecord end def construct_limited_ids_condition(relation) - orders = relation.order_values.join(", ") + orders = relation.order_values values = @klass.connection.distinct("#{@klass.connection.quote_table_name @klass.table_name}.#{@klass.primary_key}", orders) ids_array = relation.select(values).collect {|row| row[@klass.primary_key]} @@ -298,7 +300,7 @@ module ActiveRecord unless record conditions = arel.where_sql conditions = " [#{conditions}]" if conditions - raise RecordNotFound, "Couldn't find #{@klass.name} with ID=#{id}#{conditions}" + raise RecordNotFound, "Couldn't find #{@klass.name} with #{@klass.primary_key}=#{id}#{conditions}" end record diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 3fc596e02a..ba99800fb2 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -228,7 +228,7 @@ module ActiveRecord @session_id = attributes[:session_id] @data = attributes[:data] @marshaled_data = attributes[:marshaled_data] - @new_record = @marshaled_data.nil? + @persisted = !@marshaled_data.nil? end # Lazy-unmarshal session state. @@ -252,8 +252,8 @@ module ActiveRecord marshaled_data = self.class.marshal(data) connect = connection - if @new_record - @new_record = false + unless @persisted + @persisted = true connect.update <<-end_sql, 'Create session' INSERT INTO #{table_name} ( #{connect.quote_column_name(session_id_column)}, @@ -272,7 +272,7 @@ module ActiveRecord end def destroy - return if @new_record + return unless @persisted connect = connection connect.delete <<-end_sql, 'Destroy session' @@ -321,6 +321,7 @@ module ActiveRecord if sid = current_session_id(env) Base.silence do get_session_model(env, sid).destroy + env[SESSION_RECORD_KEY] = nil end end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index a7583f06cc..230adf6b2b 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -61,7 +61,7 @@ module ActiveRecord end def should_record_timestamps? - record_timestamps && (!partial_updates? || changed?) + record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?) end def timestamp_attributes_for_update_in_model diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index ab737f0f88..8c94d1a2bc 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -242,7 +242,7 @@ module ActiveRecord with_transaction_returning_status { super } end - # Reset id and @new_record if the transaction rolls back. + # Reset id and @persisted if the transaction rolls back. def rollback_active_record_state! remember_transaction_record_state yield @@ -297,9 +297,9 @@ module ActiveRecord # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc @_start_transaction_state ||= {} - unless @_start_transaction_state.include?(:new_record) + unless @_start_transaction_state.include?(:persisted) @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) - @_start_transaction_state[:new_record] = @new_record + @_start_transaction_state[:persisted] = @persisted end unless @_start_transaction_state.include?(:destroyed) @_start_transaction_state[:destroyed] = @destroyed @@ -323,7 +323,7 @@ module ActiveRecord restore_state = remove_instance_variable(:@_start_transaction_state) if restore_state @attributes = @attributes.dup if @attributes.frozen? - @new_record = restore_state[:new_record] + @persisted = restore_state[:persisted] @destroyed = restore_state[:destroyed] if restore_state[:id] self.id = restore_state[:id] @@ -345,11 +345,11 @@ module ActiveRecord def transaction_include_action?(action) #:nodoc case action when :create - transaction_record_state(:new_record) + transaction_record_state(:new_record) || !transaction_record_state(:persisted) when :destroy destroyed? when :update - !(transaction_record_state(:new_record) || destroyed?) + !(transaction_record_state(:new_record) || !transaction_record_state(:persisted) || destroyed?) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index f367315b22..ee45fcdf35 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -51,7 +51,7 @@ module ActiveRecord # Runs all the specified validations and returns true if no errors were added otherwise false. def valid?(context = nil) - context ||= (new_record? ? :create : :update) + context ||= (persisted? ? :update : :create) output = super(context) errors.empty? && output end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index cb1d2ae421..3eba7510ac 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -31,7 +31,7 @@ module ActiveRecord relation = relation.where(scope_item => scope_value) end - unless record.new_record? + if record.persisted? # TODO : This should be in Arel relation = relation.where("#{record.class.quoted_table_name}.#{record.class.primary_key} <> ?", record.send(:id)) end @@ -154,33 +154,25 @@ module ActiveRecord # | # title! # # This could even happen if you use transactions with the 'serializable' - # isolation level. There are several ways to get around this problem: + # isolation level. The best way to work around this problem is to add a unique + # index to the database table using + # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the + # rare case that a race condition occurs, the database will guarantee + # the field's uniqueness. # - # - By locking the database table before validating, and unlocking it after - # saving. However, table locking is very expensive, and thus not - # recommended. - # - By locking a lock file before validating, and unlocking it after saving. - # This does not work if you've scaled your Rails application across - # multiple web servers (because they cannot share lock files, or cannot - # do that efficiently), and thus not recommended. - # - Creating a unique index on the field, by using - # ActiveRecord::ConnectionAdapters::SchemaStatements#add_index. In the - # rare case that a race condition occurs, the database will guarantee - # the field's uniqueness. + # When the database catches such a duplicate insertion, + # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid + # exception. You can either choose to let this error propagate (which + # will result in the default Rails exception page being shown), or you + # can catch it and restart the transaction (e.g. by telling the user + # that the title already exists, and asking him to re-enter the title). + # This technique is also known as optimistic concurrency control: + # http://en.wikipedia.org/wiki/Optimistic_concurrency_control # - # When the database catches such a duplicate insertion, - # ActiveRecord::Base#save will raise an ActiveRecord::StatementInvalid - # exception. You can either choose to let this error propagate (which - # will result in the default Rails exception page being shown), or you - # can catch it and restart the transaction (e.g. by telling the user - # that the title already exists, and asking him to re-enter the title). - # This technique is also known as optimistic concurrency control: - # http://en.wikipedia.org/wiki/Optimistic_concurrency_control - # - # Active Record currently provides no way to distinguish unique - # index constraint errors from other types of database errors, so you - # will have to parse the (database-specific) exception message to detect - # such a case. + # Active Record currently provides no way to distinguish unique + # index constraint errors from other types of database errors, so you + # will have to parse the (database-specific) exception message to detect + # such a case. # def validates_uniqueness_of(*attr_names) validates_with UniquenessValidator, _merge_attributes(attr_names) diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 67bd8ec7e0..62ffde558f 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -49,18 +49,18 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_exec_no_binds - @connection.exec('drop table if exists ex') - @connection.exec(<<-eosql) + @connection.exec_query('drop table if exists ex') + @connection.exec_query(<<-eosql) CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `data` varchar(255)) eosql - result = @connection.exec('SELECT id, data FROM ex') + result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns - @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @connection.exec('SELECT id, data FROM ex') + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -68,13 +68,13 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_exec_with_binds - @connection.exec('drop table if exists ex') - @connection.exec(<<-eosql) + @connection.exec_query('drop table if exists ex') + @connection.exec_query(<<-eosql) CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `data` varchar(255)) eosql - @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @connection.exec( + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) assert_equal 1, result.rows.length @@ -84,15 +84,15 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_exec_typecasts_bind_vals - @connection.exec('drop table if exists ex') - @connection.exec(<<-eosql) + @connection.exec_query('drop table if exists ex') + @connection.exec_query(<<-eosql) CREATE TABLE `ex` (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY, `data` varchar(255)) eosql - @connection.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec( + result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index b0fd2273df..b0a4a4e39d 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -5,8 +5,8 @@ module ActiveRecord class PostgreSQLAdapterTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection - @connection.exec('drop table if exists ex') - @connection.exec('create table ex(id serial primary key, data character varying(255))') + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id serial primary key, data character varying(255))') end def test_table_alias_length @@ -16,14 +16,14 @@ module ActiveRecord end def test_exec_no_binds - result = @connection.exec('SELECT id, data FROM ex') + result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns string = @connection.quote('foo') - @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec('SELECT id, data FROM ex') + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length @@ -32,8 +32,8 @@ module ActiveRecord def test_exec_with_binds string = @connection.quote('foo') - @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") - result = @connection.exec( + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") + result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = $1', nil, [[nil, 1]]) assert_equal 1, result.rows.length @@ -44,10 +44,10 @@ module ActiveRecord def test_exec_typecasts_bind_vals string = @connection.quote('foo') - @connection.exec("INSERT INTO ex (id, data) VALUES (1, #{string})") + @connection.exec_query("INSERT INTO ex (id, data) VALUES (1, #{string})") column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec( + result = @connection.exec_query( 'SELECT id, data FROM ex WHERE id = $1', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 881631fb19..d5e1838543 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -55,7 +55,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase set_session_auth USERS.each do |u| set_session_auth u - assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] set_session_auth end end @@ -67,7 +67,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase USERS.each do |u| @connection.clear_cache! set_session_auth u - assert_equal u, @connection.exec("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] + assert_equal u, @connection.exec_query("SELECT name FROM #{TABLE_NAME} WHERE id = $1", 'SQL', [[nil, 1]]).first['name'] set_session_auth end end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 973c51f3d7..234696adeb 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -58,24 +58,24 @@ module ActiveRecord end def test_exec_no_binds - @conn.exec('create table ex(id int, data string)') - result = @conn.exec('SELECT id, data FROM ex') + @conn.exec_query('create table ex(id int, data string)') + result = @conn.exec_query('SELECT id, data FROM ex') assert_equal 0, result.rows.length assert_equal 2, result.columns.length assert_equal %w{ id data }, result.columns - @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec('SELECT id, data FROM ex') + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query('SELECT id, data FROM ex') assert_equal 1, result.rows.length assert_equal 2, result.columns.length assert_equal [[1, 'foo']], result.rows end - def test_exec_with_binds - @conn.exec('create table ex(id int, data string)') - @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @conn.exec( + def test_exec_query_with_binds + @conn.exec_query('create table ex(id int, data string)') + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @conn.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) assert_equal 1, result.rows.length @@ -84,12 +84,12 @@ module ActiveRecord assert_equal [[1, 'foo']], result.rows end - def test_exec_typecasts_bind_vals - @conn.exec('create table ex(id int, data string)') - @conn.exec('INSERT INTO ex (id, data) VALUES (1, "foo")') + def test_exec_query_typecasts_bind_vals + @conn.exec_query('create table ex(id int, data string)') + @conn.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') column = @conn.columns('ex').find { |col| col.name == 'id' } - result = @conn.exec( + result = @conn.exec_query( 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) assert_equal 1, result.rows.length diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 0fa4328826..1b0c00bd5a 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -285,10 +285,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm = firm - assert final_cut.new_record? + assert !final_cut.persisted? assert final_cut.save - assert !final_cut.new_record? - assert !firm.new_record? + assert final_cut.persisted? + assert firm.persisted? assert_equal firm, final_cut.firm assert_equal firm, final_cut.firm(true) end @@ -297,10 +297,10 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase final_cut = Client.new("name" => "Final Cut") firm = Firm.find(1) final_cut.firm_with_primary_key = firm - assert final_cut.new_record? + assert !final_cut.persisted? assert final_cut.save - assert !final_cut.new_record? - assert !firm.new_record? + assert final_cut.persisted? + assert firm.persisted? assert_equal firm, final_cut.firm_with_primary_key assert_equal firm, final_cut.firm_with_primary_key(true) end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 271bb92ee8..37c6f354a8 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -137,7 +137,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase end def test_eager_association_loading_with_multiple_stis_and_order - author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => 'authors.name, comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4') + author = Author.find(:first, :include => { :posts => [ :special_comments , :very_special_comment ] }, :order => ['authors.name', 'comments.body', 'very_special_comments_posts.body'], :conditions => 'posts.id = 4') assert_equal authors(:david), author assert_no_queries do author.posts.first.special_comments diff --git a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb index b124a2bfc3..fb59f63f91 100644 --- a/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb +++ b/activerecord/test/cases/associations/eager_load_includes_full_sti_class_test.rb @@ -17,7 +17,7 @@ class EagerLoadIncludeFullStiClassNamesTest < ActiveRecord::TestCase def generate_test_objects post = Namespaced::Post.create( :title => 'Great stuff', :body => 'This is not', :author_id => 1 ) - tagging = Tagging.create( :taggable => post ) + Tagging.create( :taggable => post ) end def test_class_names diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 2d8e02e398..66fb5ac1e1 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -323,7 +323,7 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_has_many_through_a_belongs_to_association author = authors(:mary) - post = Post.create!(:author => author, :title => "TITLE", :body => "BODY") + Post.create!(:author => author, :title => "TITLE", :body => "BODY") author.author_favorites.create(:favorite_author_id => 1) author.author_favorites.create(:favorite_author_id => 2) posts_with_author_favorites = author.posts.find(:all, :include => :author_favorites) @@ -521,7 +521,7 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_eager_with_inheritance - posts = SpecialPost.find(:all, :include => [ :comments ]) + SpecialPost.find(:all, :include => [ :comments ]) end def test_eager_has_one_with_association_inheritance @@ -561,16 +561,16 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_eager_with_invalid_association_reference assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=> :monkeys ) + Post.find(6, :include=> :monkeys ) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=>[ :monkeys ]) + Post.find(6, :include=>[ :monkeys ]) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys") { - post = Post.find(6, :include=>[ 'monkeys' ]) + Post.find(6, :include=>[ 'monkeys' ]) } assert_raise(ActiveRecord::ConfigurationError, "Association was not found; perhaps you misspelled it? You specified :include => :monkeys, :elephants") { - post = Post.find(6, :include=>[ :monkeys, :elephants ]) + Post.find(6, :include=>[ :monkeys, :elephants ]) } end @@ -584,8 +584,8 @@ class EagerAssociationTest < ActiveRecord::TestCase end def test_limited_eager_with_multiple_order_columns - assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title), posts.id', :limit => 2, :offset => 1) - assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => 'UPPER(posts.title) DESC, posts.id', :limit => 2, :offset => 1) + assert_equal posts(:thinking, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title)', 'posts.id'], :limit => 2, :offset => 1) + assert_equal posts(:sti_post_and_comments, :sti_comments), Post.find(:all, :include => [:author, :comments], :conditions => "authors.name = 'David'", :order => ['UPPER(posts.title) DESC', 'posts.id'], :limit => 2, :offset => 1) end def test_limited_eager_with_numeric_in_association 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 7e070e1746..705550216c 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 @@ -251,10 +251,10 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase no_of_projects = Project.count aredridel = Developer.new("name" => "Aredridel") aredridel.projects.concat([Project.find(1), p = Project.new("name" => "Projekt")]) - assert aredridel.new_record? - assert p.new_record? + assert !aredridel.persisted? + assert !p.persisted? assert aredridel.save - assert !aredridel.new_record? + assert aredridel.persisted? assert_equal no_of_devels+1, Developer.count assert_equal no_of_projects+1, Project.count assert_equal 2, aredridel.projects.size @@ -288,22 +288,22 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal devel.projects.last, proj assert devel.projects.loaded? - assert proj.new_record? + assert !proj.persisted? devel.save - assert !proj.new_record? + assert proj.persisted? assert_equal devel.projects.last, proj assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end def test_build_by_new_record devel = Developer.new(:name => "Marcel", :salary => 75000) - proj1 = devel.projects.build(:name => "Make bed") + devel.projects.build(:name => "Make bed") proj2 = devel.projects.build(:name => "Lie in it") assert_equal devel.projects.last, proj2 - assert proj2.new_record? + assert !proj2.persisted? devel.save - assert !devel.new_record? - assert !proj2.new_record? + assert devel.persisted? + assert proj2.persisted? assert_equal devel.projects.last, proj2 assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated end @@ -316,19 +316,19 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal devel.projects.last, proj assert !devel.projects.loaded? - assert !proj.new_record? + assert proj.persisted? assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated end def test_create_by_new_record devel = Developer.new(:name => "Marcel", :salary => 75000) - proj1 = devel.projects.build(:name => "Make bed") + devel.projects.build(:name => "Make bed") proj2 = devel.projects.build(:name => "Lie in it") assert_equal devel.projects.last, proj2 - assert proj2.new_record? + assert !proj2.persisted? devel.save - assert !devel.new_record? - assert !proj2.new_record? + assert devel.persisted? + assert proj2.persisted? assert_equal devel.projects.last, proj2 assert_equal Developer.find_by_name("Marcel").projects.last, proj2 # prove join table is updated end @@ -343,7 +343,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase # 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 another_post.persisted? assert_equal 'Yet Another Testing Title', another_post.title end @@ -559,8 +559,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_dynamic_find_should_respect_association_order # Developers are ordered 'name DESC, id DESC' - low_id_jamis = developers(:jamis) - middle_id_jamis = developers(:poor_jamis) high_id_jamis = projects(:active_record).developers.create(:name => 'Jamis') assert_equal high_id_jamis, projects(:active_record).developers.find(:first, :conditions => "name = 'Jamis'") diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index c9f00fd737..ecfc769f3a 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -66,6 +66,17 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 'exotic', bulb.name end + def test_no_sql_should_be_fired_if_association_already_loaded + car = Car.create(:name => 'honda') + bulb = car.bulbs.create + bulbs = Car.first.bulbs + bulbs.inspect # to load all instances of bulbs + assert_no_queries do + bulbs.first() + bulbs.first({}) + end + end + def test_create_resets_cached_counters person = Person.create!(:first_name => 'tenderlove') post = Post.first @@ -73,7 +84,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], person.readers assert_nil person.readers.find_by_post_id(post.id) - reader = person.readers.create(:post_id => post.id) + person.readers.create(:post_id => post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length @@ -88,7 +99,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], person.readers assert_nil person.readers.find_by_post_id(post.id) - reader = person.readers.find_or_create_by_post_id(post.id) + person.readers.find_or_create_by_post_id(post.id) assert_equal 1, person.readers.count assert_equal 1, person.readers.length @@ -187,7 +198,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase another = author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") assert_equal number_of_posts + 1, Post.count assert_equal another, author.posts.find_or_create_by_title_and_body("Another Post", "This is the Body") - assert !another.new_record? + assert another.persisted? end def test_cant_save_has_many_readonly_association @@ -402,7 +413,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_adding_using_create first_firm = companies(:first_firm) assert_equal 2, first_firm.plain_clients.size - natural = first_firm.plain_clients.create(:name => "Natural Company") + first_firm.plain_clients.create(:name => "Natural Company") assert_equal 3, first_firm.plain_clients.length assert_equal 3, first_firm.plain_clients.size end @@ -453,7 +464,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name - assert new_client.new_record? + assert !new_client.persisted? assert_equal new_client, company.clients_of_firm.last end @@ -483,7 +494,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_build_followed_by_save_does_not_load_target - new_client = companies(:first_firm).clients_of_firm.build("name" => "Another Client") + companies(:first_firm).clients_of_firm.build("name" => "Another Client") assert companies(:first_firm).save assert !companies(:first_firm).clients_of_firm.loaded? end @@ -508,7 +519,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !company.clients_of_firm.loaded? assert_equal "Another Client", new_client.name - assert new_client.new_record? + assert !new_client.persisted? assert_equal new_client, company.clients_of_firm.last end @@ -543,7 +554,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create force_signal37_to_load_all_clients_of_firm new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") - assert !new_client.new_record? + assert new_client.persisted? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert_equal new_client, companies(:first_firm).clients_of_firm(true).last end @@ -554,7 +565,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_create_followed_by_save_does_not_load_target - new_client = companies(:first_firm).clients_of_firm.create("name" => "Another Client") + companies(:first_firm).clients_of_firm.create("name" => "Another Client") assert companies(:first_firm).save assert !companies(:first_firm).clients_of_firm.loaded? end @@ -563,7 +574,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase the_client = companies(:first_firm).clients.find_or_initialize_by_name("Yet another client") assert_equal companies(:first_firm).id, the_client.firm_id assert_equal "Yet another client", the_client.name - assert the_client.new_record? + assert !the_client.persisted? end def test_find_or_create_updates_size @@ -752,7 +763,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase another_ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.create - assert !another_ms_client.new_record? + assert another_ms_client.persisted? assert_equal 'Microsoft', another_ms_client.name end @@ -852,7 +863,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_dependence_for_associations_with_hash_condition david = authors(:david) - post = posts(:thinking).id assert_difference('Post.count', -1) { assert david.destroy } end @@ -872,7 +882,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_three_levels_of_dependence topic = Topic.create "title" => "neat and simple" reply = topic.replies.create "title" => "neat and simple", "content" => "still digging it" - silly_reply = reply.replies.create "title" => "neat and simple", "content" => "ain't complaining" + reply.replies.create "title" => "neat and simple", "content" => "ain't complaining" assert_nothing_raised { topic.destroy } end @@ -897,7 +907,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_depends_and_nullify num_accounts = Account.count - num_companies = Company.count core = companies(:rails_core) assert_equal accounts(:rails_core_account), core.account @@ -914,7 +923,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_restrict firm = RestrictedFirm.new(:name => 'restrict') firm.save! - child_firm = firm.companies.create(:name => 'child') + firm.companies.create(:name => 'child') assert !firm.companies.empty? assert_raise(ActiveRecord::DeleteRestrictionError) { firm.destroy } end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 4b9f49f1ec..94e1eb8c89 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -449,4 +449,11 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase comment = post.comments.build assert author.comments.include?(comment) end + + def test_size_of_through_association_should_increase_correctly_when_has_many_association_is_added + post = posts(:thinking) + readers = post.readers.size + post.people << people(:michael) + assert_equal readers + 1, post.readers.size + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index b522be3fe0..6fbeff8aa9 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -268,7 +268,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_assignment_before_child_saved firm = Firm.find(1) firm.account = a = Account.new("credit_limit" => 1000) - assert !a.new_record? + assert a.persisted? assert_equal a, firm.account assert_equal a, firm.account assert_equal a, firm.account(true) @@ -323,7 +323,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase def test_create_respects_hash_condition account = companies(:first_firm).create_account_limit_500_with_hash_conditions - assert !account.new_record? + assert account.persisted? assert_equal 500, account.credit_limit end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 7a22ce4dad..96edcfbb35 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -450,11 +450,11 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase new_tag = Tag.new(:name => "new") saved_post.tags << new_tag - assert !new_tag.new_record? #consistent with habtm! - assert !saved_post.new_record? + assert new_tag.persisted? #consistent with habtm! + assert saved_post.persisted? assert saved_post.tags.include?(new_tag) - assert !new_tag.new_record? + assert new_tag.persisted? assert saved_post.reload.tags(true).include?(new_tag) @@ -462,16 +462,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase saved_tag = tags(:general) new_post.tags << saved_tag - assert new_post.new_record? - assert !saved_tag.new_record? + assert !new_post.persisted? + assert saved_tag.persisted? assert new_post.tags.include?(saved_tag) new_post.save! - assert !new_post.new_record? + assert new_post.persisted? assert new_post.reload.tags(true).include?(saved_tag) - assert posts(:thinking).tags.build.new_record? - assert posts(:thinking).tags.new.new_record? + assert !posts(:thinking).tags.build.persisted? + assert !posts(:thinking).tags.new.persisted? end def test_create_associate_when_adding_to_has_many_through diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 89be94c81f..459f9fa55c 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -83,7 +83,7 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas assert !firm.build_account_using_primary_key.valid? assert firm.save - assert firm.account_using_primary_key.new_record? + assert !firm.account_using_primary_key.persisted? end def test_save_fails_for_invalid_has_one @@ -114,10 +114,10 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas account = firm.account.build("credit_limit" => 1000) assert_equal account, firm.account - assert account.new_record? + assert !account.persisted? assert firm.save assert_equal account, firm.account - assert !account.new_record? + assert account.persisted? end def test_build_before_either_saved @@ -125,16 +125,16 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas firm.account = account = Account.new("credit_limit" => 1000) assert_equal account, firm.account - assert account.new_record? + assert !account.persisted? assert firm.save assert_equal account, firm.account - assert !account.new_record? + assert account.persisted? end def test_assignment_before_parent_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.find(1) - assert firm.new_record? + assert !firm.persisted? assert_equal a, firm.account assert firm.save assert_equal a, firm.account @@ -144,12 +144,12 @@ class TestDefaultAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCas def test_assignment_before_either_saved firm = Firm.new("name" => "GlobalMegaCorp") firm.account = a = Account.new("credit_limit" => 1000) - assert firm.new_record? - assert a.new_record? + assert !firm.persisted? + assert !a.persisted? assert_equal a, firm.account assert firm.save - assert !firm.new_record? - assert !a.new_record? + assert firm.persisted? + assert a.persisted? assert_equal a, firm.account assert_equal a, firm.account(true) end @@ -203,7 +203,7 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test assert !client.firm.valid? assert client.save - assert client.firm.new_record? + assert !client.firm.persisted? end def test_save_fails_for_invalid_belongs_to @@ -232,10 +232,10 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test apple = Firm.new("name" => "Apple") client.firm = apple assert_equal apple, client.firm - assert apple.new_record? + assert !apple.persisted? assert client.save assert apple.save - assert !apple.new_record? + assert apple.persisted? assert_equal apple, client.firm assert_equal apple, client.firm(true) end @@ -244,11 +244,11 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test final_cut = Client.new("name" => "Final Cut") apple = Firm.new("name" => "Apple") final_cut.firm = apple - assert final_cut.new_record? - assert apple.new_record? + assert !final_cut.persisted? + assert !apple.persisted? assert final_cut.save - assert !final_cut.new_record? - assert !apple.new_record? + assert final_cut.persisted? + assert apple.persisted? assert_equal apple, final_cut.firm assert_equal apple, final_cut.firm(true) end @@ -348,10 +348,10 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa def test_invalid_adding firm = Firm.find(1) assert !(firm.clients_of_firm << c = Client.new) - assert c.new_record? + assert !c.persisted? assert !firm.valid? assert !firm.save - assert c.new_record? + assert !c.persisted? end def test_invalid_adding_before_save @@ -359,12 +359,12 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa no_of_clients = Client.count new_firm = Firm.new("name" => "A New Firm, Inc") new_firm.clients_of_firm.concat([c = Client.new, Client.new("name" => "Apple")]) - assert c.new_record? + assert !c.persisted? assert !c.valid? assert !new_firm.valid? assert !new_firm.save - assert c.new_record? - assert new_firm.new_record? + assert !c.persisted? + assert !new_firm.persisted? end def test_invalid_adding_with_validate_false @@ -375,7 +375,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert firm.valid? assert !client.valid? assert firm.save - assert client.new_record? + assert !client.persisted? end def test_valid_adding_with_validate_false @@ -386,22 +386,22 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert firm.valid? assert client.valid? - assert client.new_record? + assert !client.persisted? firm.unvalidated_clients_of_firm << client assert firm.save - assert !client.new_record? + assert client.persisted? assert_equal no_of_clients + 1, Client.count end def test_invalid_build new_client = companies(:first_firm).clients_of_firm.build - assert new_client.new_record? + assert !new_client.persisted? assert !new_client.valid? assert_equal new_client, companies(:first_firm).clients_of_firm.last assert !companies(:first_firm).save - assert new_client.new_record? + assert !new_client.persisted? assert_equal 1, companies(:first_firm).clients_of_firm(true).size end @@ -420,8 +420,8 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa assert_equal no_of_firms, Firm.count # Firm was not saved to database. assert_equal no_of_clients, Client.count # Clients were not saved to database. assert new_firm.save - assert !new_firm.new_record? - assert !c.new_record? + assert new_firm.persisted? + assert c.persisted? assert_equal new_firm, c.firm assert_equal no_of_firms + 1, Firm.count # Firm was saved to database. assert_equal no_of_clients + 2, Client.count # Clients were saved to database. @@ -455,7 +455,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } - assert !new_client.new_record? + assert new_client.persisted? assert_equal 2, company.clients_of_firm(true).size end @@ -475,7 +475,7 @@ class TestDefaultAutosaveAssociationOnAHasManyAssociation < ActiveRecord::TestCa company.name += '-changed' assert_queries(2) { assert company.save } - assert !new_client.new_record? + assert new_client.persisted? assert_equal 2, company.clients_of_firm(true).size end @@ -507,62 +507,62 @@ class TestDefaultAutosaveAssociationOnNewRecord < ActiveRecord::TestCase new_account = Account.new("credit_limit" => 1000) new_firm = Firm.new("name" => "some firm") - assert new_firm.new_record? + assert !new_firm.persisted? new_account.firm = new_firm new_account.save! - assert !new_firm.new_record? + assert new_firm.persisted? new_account = Account.new("credit_limit" => 1000) new_autosaved_firm = Firm.new("name" => "some firm") - assert new_autosaved_firm.new_record? + assert !new_autosaved_firm.persisted? new_account.unautosaved_firm = new_autosaved_firm new_account.save! - assert new_autosaved_firm.new_record? + assert !new_autosaved_firm.persisted? end def test_autosave_new_record_on_has_one_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert account.new_record? + assert !account.persisted? firm.account = account firm.save! - assert !account.new_record? + assert account.persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) firm.unautosaved_account = account - assert account.new_record? + assert !account.persisted? firm.unautosaved_account = account firm.save! - assert account.new_record? + assert !account.persisted? end def test_autosave_new_record_on_has_many_can_be_disabled_per_relationship firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert account.new_record? + assert !account.persisted? firm.accounts << account firm.save! - assert !account.new_record? + assert account.persisted? firm = Firm.new("name" => "some firm") account = Account.new("credit_limit" => 1000) - assert account.new_record? + assert !account.persisted? firm.unautosaved_accounts << account firm.save! - assert account.new_record? + assert !account.persisted? end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e63e1fbe09..9f2b0c9c86 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -656,8 +656,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_new_record_returns_boolean - assert_equal true, Topic.new.new_record? - assert_equal false, Topic.find(1).new_record? + assert_equal false, Topic.new.persisted? + assert_equal true, Topic.find(1).persisted? end def test_clone @@ -665,7 +665,7 @@ class BasicsTest < ActiveRecord::TestCase cloned_topic = nil assert_nothing_raised { cloned_topic = topic.clone } assert_equal topic.title, cloned_topic.title - assert cloned_topic.new_record? + assert !cloned_topic.persisted? # test if the attributes have been cloned topic.title = "a" @@ -684,7 +684,7 @@ class BasicsTest < ActiveRecord::TestCase # test if saved clone object differs from original cloned_topic.save - assert !cloned_topic.new_record? + assert cloned_topic.persisted? assert_not_equal cloned_topic.id, topic.id cloned_topic.reload @@ -700,7 +700,7 @@ class BasicsTest < ActiveRecord::TestCase assert_nothing_raised { clone = dev.clone } assert_kind_of DeveloperSalary, clone.salary assert_equal dev.salary.amount, clone.salary.amount - assert clone.new_record? + assert !clone.persisted? # test if the attributes have been cloned original_amount = clone.salary.amount @@ -708,7 +708,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal original_amount, clone.salary.amount assert clone.save - assert !clone.new_record? + assert clone.persisted? assert_not_equal clone.id, dev.id end @@ -1353,7 +1353,7 @@ class BasicsTest < ActiveRecord::TestCase def test_inspect_instance topic = topics(:first) - assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil>), topic.inspect + assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_s(:db)}", bonus_time: "#{topic.bonus_time.to_s(:db)}", last_read: "#{topic.last_read.to_s(:db)}", content: "Have a nice day", approved: false, replies_count: 1, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_s(:db)}", updated_at: "#{topic.updated_at.to_s(:db)}">), topic.inspect end def test_inspect_new_instance diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index bde93d1c85..b1a54af192 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -395,6 +395,20 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_save_always_should_update_timestamps_when_serialized_attributes_are_present + with_partial_updates(Topic) do + topic = Topic.create!(:content => {:a => "a"}) + topic.save! + + updated_at = topic.updated_at + topic.content[:hello] = 'world' + topic.save! + + assert_not_equal updated_at, topic.updated_at + assert_equal 'world', topic.content[:hello] + end + end + def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present with_partial_updates(Topic) do Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index c058196078..39ce47d9d6 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -10,6 +10,7 @@ require 'models/entrant' require 'models/project' require 'models/developer' require 'models/customer' +require 'models/toy' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers, :categories, :categorizations @@ -726,7 +727,7 @@ class FinderTest < ActiveRecord::TestCase sig38 = Company.find_or_create_by_name("38signals") assert_equal number_of_companies + 1, Company.count assert_equal sig38, Company.find_or_create_by_name("38signals") - assert !sig38.new_record? + assert sig38.persisted? end def test_find_or_create_from_two_attributes @@ -734,7 +735,7 @@ class FinderTest < ActiveRecord::TestCase another = Topic.find_or_create_by_title_and_author_name("Another topic","John") assert_equal number_of_topics + 1, Topic.count assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") - assert !another.new_record? + assert another.persisted? end def test_find_or_create_from_two_attributes_with_one_being_an_aggregate @@ -742,7 +743,7 @@ class FinderTest < ActiveRecord::TestCase created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") assert_equal number_of_customers + 1, Customer.count assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") - assert !created_customer.new_record? + assert created_customer.persisted? end def test_find_or_create_from_one_attribute_and_hash @@ -750,7 +751,7 @@ class FinderTest < ActiveRecord::TestCase sig38 = Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) assert_equal number_of_companies + 1, Company.count assert_equal sig38, Company.find_or_create_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) - assert !sig38.new_record? + assert sig38.persisted? assert_equal "38signals", sig38.name assert_equal 17, sig38.firm_id assert_equal 23, sig38.client_of @@ -761,7 +762,7 @@ class FinderTest < ActiveRecord::TestCase created_customer = Customer.find_or_create_by_balance(Money.new(123)) assert_equal number_of_customers + 1, Customer.count assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) - assert !created_customer.new_record? + assert created_customer.persisted? end def test_find_or_create_from_one_aggregate_attribute_and_hash @@ -771,7 +772,7 @@ class FinderTest < ActiveRecord::TestCase created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name}) assert_equal number_of_customers + 1, Customer.count assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name}) - assert !created_customer.new_record? + assert created_customer.persisted? assert_equal balance, created_customer.balance assert_equal name, created_customer.name end @@ -779,13 +780,13 @@ class FinderTest < ActiveRecord::TestCase def test_find_or_initialize_from_one_attribute sig38 = Company.find_or_initialize_by_name("38signals") assert_equal "38signals", sig38.name - assert sig38.new_record? + assert !sig38.persisted? end def test_find_or_initialize_from_one_aggregate_attribute new_customer = Customer.find_or_initialize_by_balance(Money.new(123)) assert_equal 123, new_customer.balance.amount - assert new_customer.new_record? + assert !new_customer.persisted? end def test_find_or_initialize_from_one_attribute_should_not_set_attribute_even_when_protected @@ -793,7 +794,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_not_equal 1000, c.rating assert c.valid? - assert c.new_record? + assert !c.persisted? end def test_find_or_create_from_one_attribute_should_not_set_attribute_even_when_protected @@ -801,7 +802,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_not_equal 1000, c.rating assert c.valid? - assert !c.new_record? + assert c.persisted? end def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected @@ -809,7 +810,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating assert c.valid? - assert c.new_record? + assert !c.persisted? end def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected @@ -817,7 +818,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating assert c.valid? - assert !c.new_record? + assert c.persisted? end def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash @@ -825,7 +826,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating assert c.valid? - assert c.new_record? + assert !c.persisted? end def test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected_and_also_set_the_hash @@ -833,7 +834,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000, c.rating assert c.valid? - assert !c.new_record? + assert c.persisted? end def test_find_or_initialize_should_set_protected_attributes_if_given_as_block @@ -841,7 +842,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000.to_f, c.rating.to_f assert c.valid? - assert c.new_record? + assert !c.persisted? end def test_find_or_create_should_set_protected_attributes_if_given_as_block @@ -849,7 +850,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000.to_f, c.rating.to_f assert c.valid? - assert !c.new_record? + assert c.persisted? end def test_find_or_create_should_work_with_block_on_first_call @@ -860,21 +861,21 @@ class FinderTest < ActiveRecord::TestCase assert_equal "Fortune 1000", c.name assert_equal 1000.to_f, c.rating.to_f assert c.valid? - assert !c.new_record? + assert c.persisted? end def test_find_or_initialize_from_two_attributes another = Topic.find_or_initialize_by_title_and_author_name("Another topic","John") assert_equal "Another topic", another.title assert_equal "John", another.author_name - assert another.new_record? + assert !another.persisted? end def test_find_or_initialize_from_one_aggregate_attribute_and_one_not new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") assert_equal 123, new_customer.balance.amount assert_equal "Elizabeth", new_customer.name - assert new_customer.new_record? + assert !new_customer.persisted? end def test_find_or_initialize_from_one_attribute_and_hash @@ -882,7 +883,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal "38signals", sig38.name assert_equal 17, sig38.firm_id assert_equal 23, sig38.client_of - assert sig38.new_record? + assert !sig38.persisted? end def test_find_or_initialize_from_one_aggregate_attribute_and_hash @@ -891,7 +892,7 @@ class FinderTest < ActiveRecord::TestCase new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name}) assert_equal balance, new_customer.balance assert_equal name, new_customer.name - assert new_customer.new_record? + assert !new_customer.persisted? end def test_find_with_bad_sql @@ -1010,6 +1011,15 @@ class FinderTest < ActiveRecord::TestCase end end + def test_find_one_message_with_custom_primary_key + Toy.set_primary_key :name + begin + Toy.find 'Hello World!' + rescue ActiveRecord::RecordNotFound => e + assert_equal 'Couldn\'t find Toy with name=Hello World!', e.message + end + end + protected def bind(statement, *vars) if vars.first.is_a?(Hash) diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f6ef155d66..52f26b71f5 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -48,6 +48,10 @@ end ActiveRecord::Base.connection.class.class_eval do IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /SHOW FIELDS/] + # FIXME: this needs to be refactored so specific database can add their own + # ignored SQL. This ignored SQL is for Oracle. + IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im] + def execute_with_query_record(sql, name = nil, &block) $queries_executed ||= [] $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r } @@ -56,13 +60,13 @@ ActiveRecord::Base.connection.class.class_eval do alias_method_chain :execute, :query_record - def exec_with_query_record(sql, name = nil, binds = [], &block) + def exec_query_with_query_record(sql, name = nil, binds = [], &block) $queries_executed ||= [] $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r } - exec_without_query_record(sql, name, binds, &block) + exec_query_without_query_record(sql, name, binds, &block) end - alias_method_chain :exec, :query_record + alias_method_chain :exec_query, :query_record end ActiveRecord::Base.connection.class.class_eval { diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 60f89da95e..92af53d56f 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -139,6 +139,14 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase assert_equal 'gardening', interest.reload.topic end + def test_reject_if_with_blank_nested_attributes_id + # When using a select list to choose an existing 'ship' id, with :include_blank => true + Pirate.accepts_nested_attributes_for :ship, :reject_if => proc {|attributes| attributes[:id].blank? } + + pirate = Pirate.new(:catchphrase => "Stop wastin' me time") + pirate.ship_attributes = { :id => "" } + assert_nothing_raised(ActiveRecord::RecordNotFound) { pirate.save! } + end end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @@ -163,7 +171,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase @ship.destroy @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } - assert @pirate.ship.new_record? + assert !@pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end @@ -184,7 +192,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_replace_an_existing_record_if_there_is_no_id @pirate.reload.ship_attributes = { :name => 'Davy Jones Gold Dagger' } - assert @pirate.ship.new_record? + assert !@pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name assert_equal 'Nights Dirty Lightning', @ship.name end @@ -256,7 +264,7 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase def test_should_also_work_with_a_HashWithIndifferentAccess @pirate.ship_attributes = HashWithIndifferentAccess.new(:id => @ship.id, :name => 'Davy Jones Gold Dagger') - assert !@pirate.ship.new_record? + assert @pirate.ship.persisted? assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name end @@ -348,7 +356,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @pirate.destroy @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } - assert @ship.pirate.new_record? + assert !@ship.pirate.persisted? assert_equal 'Arr', @ship.pirate.catchphrase end @@ -369,7 +377,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase def test_should_replace_an_existing_record_if_there_is_no_id @ship.reload.pirate_attributes = { :catchphrase => 'Arr' } - assert @ship.pirate.new_record? + assert !@ship.pirate.persisted? assert_equal 'Arr', @ship.pirate.catchphrase assert_equal 'Aye', @pirate.catchphrase end @@ -458,7 +466,7 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase @pirate.delete @ship.reload.attributes = { :update_only_pirate_attributes => { :catchphrase => 'Arr' } } - assert @ship.update_only_pirate.new_record? + assert !@ship.update_only_pirate.persisted? end def test_should_update_existing_when_update_only_is_true_and_no_id_is_given @@ -596,10 +604,10 @@ module NestedAttributesOnACollectionAssociationTests association_getter => { 'foo' => { :name => 'Grace OMalley' }, 'bar' => { :name => 'Privateers Greed' }} } - assert @pirate.send(@association_name).first.new_record? + assert !@pirate.send(@association_name).first.persisted? assert_equal 'Grace OMalley', @pirate.send(@association_name).first.name - assert @pirate.send(@association_name).last.new_record? + assert !@pirate.send(@association_name).last.persisted? assert_equal 'Privateers Greed', @pirate.send(@association_name).last.name end diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index ffe6fb95b8..07262f56be 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -241,6 +241,15 @@ class PersistencesTest < ActiveRecord::TestCase assert_nothing_raised { minimalistic.save } end + def test_update_sti_type + assert_instance_of Reply, topics(:second) + + topic = topics(:second).becomes(Topic) + assert_instance_of Topic, topic + topic.save! + assert_instance_of Topic, Topic.find(topic.id) + end + def test_delete topic = Topic.find(1) assert_equal topic, topic.delete, 'topic.delete did not return self' diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index eeb619ac2f..3b9e4f42a6 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -24,25 +24,25 @@ class ReflectionTest < ActiveRecord::TestCase def test_read_attribute_names assert_equal( - %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type ).sort, + %w( id title author_name author_email_address bonus_time written_on last_read content group approved replies_count parent_id parent_title type created_at updated_at ).sort, @first.attribute_names ) end def test_columns - assert_equal 14, Topic.columns.length + assert_equal 16, Topic.columns.length end def test_columns_are_returned_in_the_order_they_were_declared column_names = Topic.columns.map { |column| column.name } - assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group), column_names + assert_equal %w(id title author_name author_email_address written_on bonus_time last_read content approved replies_count parent_id parent_title type group created_at updated_at), column_names end def test_content_columns content_columns = Topic.content_columns content_column_names = content_columns.map {|column| column.name} - assert_equal 10, content_columns.length - assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title).sort, content_column_names.sort + assert_equal 12, content_columns.length + assert_equal %w(title author_name author_email_address written_on bonus_time last_read content group approved parent_title created_at updated_at).sort, content_column_names.sort end def test_column_string_type_and_limit diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index f4f3dc4d5a..b44c716db8 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'models/tag' require 'models/tagging' require 'models/post' require 'models/topic' @@ -17,7 +18,7 @@ require 'models/tyre' class RelationTest < ActiveRecord::TestCase fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments, - :taggings, :cars + :tags, :taggings, :cars def test_bind_values relation = Post.scoped @@ -144,6 +145,16 @@ class RelationTest < ActiveRecord::TestCase assert_equal entrants(:first).name, entrants.first.name end + def test_finding_with_complex_order_and_limit + tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").limit(1).to_a + assert_equal 1, tags.length + end + + def test_finding_with_complex_order + tags = Tag.includes(:taggings).order("REPLACE('abc', taggings.taggable_type, taggings.taggable_type)").to_a + assert_equal 2, tags.length + end + def test_finding_with_order_limit_and_offset entrants = Entrant.order("id ASC").limit(2).offset(1) @@ -372,7 +383,7 @@ class RelationTest < ActiveRecord::TestCase lifo = authors.find_or_initialize_by_name('Lifo') assert_equal "Lifo", lifo.name - assert lifo.new_record? + assert !lifo.persisted? assert_equal authors(:david), authors.find_or_initialize_by_name(:name => 'David') end @@ -382,7 +393,7 @@ class RelationTest < ActiveRecord::TestCase lifo = authors.find_or_create_by_name('Lifo') assert_equal "Lifo", lifo.name - assert ! lifo.new_record? + assert lifo.persisted? assert_equal authors(:david), authors.find_or_create_by_name(:name => 'David') end @@ -616,10 +627,10 @@ class RelationTest < ActiveRecord::TestCase sparrow = birds.create assert_kind_of Bird, sparrow - assert sparrow.new_record? + assert !sparrow.persisted? hen = birds.where(:name => 'hen').create - assert ! hen.new_record? + assert hen.persisted? assert_equal 'hen', hen.name end @@ -630,7 +641,7 @@ class RelationTest < ActiveRecord::TestCase hen = birds.where(:name => 'hen').create! assert_kind_of Bird, hen - assert ! hen.new_record? + assert hen.persisted? assert_equal 'hen', hen.name end diff --git a/activerecord/test/cases/session_store/sql_bypass.rb b/activerecord/test/cases/session_store/sql_bypass.rb index f0ba166465..7402b2afd6 100644 --- a/activerecord/test/cases/session_store/sql_bypass.rb +++ b/activerecord/test/cases/session_store/sql_bypass.rb @@ -18,9 +18,9 @@ module ActiveRecord assert !Session.table_exists? end - def test_new_record? + def test_persisted? s = SqlBypass.new :data => 'foo', :session_id => 10 - assert s.new_record?, 'this is a new record!' + assert !s.persisted?, 'this is a new record!' end def test_not_loaded? diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0fbcef4091..dd9de3510b 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -182,7 +182,7 @@ class TransactionTest < ActiveRecord::TestCase :bonus_time => "2005-01-30t15:28:00.00+01:00", :content => "Have a nice day", :approved => false) - new_record_snapshot = new_topic.new_record? + new_record_snapshot = !new_topic.persisted? id_present = new_topic.has_attribute?(Topic.primary_key) id_snapshot = new_topic.id @@ -195,7 +195,7 @@ class TransactionTest < ActiveRecord::TestCase flunk rescue => e assert_equal "Make the transaction rollback", e.message - assert_equal new_record_snapshot, new_topic.new_record?, "The topic should have its old new_record value" + assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value" assert_equal id_snapshot, new_topic.id, "The topic should have its old id" assert_equal id_present, new_topic.has_attribute?(Topic.primary_key) ensure @@ -370,21 +370,21 @@ class TransactionTest < ActiveRecord::TestCase assert topic_2.save @first.save @second.destroy - assert_equal false, topic_1.new_record? + assert_equal true, topic_1.persisted? assert_not_nil topic_1.id - assert_equal false, topic_2.new_record? + assert_equal true, topic_2.persisted? assert_not_nil topic_2.id - assert_equal false, @first.new_record? + assert_equal true, @first.persisted? assert_not_nil @first.id assert_equal true, @second.destroyed? raise ActiveRecord::Rollback end - assert_equal true, topic_1.new_record? + assert_equal false, topic_1.persisted? assert_nil topic_1.id - assert_equal true, topic_2.new_record? + assert_equal false, topic_2.persisted? assert_nil topic_2.id - assert_equal false, @first.new_record? + assert_equal true, @first.persisted? assert_not_nil @first.id assert_equal false, @second.destroyed? end diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index c942036128..99f921879c 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -33,15 +33,3 @@ ActiveRecord::Base.configurations = { ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' -# for assert_queries test helper -ActiveRecord::Base.connection.class.class_eval do - IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from ((all|user)_tab_columns|(all|user)_triggers|(all|user)_constraints)/im] - - def select_with_query_record(sql, name = nil) - $queries_executed ||= [] - $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r } - select_without_query_record(sql, name) - end - - alias_method_chain :select, :query_record -end diff --git a/activerecord/test/models/eye.rb b/activerecord/test/models/eye.rb index 77f17b578e..dc8ae2b3f6 100644 --- a/activerecord/test/models/eye.rb +++ b/activerecord/test/models/eye.rb @@ -17,7 +17,7 @@ class Eye < ActiveRecord::Base after_save :trace_after_save2 def trace_after_create - (@after_create_callbacks_stack ||= []) << iris.new_record? + (@after_create_callbacks_stack ||= []) << !iris.persisted? end alias trace_after_create2 trace_after_create diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index d89c8cf381..f2c45053e7 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -48,7 +48,7 @@ class Pirate < ActiveRecord::Base end def reject_empty_ships_on_create(attributes) - attributes.delete('_reject_me_if_new').present? && new_record? + attributes.delete('_reject_me_if_new').present? && !persisted? end attr_accessor :cancel_save_from_callback diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index d4b8b91de8..8e28f8b86b 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -8,7 +8,7 @@ class Subject < ActiveRecord::Base protected def set_email_address - if self.new_record? + unless self.persisted? self.author_email_address = 'test@test.com' end end diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index 82d4b5997f..6496f36f7e 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -89,7 +89,7 @@ class Topic < ActiveRecord::Base end def set_email_address - if self.new_record? + unless self.persisted? self.author_email_address = 'test@test.com' end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index bb80a1ca17..d4eb56c4d7 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -552,6 +552,7 @@ ActiveRecord::Schema.define do t.string :parent_title t.string :type t.string :group + t.timestamps end create_table :toys, :primary_key => :toy_id ,:force => true do |t| diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 24fe3072e0..386bafd7de 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -2,6 +2,10 @@ * No changes +*Rails 3.0.2 (unreleased)* + +* No changes + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 3b6a29f7d6..6e8cce0d27 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -4,6 +4,10 @@ * Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] +*Rails 3.0.2 (unreleased)* + +* Added before_remove_const callback to ActiveSupport::Dependencies.remove_unloadable_constants! [Andrew White] + *Rails 3.0.1 (October 15, 2010)* * No Changes, just a version bump. diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 84f6f29572..18182bbb40 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/file/atomic' require 'active_support/core_ext/string/conversions' +require 'rack/utils' module ActiveSupport module Cache @@ -11,8 +12,6 @@ module ActiveSupport attr_reader :cache_path DIR_FORMATTER = "%03X" - ESCAPE_FILENAME_CHARS = /[^a-z0-9_.-]/i - UNESCAPE_FILENAME_CHARS = /%[0-9A-F]{2}/ def initialize(cache_path, options = nil) super(options) @@ -136,7 +135,7 @@ module ActiveSupport # Translate a key into a file path. def key_file_path(key) - fname = key.to_s.gsub(ESCAPE_FILENAME_CHARS){|match| "%#{match.ord.to_s(16).upcase}"} + fname = Rack::Utils.escape(key) hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) @@ -156,7 +155,7 @@ module ActiveSupport # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.size, path.size].split(File::SEPARATOR, 4).last - fname.gsub(UNESCAPE_FILENAME_CHARS){|match| $1.ord.to_s(16)} + Rack::Utils.unescape(fname) end # Delete empty directories in the cache. diff --git a/activesupport/lib/active_support/core_ext/cgi.rb b/activesupport/lib/active_support/core_ext/cgi.rb deleted file mode 100644 index 7279a3d4da..0000000000 --- a/activesupport/lib/active_support/core_ext/cgi.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/cgi/escape_skipping_slashes' diff --git a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb b/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb deleted file mode 100644 index d3c3575748..0000000000 --- a/activesupport/lib/active_support/core_ext/cgi/escape_skipping_slashes.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'cgi' - -class CGI #:nodoc: - if RUBY_VERSION >= '1.9' - def self.escape_skipping_slashes(str) - str = str.join('/') if str.respond_to? :join - str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do - "%#{$1.unpack('H2' * $1.bytesize).join('%').upcase}" - end.tr(' ', '+') - end - else - def self.escape_skipping_slashes(str) - str = str.join('/') if str.respond_to? :join - str.gsub(/([^ \/a-zA-Z0-9_.-])/n) do - "%#{$1.unpack('H2').first.upcase}" - end.tr(' ', '+') - end - end -end diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index 501483498d..fd1cda991e 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/deep_merge' +require 'active_support/core_ext/hash/deep_dup' require 'active_support/core_ext/hash/diff' require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/indifferent_access' diff --git a/activesupport/lib/active_support/core_ext/hash/deep_dup.rb b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb new file mode 100644 index 0000000000..447142605c --- /dev/null +++ b/activesupport/lib/active_support/core_ext/hash/deep_dup.rb @@ -0,0 +1,11 @@ +class Hash + # Returns a deep copy of hash. + def deep_dup + duplicate = self.dup + duplicate.each_pair do |k,v| + tv = duplicate[k] + duplicate[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_dup : v + end + duplicate + end +end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 77a3cfc21d..eda9694614 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -30,35 +30,4 @@ class Object else alias_method :instance_variable_names, :instance_variables end - - # Copies the instance variables of +object+ into +self+. - # - # Instance variable names in the +exclude+ array are ignored. If +object+ - # responds to <tt>protected_instance_variables</tt> the ones returned are - # also ignored. For example, Rails controllers implement that method. - # - # In both cases strings and symbols are understood, and they have to include - # the at sign. - # - # class C - # def initialize(x, y, z) - # @x, @y, @z = x, y, z - # end - # - # def protected_instance_variables - # %w(@z) - # end - # end - # - # a = C.new(0, 1, 2) - # b = C.new(3, 4, 5) - # - # a.copy_instance_variables_from(b, [:@y]) - # # a is now: @x = 3, @y = 1, @z = 2 - def copy_instance_variables_from(object, exclude = []) #:nodoc: - exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables - - vars = object.instance_variables.map(&:to_s) - exclude.map(&:to_s) - vars.each { |name| instance_variable_set(name, object.instance_variable_get(name)) } - end end diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 48e9d04787..4d742b9ed2 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -8,6 +8,8 @@ class Object # *Unlike* that method however, a +NoMethodError+ exception will *not* be raised # and +nil+ will be returned instead, if the receiving object is a +nil+ object or NilClass. # + # If try is called without a method to call, it will yield any given block with the object. + # # ==== Examples # # Without try @@ -21,10 +23,20 @@ class Object # +try+ also accepts arguments and/or a block, for the method it is trying # Person.try(:find, 1) # @people.try(:collect) {|p| p.name} + # + # Without a method argument try will yield to the block unless the reciever is nil. + # @person.try { |p| "#{p.first_name} #{p.last_name}" } #-- # +try+ behaves like +Object#send+, unless called on +NilClass+. - alias_method :try, :__send__ + def try(*a, &b) + if a.empty? && block_given? + yield self + else + __send__(*a, &b) + end + end + end class NilClass #:nodoc: diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index c406dd3c2e..320f5c1c92 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -28,7 +28,7 @@ module ActiveSupport end def self.new_from_hash_copying_default(hash) - ActiveSupport::HashWithIndifferentAccess.new(hash).tap do |new_hash| + new(hash).tap do |new_hash| new_hash.default = hash.default end end @@ -97,7 +97,9 @@ module ActiveSupport # Returns an exact copy of the hash. def dup - HashWithIndifferentAccess.new(self) + self.class.new(self).tap do |new_hash| + new_hash.default = default + end end # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash @@ -142,7 +144,7 @@ module ActiveSupport when Hash self.class.new_from_hash_copying_default(value) when Array - value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e } + value.dup.replace(value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e }) else value end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 7f148dc853..8e157d3af4 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -137,6 +137,8 @@ module ActiveSupport alias_method :each_pair, :each + alias_method :select, :find_all + def clear super @keys.clear diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 93f5d5a0cc..6a7da8266c 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -73,7 +73,7 @@ module ActiveSupport # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your system's <tt>ENV['TZ']</tt> zone def localtime - utc.getlocal + utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal end alias_method :getlocal, :localtime diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index b6a8cf3caf..cddfcddb57 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -126,9 +126,11 @@ module ActiveSupport end def rename_key(key, options = {}) - camelize = options.has_key?(:camelize) && options[:camelize] + camelize = options[:camelize] dasherize = !options.has_key?(:dasherize) || options[:dasherize] - key = key.camelize if camelize + if camelize + key = true == camelize ? key.camelize : key.camelize(camelize) + end key = _dasherize(key) if dasherize key end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 28ef695a4b..579d5dad24 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -356,9 +356,13 @@ module CacheDeleteMatchedBehavior def test_delete_matched @cache.write("foo", "bar") @cache.write("fu", "baz") + @cache.write("foo/bar", "baz") + @cache.write("fu/baz", "bar") @cache.delete_matched(/oo/) assert_equal false, @cache.exist?("foo") assert_equal true, @cache.exist?("fu") + assert_equal false, @cache.exist?("foo/bar") + assert_equal true, @cache.exist?("fu/baz") end end @@ -509,6 +513,11 @@ class FileStoreTest < ActiveSupport::TestCase assert_nil old_cache.read('foo') end end + + def test_key_transformation + key = @cache.send(:key_file_path, "views/index?id=1") + assert_equal "views/index?id=1", @cache.send(:file_path_key, key) + end end class MemoryStoreTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/cgi_ext_test.rb b/activesupport/test/core_ext/cgi_ext_test.rb deleted file mode 100644 index c80362e382..0000000000 --- a/activesupport/test/core_ext/cgi_ext_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/cgi' - -class EscapeSkippingSlashesTest < Test::Unit::TestCase - def test_array - assert_equal 'hello/world', CGI.escape_skipping_slashes(%w(hello world)) - assert_equal 'hello+world/how/are/you', CGI.escape_skipping_slashes(['hello world', 'how', 'are', 'you']) - end - - def test_typical - assert_equal 'hi', CGI.escape_skipping_slashes('hi') - assert_equal 'hi/world', CGI.escape_skipping_slashes('hi/world') - assert_equal 'hi/world+you+funky+thing', CGI.escape_skipping_slashes('hi/world you funky thing') - end -end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 545fed2684..370f26b0d7 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -6,6 +6,12 @@ require 'active_support/ordered_hash' require 'active_support/core_ext/object/conversions' class HashExtTest < Test::Unit::TestCase + class IndifferentHash < HashWithIndifferentAccess + end + + class SubclassingArray < Array + end + def setup @strings = { 'a' => 1, 'b' => 2 } @symbols = { :a => 1, :b => 2 } @@ -248,6 +254,20 @@ class HashExtTest < Test::Unit::TestCase hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] end + + def test_should_preserve_array_subclass_when_value_is_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array }}.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end + + def test_should_preserve_array_class_when_hash_value_is_frozen_array + array = SubclassingArray.new + array << { "address" => "1" } + hash = { "urls" => { "url" => array.freeze }}.with_indifferent_access + assert_equal SubclassingArray, hash[:urls][:url].class + end def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash h = HashWithIndifferentAccess.new @@ -267,7 +287,6 @@ class HashExtTest < Test::Unit::TestCase assert_equal 1, h['first'] end - def test_indifferent_subhashes h = {'user' => {'id' => 5}}.with_indifferent_access ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} @@ -276,6 +295,17 @@ class HashExtTest < Test::Unit::TestCase ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} end + def test_indifferent_duplication + # Should preserve default value + h = HashWithIndifferentAccess.new + h.default = '1234' + assert_equal h.default, h.dup.default + + # Should preserve class for subclasses + h = IndifferentHash.new + assert_equal h.class, h.dup.class + end + def test_assert_valid_keys assert_nothing_raised do { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) @@ -316,6 +346,21 @@ class HashExtTest < Test::Unit::TestCase assert_equal expected, hash_1 end + def test_deep_dup + hash = { :a => { :b => 'b' } } + dup = hash.deep_dup + dup[:a][:c] = 'c' + assert_equal nil, hash[:a][:c] + assert_equal 'c', dup[:a][:c] + end + + def test_deep_dup_initialize + zero_hash = Hash.new 0 + hash = { :a => zero_hash } + dup = hash.deep_dup + assert_equal 0, dup[:a][44] + end + def test_store_on_indifferent_access hash = HashWithIndifferentAccess.new hash.store(:test1, 1) @@ -486,7 +531,7 @@ class HashExtToParamTests < Test::Unit::TestCase def test_to_param_hash_escapes_its_keys_and_values assert_equal 'param+1=A+string+with+%2F+characters+%26+that+should+be+%3F+escaped', { 'param 1' => 'A string with / characters & that should be ? escaped' }.to_param end - + def test_to_param_orders_by_key_in_ascending_order assert_equal 'a=2&b=1&c=0', ActiveSupport::OrderedHash[*%w(b 1 c 0 a 2)].to_param end @@ -525,6 +570,13 @@ class HashToXmlTest < Test::Unit::TestCase assert xml.include?(%(<Name>David</Name>)) end + def test_one_level_camelize_lower + xml = { :name => "David", :street_name => "Paulina" }.to_xml(@xml_options.merge(:camelize => :lower)) + assert_equal "<person>", xml.first(8) + assert xml.include?(%(<streetName>Paulina</streetName>)) + assert xml.include?(%(<name>David</name>)) + end + def test_one_level_with_types xml = { :name => "David", :street => "Paulina", :age => 26, :age_in_millis => 820497600000, :moved_on => Date.new(2005, 11, 15), :resident => :yes }.to_xml(@xml_options) assert_equal "<person>", xml.first(8) diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index 3ccf18f473..398e6ca9b2 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -82,37 +82,6 @@ class ObjectInstanceVariableTest < Test::Unit::TestCase assert_equal %w(@bar @baz), @source.instance_variable_names.sort end - def test_copy_instance_variables_from_without_explicit_excludes - assert_equal [], @dest.instance_variables - @dest.copy_instance_variables_from(@source) - - assert_equal %w(@bar @baz), @dest.instance_variables.sort.map(&:to_s) - %w(@bar @baz).each do |name| - assert_equal @source.instance_variable_get(name).object_id, - @dest.instance_variable_get(name).object_id - end - end - - def test_copy_instance_variables_from_with_explicit_excludes - @dest.copy_instance_variables_from(@source, ['@baz']) - assert !@dest.instance_variable_defined?('@baz') - assert_equal 'bar', @dest.instance_variable_get('@bar') - end - - def test_copy_instance_variables_automatically_excludes_protected_instance_variables - @source.instance_variable_set(:@quux, 'quux') - class << @source - def protected_instance_variables - ['@bar', :@quux] - end - end - - @dest.copy_instance_variables_from(@source) - assert !@dest.instance_variable_defined?('@bar') - assert !@dest.instance_variable_defined?('@quux') - assert_equal 'baz', @dest.instance_variable_get('@baz') - end - def test_instance_values object = Object.new object.instance_variable_set :@a, 1 @@ -165,4 +134,14 @@ class ObjectTryTest < Test::Unit::TestCase def test_false_try assert_equal 'false', false.try(:to_s) end + + def test_try_only_block + assert_equal @string.reverse, @string.try { |s| s.reverse } + end + + def test_try_only_block_nil + ran = false + nil.try { ran = true } + assert_equal false, ran + end end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 0bb2c4a39e..5579c27215 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -36,6 +36,10 @@ class TimeWithZoneTest < Test::Unit::TestCase assert_equal @twz.object_id, @twz.in_time_zone(ActiveSupport::TimeZone['Eastern Time (US & Canada)']).object_id end + def test_localtime + assert_equal @twz.localtime, @twz.utc.getlocal + end + def test_utc? assert_equal false, @twz.utc? assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc? @@ -763,6 +767,13 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < Test::Unit::TestCase end end + def test_localtime + Time.zone_default = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + assert_equal @dt.in_time_zone.localtime, @dt.in_time_zone.utc.to_time.getlocal + ensure + Time.zone_default = nil + end + def test_use_zone Time.zone = 'Alaska' Time.use_zone 'Hawaii' do diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 50778b5864..72088854fc 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -109,6 +109,14 @@ class OrderedHashTest < Test::Unit::TestCase assert_equal @keys, keys end + def test_find_all + assert_equal @keys, @ordered_hash.find_all { true }.map(&:first) + end + + def test_select + assert_equal @keys, @ordered_hash.select { true }.map(&:first) + end + def test_delete_if copy = @ordered_hash.dup copy.delete('pink') diff --git a/activesupport/test/test_xml_mini.rb b/activesupport/test/test_xml_mini.rb index 585eb15c6e..309fa234bf 100644 --- a/activesupport/test/test_xml_mini.rb +++ b/activesupport/test/test_xml_mini.rb @@ -14,14 +14,26 @@ class XmlMiniTest < Test::Unit::TestCase assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => false) end - def test_rename_key_camelizes_with_camelize_true - assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) + def test_rename_key_camelizes_with_camelize_false + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => false) + end + + def test_rename_key_camelizes_with_camelize_nil + assert_equal "my_key", ActiveSupport::XmlMini.rename_key("my_key", :camelize => nil) end def test_rename_key_camelizes_with_camelize_true assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => true) end + def test_rename_key_lower_camelizes_with_camelize_lower + assert_equal "myKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :lower) + end + + def test_rename_key_lower_camelizes_with_camelize_upper + assert_equal "MyKey", ActiveSupport::XmlMini.rename_key("my_key", :camelize => :upper) + end + def test_rename_key_does_not_dasherize_leading_underscores assert_equal "_id", ActiveSupport::XmlMini.rename_key("_id") end diff --git a/rails.gemspec b/rails.gemspec index d791e21ec2..98b5f46554 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_dependency('activeresource', version) s.add_dependency('actionmailer', version) s.add_dependency('railties', version) - s.add_dependency('bundler', '~> 1.0.3') + s.add_dependency('bundler', '~> 1.0') end diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 9b1d264d2c..dc1200812e 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -167,6 +167,12 @@ def log_info(sql, name, ms) end </ruby> ++try+ can also be called without arguments but a block, which will only be executed if the object is not nil: + +<ruby> +@person.try { |p| "#{p.first_name} #{p.last_name}" } +</ruby> + NOTE: Defined in +active_support/core_ext/object/try.rb+. h4. +singleton_class+ @@ -395,39 +401,6 @@ C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. -h5. +copy_instance_variables_from(object, exclude = [])+ - -Copies the instance variables of +object+ into +self+. - -Instance variable names in the +exclude+ array are ignored. If +object+ -responds to +protected_instance_variables+ the ones returned are -also ignored. For example, Rails controllers implement that method. - -In both arrays strings and symbols are understood, and they have to include -the at sign. - -<ruby> -class C - def initialize(x, y, z) - @x, @y, @z = x, y, z - end - - def protected_instance_variables - %w(@z) - end -end - -a = C.new(0, 1, 2) -b = C.new(3, 4, 5) - -a.copy_instance_variables_from(b, [:@y]) -# a is now: @x = 3, @y = 1, @z = 2 -</ruby> - -In the example +object+ and +self+ are of the same type, but they don't need to. - -NOTE: Defined in +active_support/core_ext/object/instance_variables.rb+. - h4. Silencing Warnings, Streams, and Exceptions The methods +silence_warnings+ and +enable_warnings+ change the value of +$VERBOSE+ accordingly for the duration of their block, and reset it afterwards: diff --git a/railties/guides/source/api_documentation_guidelines.textile b/railties/guides/source/api_documentation_guidelines.textile index 900b3fb5d5..e3ccd6396c 100644 --- a/railties/guides/source/api_documentation_guidelines.textile +++ b/railties/guides/source/api_documentation_guidelines.textile @@ -116,14 +116,12 @@ Use fixed-width fonts for: * file names <ruby> -# Copies the instance variables of +object+ into +self+. -# -# Instance variable names in the +exclude+ array are ignored. If +object+ -# responds to <tt>protected_instance_variables</tt> the ones returned are -# also ignored. For example, Rails controllers implement that method. -# ... -def copy_instance_variables_from(object, exclude = []) - ... +class Array + # Calls <tt>to_param</tt> on all its elements and joins the result with + # slashes. This is used by <tt>url_for</tt> in Action Pack. + def to_param + collect { |e| e.to_param }.join '/' + end end </ruby> diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index 514be33a40..ded82512d3 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -232,13 +232,13 @@ The corresponding view +app/views/articles/new.html.erb+ using +form_for+ looks <%= form_for @article, :url => { :action => "create" }, :html => {:class => "nifty_form"} do |f| %> <%= f.text_field :title %> <%= f.text_area :body, :size => "60x12" %> - <%= submit_tag "Create" %> + <%= f.submit "Create" %> <% end %> </erb> There are a few things to note here: -# +:article+ is the name of the model and +@article+ is the actual object being edited. +# +@article+ is the actual object being edited. # There is a single hash of options. Routing options are passed in the +:url+ hash, HTML options are passed in the +:html+ hash. # The +form_for+ method yields a *form builder* object (the +f+ variable). # Methods to create form controls are called *on* the form builder object +f+ @@ -294,13 +294,13 @@ When dealing with RESTful resources, calls to +form_for+ can get significantly e <ruby> ## Creating a new article # long-style: -form_for(:article, @article, :url => articles_path) +form_for(@article, :url => articles_path) # same thing, short-style (record identification gets used): form_for(@article) ## Editing an existing article # long-style: -form_for(:article, @article, :url => article_path(@article), :html => { :method => "put" }) +form_for(@article, :url => article_path(@article), :html => { :method => "put" }) # short-style: form_for(@article) </ruby> diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d84373cddf..182068071d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -35,7 +35,6 @@ module Rails # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' - autoload :Configurable, 'rails/application/configurable' autoload :Configuration, 'rails/application/configuration' autoload :Finisher, 'rails/application/finisher' autoload :Railties, 'rails/application/railties' diff --git a/railties/lib/rails/cli.rb b/railties/lib/rails/cli.rb index 1260772605..2b32f7edf1 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -5,10 +5,12 @@ require 'rails/script_rails_loader' # the rest of this script is not run. Rails::ScriptRailsLoader.exec_script_rails! -railties_path = File.expand_path('../../lib', __FILE__) -$:.unshift(railties_path) if File.directory?(railties_path) && !$:.include?(railties_path) - require 'rails/ruby_version_check' Signal.trap("INT") { puts; exit } -require 'rails/commands/application' +if ARGV.first == 'plugin' + ARGV.shift + require 'rails/commands/plugin_new' +else + require 'rails/commands/application' +end diff --git a/railties/lib/rails/commands/plugin_new.rb b/railties/lib/rails/commands/plugin_new.rb new file mode 100644 index 0000000000..00a7e30902 --- /dev/null +++ b/railties/lib/rails/commands/plugin_new.rb @@ -0,0 +1,16 @@ +require 'rails/version' +if %w(--version -v).include? ARGV.first + puts "Rails #{Rails::VERSION::STRING}" + exit(0) +end + +if ARGV.first != "new" + ARGV[0] = "--help" +else + ARGV.shift +end + +require 'rails/generators' +require 'rails/generators/rails/plugin_new/plugin_new_generator' + +Rails::Generators::PluginNewGenerator.start diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 8369795e71..66fab0a760 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,5 +1,6 @@ require 'active_support/deprecation' require 'active_support/ordered_options' +require 'active_support/core_ext/hash/deep_dup' require 'rails/paths' require 'rails/rack' @@ -51,6 +52,13 @@ module Rails @colorize_logging = true end + def initialize_copy(source) + @aliases = @aliases.deep_dup + @options = @options.deep_dup + @fallbacks = @fallbacks.deep_dup + @templates = @templates.dup + end + def method_missing(method, *args) method = method.to_s.sub(/=$/, '').to_sym diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 72bfe207d7..466a31c2a0 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -324,8 +324,8 @@ module Rails # MyEngine::Engine.load_seed # class Engine < Railtie - autoload :Configurable, "rails/engine/configurable" autoload :Configuration, "rails/engine/configuration" + autoload :Railties, "rails/engine/railties" class << self attr_accessor :called_from, :isolated diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index 7a07dcad7d..4f458b0aee 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -10,6 +10,7 @@ module Rails def initialize(root=nil) super() @root = root + @generators = app_generators.dup end # Returns the middleware stack for the engine. diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb new file mode 100644 index 0000000000..f5c626553c --- /dev/null +++ b/railties/lib/rails/generators/app_base.rb @@ -0,0 +1,176 @@ +require 'digest/md5' +require 'active_support/secure_random' +require 'rails/version' unless defined?(Rails::VERSION) +require 'rbconfig' +require 'open-uri' +require 'uri' + +module Rails + module Generators + class AppBase < Base + DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + JAVASCRIPTS = %w( prototype jquery ) + + attr_accessor :rails_template + add_shebang_option! + + argument :app_path, :type => :string + + def self.add_shared_options_for(name) + class_option :builder, :type => :string, :aliases => "-b", + :desc => "Path to a #{name} builder (can be a filesystem path or URL)" + + class_option :template, :type => :string, :aliases => "-m", + :desc => "Path to an #{name} template (can be a filesystem path or URL)" + + class_option :skip_gemfile, :type => :boolean, :default => false, + :desc => "Don't create a Gemfile" + + class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, + :desc => "Skip Git ignores and keeps" + + class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, + :desc => "Skip Active Record files" + + class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", + :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" + + class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype", + :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})" + + class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, + :desc => "Skip javascript files" + + class_option :dev, :type => :boolean, :default => false, + :desc => "Setup the #{name} with Gemfile pointing to your Rails checkout" + + class_option :edge, :type => :boolean, :default => false, + :desc => "Setup the #{name} with Gemfile pointing to Rails repository" + + class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false, + :desc => "Skip Test::Unit files" + + class_option :help, :type => :boolean, :aliases => "-h", :group => :rails, + :desc => "Show this help message and quit" + end + + def initialize(*args) + @original_wd = Dir.pwd + + super + end + + protected + + def builder + @builder ||= begin + if path = options[:builder] + if URI(path).is_a?(URI::HTTP) + contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } + else + contents = open(File.expand_path(path, @original_wd)) {|io| io.read } + end + + prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) + instance_eval(&prok) + end + + builder_class = get_builder_class + builder_class.send(:include, ActionMethods) + builder_class.new(self) + end + end + + def build(meth, *args) + builder.send(meth, *args) if builder.respond_to?(meth) + end + + def create_root + self.destination_root = File.expand_path(app_path, destination_root) + valid_const? + + empty_directory '.' + set_default_accessors! + FileUtils.cd(destination_root) unless options[:pretend] + end + + def apply_rails_template + apply rails_template if rails_template + rescue Thor::Error, LoadError, Errno::ENOENT => e + raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" + end + + def set_default_accessors! + self.rails_template = case options[:template] + when /^http:\/\// + options[:template] + when String + File.expand_path(options[:template], Dir.pwd) + else + options[:template] + end + end + + def database_gemfile_entry + entry = "" + unless options[:skip_active_record] + entry = "gem '#{gem_for_database}'" + entry << ", :require => '#{require_for_database}'" if require_for_database + end + entry + end + + def rails_gemfile_entry + if options.dev? + <<-GEMFILE +gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}' +gem 'arel', :git => 'git://github.com/rails/arel.git' +gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + elsif options.edge? + <<-GEMFILE +gem 'rails', :git => 'git://github.com/rails/rails.git' +gem 'arel', :git => 'git://github.com/rails/arel.git' +gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + else + <<-GEMFILE +gem 'rails', '#{Rails::VERSION::STRING}' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' +# gem 'arel', :git => 'git://github.com/rails/arel.git' +# gem "rack", :git => "git://github.com/rack/rack.git" + GEMFILE + end + end + + def gem_for_database + # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) + case options[:database] + when "oracle" then "ruby-oci8" + when "postgresql" then "pg" + when "sqlite3" then "sqlite3-ruby" + when "frontbase" then "ruby-frontbase" + when "mysql" then "mysql2" + else options[:database] + end + end + + def require_for_database + case options[:database] + when "sqlite3" then "sqlite3" + end + end + + def bundle_if_dev_or_edge + bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + run "#{bundle_command} install" if dev_or_edge? + end + + def dev_or_edge? + options.dev? || options.edge? + end + end + end +end diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb index d12b2ff0e5..c8ee939ad7 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <%%= form_for(@<%= singular_table_name %>) do |f| %> <%% if @<%= singular_table_name %>.errors.any? %> <div id="error_explanation"> @@ -21,3 +22,4 @@ <%%= f.submit %> </div> <%% end %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb index e58b9fbd08..d1bfcbc429 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/edit.html.erb @@ -1,6 +1,8 @@ +<% without_namespacing do -%> <h1>Editing <%= singular_table_name %></h1> <%%= render 'form' %> <%%= link_to 'Show', @<%= singular_table_name %> %> | <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb index 4c46db4d67..435d126ee4 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/index.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <h1>Listing <%= plural_table_name %></h1> <table> @@ -25,3 +26,4 @@ <br /> <%%= link_to 'New <%= human_name %>', new_<%= singular_table_name %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb index 02ae4d015e..fe4d0971c4 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/new.html.erb @@ -1,5 +1,7 @@ +<% without_namespacing do -%> <h1>New <%= singular_table_name %></h1> <%%= render 'form' %> <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb index c0e5ccff1e..bc3a87b99f 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -1,3 +1,4 @@ +<% without_namespacing do -%> <p id="notice"><%%= notice %></p> <% for attribute in attributes -%> @@ -10,3 +11,4 @@ <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> | <%%= link_to 'Back', <%= index_helper %>_path %> +<% end -%> diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 9131a19043..badf981d05 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -30,9 +30,15 @@ module Rails end end + def without_namespacing(&block) + inside_namespace do + concat(capture(&block)) + end + end + def indent(content, multiplier = 2) spaces = " " * multiplier - content.each_line.map {|line| "#{spaces}#{line}" }.join("\n") + content = content.each_line.map {|line| "#{spaces}#{line}" }.join end def wrap_with_namespace(content) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 44f9fde0a6..ef1eb8d237 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,9 +1,4 @@ -require 'digest/md5' -require 'active_support/secure_random' -require 'rails/version' unless defined?(Rails::VERSION) -require 'rbconfig' -require 'open-uri' -require 'uri' +require 'rails/generators/app_base' module Rails module ActionMethods @@ -158,60 +153,16 @@ module Rails RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test] - class AppGenerator < Base - DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JAVASCRIPTS = %w( prototype jquery ) - - attr_accessor :rails_template - add_shebang_option! - - argument :app_path, :type => :string - - class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", - :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" - - class_option :javascript, :type => :string, :aliases => "-j", :default => "prototype", - :desc => "Preconfigure for selected javascript library (options: #{JAVASCRIPTS.join('/')})" - - class_option :builder, :type => :string, :aliases => "-b", - :desc => "Path to an application builder (can be a filesystem path or URL)" - - class_option :template, :type => :string, :aliases => "-m", - :desc => "Path to an application template (can be a filesystem path or URL)" - - class_option :dev, :type => :boolean, :default => false, - :desc => "Setup the application with Gemfile pointing to your Rails checkout" - - class_option :edge, :type => :boolean, :default => false, - :desc => "Setup the application with Gemfile pointing to Rails repository" - - class_option :skip_gemfile, :type => :boolean, :default => false, - :desc => "Don't create a Gemfile" - - class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, - :desc => "Skip Active Record files" - - class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false, - :desc => "Skip Test::Unit files" - - class_option :skip_javascript, :type => :boolean, :aliases => "-J", :default => false, - :desc => "Skip javascript files" - - class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false, - :desc => "Skip Git ignores and keeps" + class AppGenerator < AppBase + add_shared_options_for "application" # Add bin/rails options class_option :version, :type => :boolean, :aliases => "-v", :group => :rails, :desc => "Show Rails version number and quit" - class_option :help, :type => :boolean, :aliases => "-h", :group => :rails, - :desc => "Show this help message and quit" - def initialize(*args) raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank? - @original_wd = Dir.pwd - super if !options[:skip_active_record] && !DATABASES.include?(options[:database]) @@ -223,14 +174,7 @@ module Rails end end - def create_root - self.destination_root = File.expand_path(app_path, destination_root) - valid_app_const? - - empty_directory '.' - set_default_accessors! - FileUtils.cd(destination_root) unless options[:pretend] - end + public_task :create_root def create_root_files build(:readme) @@ -309,16 +253,7 @@ module Rails build(:leftovers) end - def apply_rails_template - apply rails_template if rails_template - rescue Thor::Error, LoadError, Errno::ENOENT => e - raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" - end - - def bundle_if_dev_or_edge - bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - run "#{bundle_command} install" if dev_or_edge? - end + public_task :apply_rails_template, :bundle_if_dev_or_edge protected @@ -326,40 +261,6 @@ module Rails "rails new #{self.arguments.map(&:usage).join(' ')} [options]" end - def builder - @builder ||= begin - if path = options[:builder] - if URI(path).is_a?(URI::HTTP) - contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } - else - contents = open(File.expand_path(path, @original_wd)) {|io| io.read } - end - - prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1) - instance_eval(&prok) - end - - builder_class = defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder - builder_class.send(:include, ActionMethods) - builder_class.new(self) - end - end - - def build(meth, *args) - builder.send(meth, *args) if builder.respond_to?(meth) - end - - def set_default_accessors! - 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) create_file(*args, &block) @@ -388,7 +289,7 @@ module Rails @app_const ||= "#{app_const_base}::Application" end - def valid_app_const? + def valid_const? if app_const =~ /^\d/ raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." elsif RESERVED_NAMES.include?(app_name) @@ -402,28 +303,6 @@ module Rails ActiveSupport::SecureRandom.hex(64) end - def dev_or_edge? - options.dev? || options.edge? - end - - def gem_for_database - # %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - case options[:database] - when "oracle" then "ruby-oci8" - when "postgresql" then "pg" - when "sqlite3" then "sqlite3-ruby" - when "frontbase" then "ruby-frontbase" - when "mysql" then "mysql2" - else options[:database] - end - end - - def require_for_database - case options[:database] - when "sqlite3" then "sqlite3" - end - end - def mysql_socket @mysql_socket ||= [ "/tmp/mysql.sock", # default @@ -442,6 +321,10 @@ module Rails empty_directory(destination, config) create_file("#{destination}/.gitkeep") unless options[:skip_git] end + + def get_builder_class + defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder + end end end end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 40213b1261..86b9e8f40c 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -1,25 +1,8 @@ source 'http://rubygems.org' -<%- if options.dev? -%> -gem 'rails', :path => '<%= Rails::Generators::RAILS_DEV_PATH %>' -gem 'arel', :git => 'git://github.com/rails/arel.git' -gem "rack", :git => "git://github.com/rack/rack.git" -<%- elsif options.edge? -%> -gem 'rails', :git => 'git://github.com/rails/rails.git' -gem 'arel', :git => 'git://github.com/rails/arel.git' -gem "rack", :git => "git://github.com/rack/rack.git" -<%- else -%> -gem 'rails', '<%= Rails::VERSION::STRING %>' +<%= rails_gemfile_entry -%> -# Bundle edge Rails instead: -# gem 'rails', :git => 'git://github.com/rails/rails.git' -# gem 'arel', :git => 'git://github.com/rails/arel.git' -# gem "rack", :git => "git://github.com/rack/rack.git" -<%- end -%> - -<% unless options[:skip_active_record] -%> -gem '<%= gem_for_database %>'<% if require_for_database %>, :require => '<%= require_for_database %>'<% end %> -<% end -%> +<%= database_gemfile_entry -%> # Use unicorn as the web server # gem 'unicorn' diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index 00a23a7b89..6e515756fe 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -48,6 +48,10 @@ module <%= app_const_base %> # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) <% end -%> +<% if options[:skip_test_unit] -%> + config.generators.test_framework = false +<% end -%> + # Configure the default encoding used in templates for Ruby 1.9. config.encoding = "utf-8" diff --git a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js index ad9a79c433..a4f114586c 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js +++ b/railties/lib/rails/generators/rails/app/templates/public/javascripts/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.4.3 + * jQuery JavaScript Library v1.4.4 * http://jquery.com/ * * Copyright 2010, John Resig @@ -11,7 +11,7 @@ * Copyright 2010, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Thu Oct 14 23:10:06 2010 -0400 + * Date: Thu Nov 11 19:04:53 2010 -0500 */ (function( window, undefined ) { @@ -211,7 +211,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.4.3", + jquery: "1.4.4", // The default length of a jQuery object is 0 length: 0, @@ -330,8 +330,11 @@ jQuery.fn = jQuery.prototype = { jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { - // copy reference to target object - var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy, copyIsArray; + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { @@ -433,18 +436,21 @@ jQuery.extend({ // If there are functions bound, to execute if ( readyList ) { // Execute all of them - var fn, i = 0; - while ( (fn = readyList[ i++ ]) ) { - fn.call( document, jQuery ); - } + var fn, + i = 0, + ready = readyList; // Reset the list of functions readyList = null; - } - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); + while ( (fn = ready[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger( "ready" ).unbind( "ready" ); + } } } }, @@ -697,7 +703,8 @@ jQuery.extend({ }, merge: function( first, second ) { - var i = first.length, j = 0; + var i = first.length, + j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { @@ -964,6 +971,7 @@ return (window.jQuery = window.$ = jQuery); optSelected: opt.selected, // Will be defined later + deleteExpando: true, optDisabled: false, checkClone: false, scriptEval: false, @@ -994,6 +1002,15 @@ return (window.jQuery = window.$ = jQuery); delete window[ id ]; } + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + root.removeChild( script ); if ( div.attachEvent && div.fireEvent ) { @@ -1087,20 +1104,6 @@ return (window.jQuery = window.$ = jQuery); root = script = div = all = a = null; })(); -jQuery.props = { - "for": "htmlFor", - "class": "className", - readonly: "readOnly", - maxlength: "maxLength", - cellspacing: "cellSpacing", - rowspan: "rowSpan", - colspan: "colSpan", - tabindex: "tabIndex", - usemap: "useMap", - frameborder: "frameBorder" -}; - - var windowData = {}, @@ -1237,8 +1240,24 @@ jQuery.extend({ jQuery.fn.extend({ data: function( key, value ) { + var data = null; + if ( typeof key === "undefined" ) { - return this.length ? jQuery.data( this[0] ) : null; + if ( this.length ) { + var attr = this[0].attributes, name; + data = jQuery.data( this[0] ); + + for ( var i = 0, l = attr.length; i < l; i++ ) { + name = attr[i].name; + + if ( name.indexOf( "data-" ) === 0 ) { + name = name.substr( 5 ); + dataAttr( this[0], name, data[ name ] ); + } + } + } + + return data; } else if ( typeof key === "object" ) { return this.each(function() { @@ -1250,31 +1269,12 @@ jQuery.fn.extend({ parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { - var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { data = jQuery.data( this[0], key ); - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && this[0].nodeType === 1 ) { - data = this[0].getAttribute( "data-" + key ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - } else { - data = undefined; - } - } + data = dataAttr( this[0], key, data ); } return data === undefined && parts[1] ? @@ -1283,7 +1283,8 @@ jQuery.fn.extend({ } else { return this.each(function() { - var $this = jQuery( this ), args = [ parts[0], value ]; + var $this = jQuery( this ), + args = [ parts[0], value ]; $this.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); @@ -1299,6 +1300,33 @@ jQuery.fn.extend({ } }); +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + data = elem.getAttribute( "data-" + key ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + !jQuery.isNaN( data ) ? parseFloat( data ) : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + @@ -1329,7 +1357,8 @@ jQuery.extend({ dequeue: function( elem, type ) { type = type || "fx"; - var queue = jQuery.queue( elem, type ), fn = queue.shift(); + var queue = jQuery.queue( elem, type ), + fn = queue.shift(); // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { @@ -1405,6 +1434,19 @@ var rclass = /[\n\t]/g, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; + jQuery.fn.extend({ attr: function( name, value ) { return jQuery.access( this, name, value, true, jQuery.attr ); @@ -1438,7 +1480,9 @@ jQuery.fn.extend({ elem.className = value; } else { - var className = " " + elem.className + " ", setClass = elem.className; + var className = " " + elem.className + " ", + setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { setClass += " " + classNames[c]; @@ -1486,7 +1530,8 @@ jQuery.fn.extend({ }, toggleClass: function( value, stateVal ) { - var type = typeof value, isBool = typeof stateVal === "boolean"; + var type = typeof value, + isBool = typeof stateVal === "boolean"; if ( jQuery.isFunction( value ) ) { return this.each(function(i) { @@ -1498,7 +1543,9 @@ jQuery.fn.extend({ return this.each(function() { if ( type === "string" ) { // toggle individual class names - var className, i = 0, self = jQuery(this), + var className, + i = 0, + self = jQuery( this ), state = stateVal, classNames = value.split( rspaces ); @@ -1667,91 +1714,88 @@ jQuery.extend({ // Try to normalize/fix the name name = notxml && jQuery.props[ name ] || name; - // Only do all the following if this is a node (faster for style) - if ( elem.nodeType === 1 ) { - // These attributes require special treatment - var special = rspecialurl.test( name ); - - // Safari mis-reports the default selected property of an option - // Accessing the parent's selectedIndex property fixes it - if ( name === "selected" && !jQuery.support.optSelected ) { - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } + // These attributes require special treatment + var special = rspecialurl.test( name ); - // If applicable, access the attribute via the DOM 0 way - // 'in' checks fail in Blackberry 4.7 #6931 - if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { - if ( set ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; - if ( value === null ) { - if ( elem.nodeType === 1 ) { - elem.removeAttribute( name ); - } - - } else { - elem[ name ] = value; - } + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; } + } + } - // browsers index elements by id/name on forms, give priority to attributes. - if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { - return elem.getAttributeNode( name ).nodeValue; + // If applicable, access the attribute via the DOM 0 way + // 'in' checks fail in Blackberry 4.7 #6931 + if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); } - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - if ( name === "tabIndex" ) { - var attributeNode = elem.getAttributeNode( "tabIndex" ); + if ( value === null ) { + if ( elem.nodeType === 1 ) { + elem.removeAttribute( name ); + } - return attributeNode && attributeNode.specified ? - attributeNode.value : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; + } else { + elem[ name ] = value; } + } - return elem[ name ]; + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; } - if ( !jQuery.support.style && notxml && name === "style" ) { - if ( set ) { - elem.style.cssText = "" + value; - } + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); - return elem.style.cssText; + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; } + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { if ( set ) { - // convert the value to a string (all browsers do this but IE) see #1070 - elem.setAttribute( name, "" + value ); + elem.style.cssText = "" + value; } - // Ensure that missing attributes return undefined - // Blackberry 4.7 returns "" from getAttribute #6938 - if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { - return undefined; - } + return elem.style.cssText; + } - var attr = !jQuery.support.hrefNormalized && notxml && special ? - // Some attributes require a special call on IE - elem.getAttribute( name, 2 ) : - elem.getAttribute( name ); + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } - // Non-existent attributes return null, we normalize to undefined - return attr === null ? undefined : attr; + // Ensure that missing attributes return undefined + // Blackberry 4.7 returns "" from getAttribute #6938 + if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { + return undefined; } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; } }); @@ -1790,6 +1834,9 @@ jQuery.event = { if ( handler === false ) { handler = returnFalse; + } else if ( !handler ) { + // Fixes bug #7229. Fix recommended by jdalton + return; } var handleObjIn, handleObj; @@ -2133,8 +2180,10 @@ jQuery.event = { jQuery.event.trigger( event, data, parent, true ); } else if ( !event.isDefaultPrevented() ) { - var target = event.target, old, targetType = type.replace(rnamespaces, ""), - isClick = jQuery.nodeName(target, "a") && targetType === "click", + var old, + target = event.target, + targetType = type.replace( rnamespaces, "" ), + isClick = jQuery.nodeName( target, "a" ) && targetType === "click", special = jQuery.event.special[ targetType ] || {}; if ( (!special._default || special._default.call( elem, event ) === false) && @@ -2166,7 +2215,9 @@ jQuery.event = { }, handle: function( event ) { - var all, handlers, namespaces, namespace_sort = [], namespace_re, events, args = jQuery.makeArray( arguments ); + var all, handlers, namespaces, namespace_re, events, + namespace_sort = [], + args = jQuery.makeArray( arguments ); event = args[0] = jQuery.event.fix( event || window.event ); event.currentTarget = this; @@ -2245,7 +2296,8 @@ jQuery.event = { // Fix target property, if necessary if ( !event.target ) { - event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + // Fixes #1925 where srcElement might not be defined either + event.target = event.srcElement || document; } // check if target is a textnode (safari) @@ -2260,7 +2312,9 @@ jQuery.event = { // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { - var doc = document.documentElement, body = document.body; + var doc = document.documentElement, + body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); } @@ -2466,7 +2520,8 @@ if ( !jQuery.support.submitBubbles ) { setup: function( data, namespaces ) { if ( this.nodeName.toLowerCase() !== "form" ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, + type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { e.liveFired = undefined; @@ -2475,7 +2530,8 @@ if ( !jQuery.support.submitBubbles ) { }); jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, type = elem.type; + var elem = e.target, + type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { e.liveFired = undefined; @@ -2716,7 +2772,8 @@ jQuery.fn.extend({ toggle: function( fn ) { // Save reference to arguments for access in closure - var args = arguments, i = 1; + var args = arguments, + i = 1; // link all the functions, so any of them can unbind this click handler while ( i < args.length ) { @@ -2811,8 +2868,9 @@ jQuery.each(["live", "die"], function( i, name ) { }); function liveHandler( event ) { - var stop, maxLevel, elems = [], selectors = [], - related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, + elems = [], + selectors = [], events = jQuery.data( this, this.nodeType ? "events" : "__events__" ); if ( typeof events === "function" ) { @@ -2823,7 +2881,7 @@ function liveHandler( event ) { if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { return; } - + if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } @@ -2887,6 +2945,9 @@ function liveHandler( event ) { if ( ret === false ) { stop = false; } + if ( event.isImmediatePropagationStopped() ) { + break; + } } } @@ -2954,12 +3015,12 @@ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[ // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. -[0, 0].sort(function(){ +[0, 0].sort(function() { baseHasDuplicate = false; return 0; }); -var Sizzle = function(selector, context, results, seed) { +var Sizzle = function( selector, context, results, seed ) { results = results || []; context = context || document; @@ -2973,13 +3034,16 @@ var Sizzle = function(selector, context, results, seed) { return results; } - var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), - soFar = selector, ret, cur, pop, i; + var m, set, checkSet, extra, ret, cur, pop, i, + prune = true, + contextXML = Sizzle.isXML( context ), + parts = [], + soFar = selector; // Reset the position of the chunker regexp (start from head) do { - chunker.exec(""); - m = chunker.exec(soFar); + chunker.exec( "" ); + m = chunker.exec( soFar ); if ( m ) { soFar = m[3]; @@ -2994,8 +3058,10 @@ var Sizzle = function(selector, context, results, seed) { } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); + } else { set = Expr.relative[ parts[0] ] ? [ context ] : @@ -3011,23 +3077,31 @@ var Sizzle = function(selector, context, results, seed) { set = posProcess( selector, set ); } } + } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + context = ret.expr ? + Sizzle.filter( ret.expr, ret.set )[0] : + ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + set = ret.expr ? + Sizzle.filter( ret.expr, ret.set ) : + ret.set; if ( parts.length > 0 ) { - checkSet = makeArray(set); + checkSet = makeArray( set ); + } else { prune = false; } @@ -3048,6 +3122,7 @@ var Sizzle = function(selector, context, results, seed) { Expr.relative[ cur ]( checkSet, pop, contextXML ); } + } else { checkSet = parts = []; } @@ -3064,12 +3139,14 @@ var Sizzle = function(selector, context, results, seed) { if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } + } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { @@ -3077,6 +3154,7 @@ var Sizzle = function(selector, context, results, seed) { } } } + } else { makeArray( checkSet, results ); } @@ -3089,15 +3167,15 @@ var Sizzle = function(selector, context, results, seed) { return results; }; -Sizzle.uniqueSort = function(results){ +Sizzle.uniqueSort = function( results ) { if ( sortOrder ) { hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); + results.sort( sortOrder ); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); + if ( results[i] === results[ i - 1 ] ) { + results.splice( i--, 1 ); } } } @@ -3106,15 +3184,15 @@ Sizzle.uniqueSort = function(results){ return results; }; -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); +Sizzle.matches = function( expr, set ) { + return Sizzle( expr, null, null, set ); }; -Sizzle.matchesSelector = function(node, expr){ - return Sizzle(expr, null, null, [node]).length > 0; +Sizzle.matchesSelector = function( node, expr ) { + return Sizzle( expr, null, null, [node] ).length > 0; }; -Sizzle.find = function(expr, context, isXML){ +Sizzle.find = function( expr, context, isXML ) { var set; if ( !expr ) { @@ -3122,15 +3200,17 @@ Sizzle.find = function(expr, context, isXML){ } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; + var match, + type = Expr.order[i]; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; - match.splice(1,1); + match.splice( 1, 1 ); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; @@ -3140,20 +3220,26 @@ Sizzle.find = function(expr, context, isXML){ } if ( !set ) { - set = context.getElementsByTagName("*"); + set = context.getElementsByTagName( "*" ); } - return {set: set, expr: expr}; + return { set: set, expr: expr }; }; -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); +Sizzle.filter = function( expr, set, inplace, not ) { + var match, anyFound, + old = expr, + result = [], + curLoop = set, + isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = Expr.filter[ type ], found, item, left = match[1]; + var found, item, + filter = Expr.filter[ type ], + left = match[1]; + anyFound = false; match.splice(1,1); @@ -3171,6 +3257,7 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( !match ) { anyFound = found = true; + } else if ( match === true ) { continue; } @@ -3185,9 +3272,11 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( inplace && found != null ) { if ( pass ) { anyFound = true; + } else { curLoop[i] = false; } + } else if ( pass ) { result.push( item ); anyFound = true; @@ -3216,6 +3305,7 @@ Sizzle.filter = function(expr, set, inplace, not){ if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); + } else { break; } @@ -3233,6 +3323,7 @@ Sizzle.error = function( msg ) { var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], + match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, @@ -3243,20 +3334,24 @@ var Expr = Sizzle.selectors = { POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, + leftMatch: {}, + attrMap: { "class": "className", "for": "htmlFor" }, + attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); + href: function( elem ) { + return elem.getAttribute( "href" ); } }, + relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), + isTag = isPartStr && !/\W/.test( part ), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { @@ -3277,23 +3372,29 @@ var Expr = Sizzle.selectors = { Sizzle.filter( part, checkSet, true ); } }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string", - elem, i = 0, l = checkSet.length; - if ( isPartStr && !/\W/.test(part) ) { + ">": function( checkSet, part ) { + var elem, + isPartStr = typeof part === "string", + i = 0, + l = checkSet.length; + + if ( isPartStr && !/\W/.test( part ) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; + if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } + } else { for ( ; i < l; i++ ) { elem = checkSet[i]; + if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : @@ -3306,8 +3407,11 @@ var Expr = Sizzle.selectors = { } } }, + "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; + var nodeCheck, + doneName = done++, + checkFn = dirCheck; if ( typeof part === "string" && !/\W/.test(part) ) { part = part.toLowerCase(); @@ -3315,22 +3419,26 @@ var Expr = Sizzle.selectors = { checkFn = dirNodeCheck; } - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { + "~": function( checkSet, part, isXML ) { + var nodeCheck, + doneName = done++, + checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test( part ) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, + find: { - ID: function(match, context, isXML){ + ID: function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); // Check parentNode to catch when Blackberry 4.6 returns @@ -3338,9 +3446,11 @@ var Expr = Sizzle.selectors = { return m && m.parentNode ? [m] : []; } }, - NAME: function(match, context){ + + NAME: function( match, context ) { if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); + var ret = [], + results = context.getElementsByName( match[1] ); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { @@ -3351,12 +3461,13 @@ var Expr = Sizzle.selectors = { return ret.length === 0 ? null : ret; } }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); + + TAG: function( match, context ) { + return context.getElementsByTagName( match[1] ); } }, preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ + CLASS: function( match, curLoop, inplace, result, not, isXML ) { match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { @@ -3369,6 +3480,7 @@ var Expr = Sizzle.selectors = { if ( !inplace ) { result.push( elem ); } + } else if ( inplace ) { curLoop[i] = false; } @@ -3377,13 +3489,16 @@ var Expr = Sizzle.selectors = { return false; }, - ID: function(match){ + + ID: function( match ) { return match[1].replace(/\\/g, ""); }, - TAG: function(match, curLoop){ + + TAG: function( match, curLoop ) { return match[1].toLowerCase(); }, - CHILD: function(match){ + + CHILD: function( match ) { if ( match[1] === "nth" ) { // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( @@ -3400,7 +3515,8 @@ var Expr = Sizzle.selectors = { return match; }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ + + ATTR: function( match, curLoop, inplace, result, not, isXML ) { var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { @@ -3413,122 +3529,156 @@ var Expr = Sizzle.selectors = { return match; }, - PSEUDO: function(match, curLoop, inplace, result, not){ + + PSEUDO: function( match, curLoop, inplace, result, not ) { if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); + } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { result.push.apply( result, ret ); } + return false; } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, - POS: function(match){ + + POS: function( match ) { match.unshift( true ); + return match; } }, + filters: { - enabled: function(elem){ + enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; }, - disabled: function(elem){ + + disabled: function( elem ) { return elem.disabled === true; }, - checked: function(elem){ + + checked: function( elem ) { return elem.checked === true; }, - selected: function(elem){ + + selected: function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly elem.parentNode.selectedIndex; + return elem.selected === true; }, - parent: function(elem){ + + parent: function( elem ) { return !!elem.firstChild; }, - empty: function(elem){ + + empty: function( elem ) { return !elem.firstChild; }, - has: function(elem, i, match){ + + has: function( elem, i, match ) { return !!Sizzle( match[3], elem ).length; }, - header: function(elem){ + + header: function( elem ) { return (/h\d/i).test( elem.nodeName ); }, - text: function(elem){ + + text: function( elem ) { return "text" === elem.type; }, - radio: function(elem){ + radio: function( elem ) { return "radio" === elem.type; }, - checkbox: function(elem){ + + checkbox: function( elem ) { return "checkbox" === elem.type; }, - file: function(elem){ + + file: function( elem ) { return "file" === elem.type; }, - password: function(elem){ + password: function( elem ) { return "password" === elem.type; }, - submit: function(elem){ + + submit: function( elem ) { return "submit" === elem.type; }, - image: function(elem){ + + image: function( elem ) { return "image" === elem.type; }, - reset: function(elem){ + + reset: function( elem ) { return "reset" === elem.type; }, - button: function(elem){ + + button: function( elem ) { return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; }, - input: function(elem){ - return (/input|select|textarea|button/i).test(elem.nodeName); + + input: function( elem ) { + return (/input|select|textarea|button/i).test( elem.nodeName ); } }, setFilters: { - first: function(elem, i){ + first: function( elem, i ) { return i === 0; }, - last: function(elem, i, match, array){ + + last: function( elem, i, match, array ) { return i === array.length - 1; }, - even: function(elem, i){ + + even: function( elem, i ) { return i % 2 === 0; }, - odd: function(elem, i){ + + odd: function( elem, i ) { return i % 2 === 1; }, - lt: function(elem, i, match){ + + lt: function( elem, i, match ) { return i < match[3] - 0; }, - gt: function(elem, i, match){ + + gt: function( elem, i, match ) { return i > match[3] - 0; }, - nth: function(elem, i, match){ + + nth: function( elem, i, match ) { return match[3] - 0 === i; }, - eq: function(elem, i, match){ + + eq: function( elem, i, match ) { return match[3] - 0 === i; } }, filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; + PSEUDO: function( elem, match, i, array ) { + var name = match[1], + filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); + } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { var not = match[3]; @@ -3539,33 +3689,43 @@ var Expr = Sizzle.selectors = { } return true; + } else { Sizzle.error( "Syntax error, unrecognized expression: " + name ); } }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': + + CHILD: function( elem, match ) { + var type = match[1], + node = elem; + + switch ( type ) { + case "only": + case "first": while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } + if ( type === "first" ) { return true; } + node = elem; - case 'last': + + case "last": while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } + return true; - case 'nth': - var first = match[2], last = match[3]; + + case "nth": + var first = match[2], + last = match[3]; if ( first === 1 && last === 0 ) { return true; @@ -3576,33 +3736,41 @@ var Expr = Sizzle.selectors = { if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } + parent.sizcache = doneName; } var diff = elem.nodeIndex - last; + if ( first === 0 ) { return diff === 0; + } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, - ID: function(elem, match){ + + ID: function( elem, match ) { return elem.nodeType === 1 && elem.getAttribute("id") === match; }, - TAG: function(elem, match){ + + TAG: function( elem, match ) { return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, - CLASS: function(elem, match){ + + CLASS: function( elem, match ) { return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, - ATTR: function(elem, match){ + + ATTR: function( elem, match ) { var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : @@ -3633,8 +3801,10 @@ var Expr = Sizzle.selectors = { value === check || value.substr(0, check.length + 1) === check + "-" : false; }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; + + POS: function( elem, match, i, array ) { + var name = match[2], + filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); @@ -3653,7 +3823,7 @@ for ( var type in Expr.match ) { Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } -var makeArray = function(array, results) { +var makeArray = function( array, results ) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { @@ -3672,17 +3842,20 @@ try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || [], i = 0; +} catch( e ) { + makeArray = function( array, results ) { + var i = 0, + ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); + } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } + } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); @@ -3709,10 +3882,15 @@ if ( document.documentElement.compareDocumentPosition ) { return a.compareDocumentPosition(b) & 4 ? -1 : 1; }; + } else { sortOrder = function( a, b ) { - var ap = [], bp = [], aup = a.parentNode, bup = b.parentNode, - cur = aup, al, bl; + var al, bl, + ap = [], + bp = [], + aup = a.parentNode, + bup = b.parentNode, + cur = aup; // The nodes are identical, we can exit early if ( a === b ) { @@ -3805,31 +3983,40 @@ Sizzle.getText = function( elems ) { (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), - id = "script" + (new Date()).getTime(); + id = "script" + (new Date()).getTime(), + root = document.documentElement; + form.innerHTML = "<a name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ + Expr.find.ID = function( match, context, isXML ) { if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + + return m ? + m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? + [m] : + undefined : + []; } }; - Expr.filter.ID = function(elem, match){ + Expr.filter.ID = function( elem, match ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); - root = form = null; // release memory in IE + + // release memory in IE + root = form = null; })(); (function(){ @@ -3842,8 +4029,8 @@ Sizzle.getText = function( elems ) { // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); + Expr.find.TAG = function( match, context ) { + var results = context.getElementsByTagName( match[1] ); // Filter out possible comments if ( match[1] === "*" ) { @@ -3864,19 +4051,25 @@ Sizzle.getText = function( elems ) { // Check to see if an attribute returns normalized href attributes div.innerHTML = "<a href='#'></a>"; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); + + Expr.attrHandle.href = function( elem ) { + return elem.getAttribute( "href", 2 ); }; } - div = null; // release memory in IE + // release memory in IE + div = null; })(); if ( document.querySelectorAll ) { (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); + var oldSizzle = Sizzle, + div = document.createElement("div"), + id = "__sizzle__"; + div.innerHTML = "<p class='TEST'></p>"; // Safari can't handle uppercase or unicode characters when @@ -3885,9 +4078,12 @@ if ( document.querySelectorAll ) { return; } - Sizzle = function(query, context, extra, seed){ + Sizzle = function( query, context, extra, seed ) { context = context || document; + // Make sure that attribute selectors are quoted + query = query.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && !Sizzle.isXML(context) ) { @@ -3901,17 +4097,19 @@ if ( document.querySelectorAll ) { // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var old = context.id, id = context.id = "__sizzle__"; + var old = context.getAttribute( "id" ), + nid = old || id; + + if ( !old ) { + context.setAttribute( "id", nid ); + } try { - return makeArray( context.querySelectorAll( "#" + id + " " + query ), extra ); + return makeArray( context.querySelectorAll( "#" + nid + " " + query ), extra ); } catch(pseudoError) { } finally { - if ( old ) { - context.id = old; - - } else { + if ( !old ) { context.removeAttribute( "id" ); } } @@ -3925,7 +4123,8 @@ if ( document.querySelectorAll ) { Sizzle[ prop ] = oldSizzle[ prop ]; } - div = null; // release memory in IE + // release memory in IE + div = null; })(); } @@ -3937,7 +4136,7 @@ if ( document.querySelectorAll ) { try { // This should fail with an exception // Gecko does not error, returns false instead - matches.call( document.documentElement, ":sizzle" ); + matches.call( document.documentElement, "[test!='']:sizzle" ); } catch( pseudoError ) { pseudoWorks = true; @@ -3945,13 +4144,18 @@ if ( document.querySelectorAll ) { if ( matches ) { Sizzle.matchesSelector = function( node, expr ) { + // Make sure that attribute selectors are quoted + expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + + if ( !Sizzle.isXML( node ) ) { try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) ) { + if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { return matches.call( node, expr ); } } catch(e) {} + } - return Sizzle(expr, null, null, [node]).length > 0; + return Sizzle(expr, null, null, [node]).length > 0; }; } })(); @@ -3975,22 +4179,25 @@ if ( document.querySelectorAll ) { } Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { + Expr.find.CLASS = function( match, context, isXML ) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; - div = null; // release memory in IE + // release memory in IE + div = null; })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - elem = elem[dir]; var match = false; + elem = elem[dir]; + while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; @@ -4018,9 +4225,11 @@ function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; + if ( elem ) { - elem = elem[dir]; var match = false; + + elem = elem[dir]; while ( elem ) { if ( elem.sizcache === doneName ) { @@ -4033,6 +4242,7 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { elem.sizcache = doneName; elem.sizset = i; } + if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; @@ -4053,21 +4263,34 @@ function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { } } -Sizzle.contains = document.documentElement.contains ? function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -} : function(a, b){ - return !!(a.compareDocumentPosition(b) & 16); -}; +if ( document.documentElement.contains ) { + Sizzle.contains = function( a, b ) { + return a !== b && (a.contains ? a.contains(b) : true); + }; -Sizzle.isXML = function(elem){ +} else if ( document.documentElement.compareDocumentPosition ) { + Sizzle.contains = function( a, b ) { + return !!(a.compareDocumentPosition(b) & 16); + }; + +} else { + Sizzle.contains = function() { + return false; + }; +} + +Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; }; -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, +var posProcess = function( selector, context ) { + var match, + tmpSet = [], + later = "", root = context.nodeType ? [context] : context; // Position selectors must be done after the filter @@ -4109,7 +4332,8 @@ var runtil = /Until$/, jQuery.fn.extend({ find: function( selector ) { - var ret = this.pushStack( "", "find", selector ), length = 0; + var ret = this.pushStack( "", "find", selector ), + length = 0; for ( var i = 0, l = this.length; i < l; i++ ) { length = ret.length; @@ -4158,7 +4382,9 @@ jQuery.fn.extend({ var ret = [], i, l, cur = this[0]; if ( jQuery.isArray( selectors ) ) { - var match, matches = {}, selector, level = 1; + var match, selector, + matches = {}, + level = 1; if ( cur && selectors.length ) { for ( i = 0, l = selectors.length; i < l; i++ ) { @@ -4324,7 +4550,9 @@ jQuery.extend({ }, dir: function( elem, dir, until ) { - var matched = [], cur = elem[dir]; + var matched = [], + cur = elem[ dir ]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); @@ -4400,7 +4628,8 @@ var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnocache = /<(?:script|object|embed|option|style)/i, - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, // checked="checked" or checked (html5) + // checked="checked" or checked (html5) + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, raction = /\=([^="'>\s]+\/)>/g, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], @@ -4426,7 +4655,8 @@ jQuery.fn.extend({ text: function( text ) { if ( jQuery.isFunction(text) ) { return this.each(function(i) { - var self = jQuery(this); + var self = jQuery( this ); + self.text( text.call(this, i, self.text()) ); }); } @@ -4475,7 +4705,8 @@ jQuery.fn.extend({ } return this.each(function() { - var self = jQuery( this ), contents = self.contents(); + var self = jQuery( this ), + contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); @@ -4586,7 +4817,9 @@ jQuery.fn.extend({ // attributes in IE that are actually only stored // as properties will not be copied (such as the // the name attribute on an input). - var html = this.outerHTML, ownerDocument = this.ownerDocument; + var html = this.outerHTML, + ownerDocument = this.ownerDocument; + if ( !html ) { var div = ownerDocument.createElement("div"); div.appendChild( this.cloneNode(true) ); @@ -4641,7 +4874,8 @@ jQuery.fn.extend({ } else if ( jQuery.isFunction( value ) ) { this.each(function(i){ - var self = jQuery(this); + var self = jQuery( this ); + self.html( value.call(this, i, self.html()) ); }); @@ -4664,13 +4898,14 @@ jQuery.fn.extend({ } if ( typeof value !== "string" ) { - value = jQuery(value).detach(); + value = jQuery( value ).detach(); } return this.each(function() { - var next = this.nextSibling, parent = this.parentNode; + var next = this.nextSibling, + parent = this.parentNode; - jQuery(this).remove(); + jQuery( this ).remove(); if ( next ) { jQuery(next).before( value ); @@ -4688,7 +4923,9 @@ jQuery.fn.extend({ }, domManip: function( args, table, callback ) { - var results, first, value = args[0], scripts = [], fragment, parent; + var results, first, fragment, parent, + value = args[0], + scripts = []; // We can't cloneNode fragments that contain checked, in WebKit if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { @@ -4763,7 +5000,9 @@ function cloneCopyEvent(orig, ret) { return; } - var oldData = jQuery.data( orig[i++] ), curData = jQuery.data( this, oldData ), events = oldData && oldData.events; + var oldData = jQuery.data( orig[i++] ), + curData = jQuery.data( this, oldData ), + events = oldData && oldData.events; if ( events ) { delete curData.handle; @@ -4820,7 +5059,8 @@ jQuery.each({ replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { - var ret = [], insert = jQuery( selector ), + var ret = [], + insert = jQuery( selector ), parent = this.length === 1 && this[0].parentNode; if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { @@ -5004,8 +5244,8 @@ var ralpha = /alpha\([^)]*\)/i, cssHeight = [ "Top", "Bottom" ], curCSS, - // cache check for defaultView.getComputedStyle - getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, + getComputedStyle, + currentStyle, fcamelCase = function( all, letter ) { return letter.toUpperCase(); @@ -5161,7 +5401,29 @@ jQuery.each(["height", "width"], function( i, name ) { }); } - return val + "px"; + if ( val <= 0 ) { + val = curCSS( elem, name, name ); + + if ( val === "0px" && currentStyle ) { + val = currentStyle( elem, name, name ); + } + + if ( val != null ) { + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + } + + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + + // Should return "auto" instead of 0, use 0 for + // temporary backwards-compat + return val === "" || val === "auto" ? "0px" : val; + } + + return typeof val === "string" ? val : val + "px"; } }, @@ -5210,8 +5472,8 @@ if ( !jQuery.support.opacity ) { }; } -if ( getComputedStyle ) { - curCSS = function( elem, newName, name ) { +if ( document.defaultView && document.defaultView.getComputedStyle ) { + getComputedStyle = function( elem, newName, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); @@ -5229,10 +5491,13 @@ if ( getComputedStyle ) { return ret; }; +} -} else if ( document.documentElement.currentStyle ) { - curCSS = function( elem, name ) { - var left, rsLeft, ret = elem.currentStyle && elem.currentStyle[ name ], style = elem.style; +if ( document.documentElement.currentStyle ) { + currentStyle = function( elem, name ) { + var left, rsLeft, + ret = elem.currentStyle && elem.currentStyle[ name ], + style = elem.style; // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 @@ -5254,10 +5519,12 @@ if ( getComputedStyle ) { elem.runtimeStyle.left = rsLeft; } - return ret; + return ret === "" ? "auto" : ret; }; } +curCSS = getComputedStyle || currentStyle; + function getWH( elem, name, extra ) { var which = name === "width" ? cssWidth : cssHeight, val = name === "width" ? elem.offsetWidth : elem.offsetHeight; @@ -5284,7 +5551,8 @@ function getWH( elem, name, extra ) { if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { - var width = elem.offsetWidth, height = elem.offsetHeight; + var width = elem.offsetWidth, + height = elem.offsetHeight; return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); }; @@ -5301,7 +5569,7 @@ var jsc = jQuery.now(), rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, - rnoContent = /^(?:GET|HEAD|DELETE)$/, + rnoContent = /^(?:GET|HEAD)$/, rbracket = /\[\]$/, jsre = /\=\?(&|$)/, rquery = /\?/, @@ -5536,10 +5804,6 @@ jQuery.extend({ var customJsonp = window[ jsonp ]; window[ jsonp ] = function( tmp ) { - data = tmp; - jQuery.handleSuccess( s, xhr, status, data ); - jQuery.handleComplete( s, xhr, status, data ); - if ( jQuery.isFunction( customJsonp ) ) { customJsonp( tmp ); @@ -5551,6 +5815,10 @@ jQuery.extend({ delete window[ jsonp ]; } catch( jsonpError ) {} } + + data = tmp; + jQuery.handleSuccess( s, xhr, status, data ); + jQuery.handleComplete( s, xhr, status, data ); if ( head ) { head.removeChild( script ); @@ -5562,7 +5830,7 @@ jQuery.extend({ s.cache = false; } - if ( s.cache === false && type === "GET" ) { + if ( s.cache === false && noContent ) { var ts = jQuery.now(); // try replacing _= if it is there @@ -5572,8 +5840,8 @@ jQuery.extend({ s.url = ret + ((ret === s.url) ? (rquery.test(s.url) ? "&" : "?") + "_=" + ts : ""); } - // If data is available, append data to url for get requests - if ( s.data && type === "GET" ) { + // If data is available, append data to url for GET/HEAD requests + if ( s.data && noContent ) { s.url += (rquery.test(s.url) ? "&" : "?") + s.data; } @@ -5584,7 +5852,7 @@ jQuery.extend({ // Matches an absolute URL, and saves the domain var parts = rurl.exec( s.url ), - remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host); + remote = parts && (parts[1] && parts[1].toLowerCase() !== location.protocol || parts[2].toLowerCase() !== location.host); // If we're requesting a remote document // and trying to load JSON or Script with a GET @@ -5760,10 +6028,11 @@ jQuery.extend({ try { var oldAbort = xhr.abort; xhr.abort = function() { - // xhr.abort in IE7 is not a native JS function - // and does not have a call property - if ( xhr && oldAbort.call ) { - oldAbort.call( xhr ); + if ( xhr ) { + // oldAbort has no call property in IE7 so + // just do it this way, which works in all + // browsers + Function.prototype.call.call( oldAbort, xhr ); } onreadystatechange( "abort" ); @@ -5803,11 +6072,12 @@ jQuery.extend({ // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { - var s = [], add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction(value) ? value() : value; - s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); - }; + var s = [], + add = function( key, value ) { + // If value is a function, invoke it and return its value + value = jQuery.isFunction(value) ? value() : value; + s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); + }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { @@ -6029,28 +6299,39 @@ var elemdisplay = {}, jQuery.fn.extend({ show: function( speed, easing, callback ) { + var elem, display; + if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback); + } else { for ( var i = 0, j = this.length; i < j; i++ ) { + elem = this[i]; + display = elem.style.display; + // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not - if ( !jQuery.data(this[i], "olddisplay") && this[i].style.display === "none" ) { - this[i].style.display = ""; + if ( !jQuery.data(elem, "olddisplay") && display === "none" ) { + display = elem.style.display = ""; } // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element - if ( this[i].style.display === "" && jQuery.css( this[i], "display" ) === "none" ) { - jQuery.data(this[i], "olddisplay", defaultDisplay(this[i].nodeName)); + if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { + jQuery.data(elem, "olddisplay", defaultDisplay(elem.nodeName)); } } // Set the display of most of the elements in a second loop // to avoid the constant reflow for ( i = 0; i < j; i++ ) { - this[i].style.display = jQuery.data(this[i], "olddisplay") || ""; + elem = this[i]; + display = elem.style.display; + + if ( display === "" || display === "none" ) { + elem.style.display = jQuery.data(elem, "olddisplay") || ""; + } } return this; @@ -6115,7 +6396,7 @@ jQuery.fn.extend({ } return this[ optall.queue === false ? "each" : "queue" ](function() { - // XXX ‘this’ does not always have a nodeName when running the + // XXX 'this' does not always have a nodeName when running the // test suite var opt = jQuery.extend({}, optall), p, @@ -6188,7 +6469,7 @@ jQuery.fn.extend({ } else { var parts = rfxnum.exec(val), - start = e.cur(true) || 0; + start = e.cur() || 0; if ( parts ) { var end = parseFloat( parts[2] ), @@ -6197,7 +6478,7 @@ jQuery.fn.extend({ // We need to compute starting value if ( unit !== "px" ) { jQuery.style( self, name, (end || 1) + unit); - start = ((end || 1) / e.cur(true)) * start; + start = ((end || 1) / e.cur()) * start; jQuery.style( self, name, start + unit); } @@ -6266,7 +6547,8 @@ jQuery.each({ slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" } + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); @@ -6344,6 +6626,9 @@ jQuery.fx.prototype = { // Start an animation from one number to another custom: function( from, to, unit ) { + var self = this, + fx = jQuery.fx; + this.startTime = jQuery.now(); this.start = from; this.end = to; @@ -6351,7 +6636,6 @@ jQuery.fx.prototype = { this.now = this.start; this.pos = this.state = 0; - var self = this, fx = jQuery.fx; function t( gotoEnd ) { return self.step(gotoEnd); } @@ -6408,7 +6692,9 @@ jQuery.fx.prototype = { if ( done ) { // Reset the overflow if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - var elem = this.elem, options = this.options; + var elem = this.elem, + options = this.options; + jQuery.each( [ "", "X", "Y" ], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; } ); @@ -6587,11 +6873,16 @@ if ( "getBoundingClientRect" in document.documentElement ) { jQuery.offset.initialize(); - var offsetParent = elem.offsetParent, prevOffsetParent = elem, - doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, - body = doc.body, defaultView = doc.defaultView, + var computedStyle, + offsetParent = elem.offsetParent, + prevOffsetParent = elem, + doc = elem.ownerDocument, + docElem = doc.documentElement, + body = doc.body, + defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, - top = elem.offsetTop, left = elem.offsetLeft; + top = elem.offsetTop, + left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { @@ -6673,7 +6964,8 @@ jQuery.offset = { }, bodyOffset: function( body ) { - var top = body.offsetTop, left = body.offsetLeft; + var top = body.offsetTop, + left = body.offsetLeft; jQuery.offset.initialize(); @@ -6854,27 +7146,31 @@ jQuery.each([ "Height", "Width" ], function( i, name ) { }); } - return jQuery.isWindow( elem ) ? + if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || - elem.document.body[ "client" + name ] : - - // Get document width or height - (elem.nodeType === 9) ? // is it a document - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - Math.max( - elem.documentElement["client" + name], - elem.body["scroll" + name], elem.documentElement["scroll" + name], - elem.body["offset" + name], elem.documentElement["offset" + name] - ) : - - // Get or set width or height on the element - size === undefined ? - // Get width or height on the element - parseFloat( jQuery.css( elem, type ) ) : - - // Set the width or height on the element (default to pixels if value is unitless) - this.css( type, typeof size === "string" ? size : size + "px" ); + return elem.document.compatMode === "CSS1Compat" && elem.document.documentElement[ "client" + name ] || + elem.document.body[ "client" + name ]; + + // Get document width or height + } else if ( elem.nodeType === 9 ) { + // Either scroll[Width/Height] or offset[Width/Height], whichever is greater + return Math.max( + elem.documentElement["client" + name], + elem.body["scroll" + name], elem.documentElement["scroll" + name], + elem.body["offset" + name], elem.documentElement["offset" + name] + ); + + // Get or set width or height on the element + } else if ( size === undefined ) { + var orig = jQuery.css( elem, type ), + ret = parseFloat( orig ); + + return jQuery.isNaN( ret ) ? orig : ret; + + // Set the width or height on the element (default to pixels if value is unitless) + } else { + return this.css( type, typeof size === "string" ? size : size + "px" ); + } }; }); diff --git a/railties/lib/rails/generators/rails/plugin_new/USAGE b/railties/lib/rails/generators/rails/plugin_new/USAGE new file mode 100644 index 0000000000..9a7bf9f396 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/USAGE @@ -0,0 +1,10 @@ +Description: + The 'rails plugin new' command creates a skeleton for developing any + kind of Rails extension with ability to run tests using dummy Rails + application. + +Example: + rails plugin new ~/Code/Ruby/blog + + This generates a skeletal Rails plugin in ~/Code/Ruby/blog. + See the README in the newly created plugin to get going. diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb new file mode 100644 index 0000000000..8fac6fc70a --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -0,0 +1,246 @@ +require 'active_support/core_ext/hash/slice' +require "rails/generators/rails/app/app_generator" + +module Rails + class PluginBuilder + def rakefile + template "Rakefile" + end + + def app + directory "app" if options[:mountable] + end + + def readme + copy_file "README.rdoc" + end + + def gemfile + template "Gemfile" + end + + def license + template "MIT-LICENSE" + end + + def gemspec + template "%name%.gemspec" + end + + def gitignore + copy_file "gitignore", ".gitignore" + end + + def lib + template "lib/%name%.rb" + template "lib/tasks/%name%_tasks.rake" + if full? + template "lib/%name%/engine.rb" + end + end + + def config + template "config/routes.rb" if mountable? + end + + def test + template "test/test_helper.rb" + template "test/%name%_test.rb" + append_file "Rakefile", <<-EOF +#{rakefile_test_tasks} + +task :default => :test + EOF + if full? + template "test/integration/navigation_test.rb" + end + end + + def generate_test_dummy(force = false) + opts = (options || {}).slice(:skip_active_record, :skip_javascript, :database, :javascript) + opts[:force] = force + + invoke Rails::Generators::AppGenerator, + [ File.expand_path(dummy_path, destination_root) ], opts + end + + def test_dummy_config + template "rails/boot.rb", "#{dummy_path}/config/boot.rb", :force => true + template "rails/application.rb", "#{dummy_path}/config/application.rb", :force => true + if mountable? + template "rails/routes.rb", "#{dummy_path}/config/routes.rb", :force => true + end + end + + def test_dummy_clean + inside dummy_path do + remove_file ".gitignore" + remove_file "db/seeds.rb" + remove_file "doc" + remove_file "Gemfile" + remove_file "lib/tasks" + remove_file "public/images/rails.png" + remove_file "public/index.html" + remove_file "public/robots.txt" + remove_file "README" + remove_file "test" + remove_file "vendor" + end + end + + def script(force = false) + directory "script", :force => force do |content| + "#{shebang}\n" + content + end + chmod "script", 0755, :verbose => false + end + end + + module Generators + class PluginNewGenerator < AppBase + add_shared_options_for "plugin" + + alias_method :plugin_path, :app_path + + class_option :dummy_path, :type => :string, :default => "test/dummy", + :desc => "Create dummy application at given path" + + class_option :full, :type => :boolean, :default => false, + :desc => "Generate rails engine with integration tests" + + class_option :mountable, :type => :boolean, :default => false, + :desc => "Generate mountable isolated application" + + def initialize(*args) + raise Error, "Options should be given after the plugin name. For details run: rails plugin --help" if args[0].blank? + + super + end + + public_task :create_root + + def create_root_files + build(:readme) + build(:rakefile) + build(:gemspec) + build(:license) + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] + end + + def create_app_files + build(:app) + end + + def create_config_files + build(:config) + end + + def create_lib_files + build(:lib) + end + + def create_script_files + build(:script) + end + + def create_test_files + build(:test) unless options[:skip_test_unit] + end + + def create_test_dummy_files + return if options[:skip_test_unit] + create_dummy_app + end + + def finish_template + build(:leftovers) + end + + public_task :apply_rails_template, :bundle_if_dev_or_edge + + protected + def create_dummy_app(path = nil) + dummy_path(path) if path + + say_status :vendor_app, dummy_path + mute do + build(:generate_test_dummy) + store_application_definition! + build(:test_dummy_config) + build(:test_dummy_clean) + # ensure that script/rails has proper dummy_path + build(:script, true) + end + end + + def full? + options[:full] || options[:mountable] + end + + def mountable? + options[:mountable] + end + + def self.banner + "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]" + end + + def name + @name ||= File.basename(destination_root) + end + + def camelized + @camelized ||= name.gsub(/\W/, '_').squeeze('_').camelize + end + + def valid_const? + if camelized =~ /^\d/ + raise Error, "Invalid plugin name #{name}. Please give a name which does not start with numbers." + elsif RESERVED_NAMES.include?(name) + raise Error, "Invalid plugin name #{name}. Please give a name which does not match one of the reserved rails words." + elsif Object.const_defined?(camelized) + raise Error, "Invalid plugin name #{name}, constant #{camelized} is already in use. Please choose another application name." + end + end + + def application_definition + @application_definition ||= begin + + dummy_application_path = File.expand_path("#{dummy_path}/config/application.rb", destination_root) + unless options[:pretend] || !File.exists?(dummy_application_path) + contents = File.read(dummy_application_path) + contents[(contents.index("module Dummy"))..-1] + end + end + end + alias :store_application_definition! :application_definition + + def get_builder_class + defined?(::PluginBuilder) ? ::PluginBuilder : Rails::PluginBuilder + end + + def rakefile_test_tasks + <<-RUBY +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + RUBY + end + + def dummy_path(path = nil) + @dummy_path = path if path + @dummy_path || options[:dummy_path] + end + + def mute(&block) + shell.mute(&block) + end + end + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec new file mode 100644 index 0000000000..3d9bfb22c7 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -0,0 +1,9 @@ +# Provide a simple gemspec so you can easily use your +# project in your rails apps through git. +Gem::Specification.new do |s| + s.name = "<%= name %>" + s.summary = "Insert <%= camelized %> summary." + s.description = "Insert <%= camelized %> description." + s.files = Dir["lib/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"] + s.version = "0.0.1" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile new file mode 100644 index 0000000000..29900c93dc --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -0,0 +1,11 @@ +source "http://rubygems.org" + +<%= rails_gemfile_entry -%> + +<% if full? -%> +<%= database_gemfile_entry -%> +<% end -%> + +if RUBY_VERSION < '1.9' + gem "ruby-debug", ">= 0.10.3" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE b/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE new file mode 100644 index 0000000000..d7a9109894 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright <%= Date.today.year %> YOURNAME + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc b/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc new file mode 100644 index 0000000000..301d647731 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/README.rdoc @@ -0,0 +1,3 @@ += <%= camelized %> + +This project rocks and uses MIT-LICENSE.
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile new file mode 100644 index 0000000000..88f50f9f04 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Rakefile @@ -0,0 +1,18 @@ +# encoding: UTF-8 +require 'rubygems' +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end + +require 'rake' +require 'rake/rdoctask' + +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = '<%= camelized %>' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt new file mode 100644 index 0000000000..f225bc9f7f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/controllers/%name%/application_controller.rb.tt @@ -0,0 +1,4 @@ +module <%= camelized %> + class ApplicationController < ActiveController::Base + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt new file mode 100644 index 0000000000..40ae9f52c2 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/app/helpers/%name%/application_helper.rb.tt @@ -0,0 +1,4 @@ +module <%= camelized %> + module ApplicationHelper + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb new file mode 100644 index 0000000000..42ddf380d8 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/config/routes.rb @@ -0,0 +1,3 @@ +<%= camelized %>::Engine.routes.draw do + +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/gitignore b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore new file mode 100644 index 0000000000..1463de6dfb --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/gitignore @@ -0,0 +1,6 @@ +.bundle/ +log/*.log +pkg/ +test/dummy/db/*.sqlite3 +test/dummy/log/*.log +test/dummy/tmp/
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb new file mode 100644 index 0000000000..2d3bdc510c --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%.rb @@ -0,0 +1,6 @@ +<% if full? -%> +require "<%= name %>/engine" + +<% end -%> +module <%= camelized %> +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb new file mode 100644 index 0000000000..9600ee0c3f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/%name%/engine.rb @@ -0,0 +1,7 @@ +module <%= camelized %> + class Engine < Rails::Engine +<% if mountable? -%> + isolate_namespace <%= camelized %> +<% end -%> + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake b/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake new file mode 100644 index 0000000000..7121f5ae23 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/lib/tasks/%name%_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :<%= name %> do +# # Task goes here +# end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb new file mode 100644 index 0000000000..8b68280a5e --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -0,0 +1,16 @@ +require File.expand_path('../boot', __FILE__) + +<% unless options[:skip_active_record] -%> +require 'rails/all' +<% else -%> +# require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "active_resource/railtie" +require "rails/test_unit/railtie" +<% end -%> + +Bundler.require +require "<%= name %>" + +<%= application_definition %> diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb new file mode 100644 index 0000000000..eba0681370 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/boot.rb @@ -0,0 +1,10 @@ +require 'rubygems' +gemfile = File.expand_path('../../../../Gemfile', __FILE__) + +if File.exist?(gemfile) + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +end + +$:.unshift File.expand_path('../../../../lib', __FILE__)
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb new file mode 100644 index 0000000000..730ee31c3d --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/routes.rb @@ -0,0 +1,4 @@ +Rails.application.routes.draw do + + mount <%= camelized %>::Engine => "/<%= name %>" +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt new file mode 100644 index 0000000000..ebd5a77dd5 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/script/rails.tt @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +ENGINE_PATH = File.expand_path('../..', __FILE__) +load File.expand_path('../../<%= dummy_path %>/script/rails', __FILE__) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb new file mode 100644 index 0000000000..0a8bbd4aaf --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/%name%_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class <%= camelized %>Test < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, <%= camelized %> + end +end diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb new file mode 100644 index 0000000000..d06fe7cbd0 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/integration/navigation_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + fixtures :all + + # Replace this with your real tests. + test "the truth" do + assert true + end +end + diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb new file mode 100644 index 0000000000..dbcaf6b92f --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/test/test_helper.rb @@ -0,0 +1,17 @@ +# Configure Rails Envinronment +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../dummy/config/environment.rb", __FILE__) +require "rails/test_help" + +Rails.backtrace_cleaner.remove_silencers! + +<% if full? && !options[:skip_active_record] -%> +# Run any available migration from application +ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__) +# and from engine +ActiveRecord::Migrator.migrate File.expand_path("../../db/migrate/", __FILE__) +<% end -%> + +# Load support files +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } diff --git a/railties/lib/rails/generators/rails/resource/resource_generator.rb b/railties/lib/rails/generators/rails/resource/resource_generator.rb index 8a943013d3..c7345f3cfb 100644 --- a/railties/lib/rails/generators/rails/resource/resource_generator.rb +++ b/railties/lib/rails/generators/rails/resource/resource_generator.rb @@ -16,9 +16,9 @@ module Rails def add_resource_route return if options[:actions].present? - route_config = class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ") + route_config = regular_class_path.collect{|namespace| "namespace :#{namespace} do " }.join(" ") route_config << "resources :#{file_name.pluralize}" - route_config << " end" * class_path.size + route_config << " end" * regular_class_path.size route route_config end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index b21340f755..b5317a055b 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -1,3 +1,4 @@ +<% module_namespacing do -%> class <%= controller_class_name %>Controller < ApplicationController # GET <%= route_url %> # GET <%= route_url %>.xml @@ -81,3 +82,4 @@ class <%= controller_class_name %>Controller < ApplicationController end end end +<% end -%> diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 829f4b200a..d6ccfc496a 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -17,7 +17,7 @@ module Rails def initialize(*args) #:nodoc: super - if name == name.pluralize && !options[:force_plural] + if name == name.pluralize && name.singularize != name.pluralize && !options[:force_plural] unless ResourceHelpers.skip_warn say "Plural version of the model detected, using singularized version. Override with --force-plural." ResourceHelpers.skip_warn = true @@ -34,7 +34,7 @@ module Rails attr_reader :controller_name def controller_class_path - @class_path + class_path end def controller_file_name @@ -46,7 +46,7 @@ module Rails end def controller_class_name - @controller_class_name ||= (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::') + (controller_class_path + [controller_file_name]).map!{ |m| m.camelize }.join('::') end def controller_i18n_scope diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index f23e495450..964d59d84c 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -1,5 +1,6 @@ require 'test_helper' +<% module_namespacing do -%> class <%= controller_class_name %>ControllerTest < ActionController::TestCase setup do @<%= singular_table_name %> = <%= table_name %>(:one) @@ -47,3 +48,4 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase assert_redirected_to <%= index_helper %>_path end end +<% end -%> diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 6cbd1f21c0..d05e031f56 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -1,5 +1,4 @@ require "cgi" -require "active_support/core_ext/cgi" module Rails module Info diff --git a/railties/railties.gemspec b/railties/railties.gemspec index d26c1bcdbc..c3793d3ac3 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.has_rdoc = false s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '~> 0.14.3') + s.add_dependency('thor', '~> 0.14.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 8e527236ea..719550f9d9 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -38,5 +38,14 @@ module ApplicationTests assert_match "Code LOC: 5 Test LOC: 0 Code to Test Ratio: 1:0.0", Dir.chdir(app_path){ `rake stats` } end + + def test_rake_routes_output_strips_anchors_from_http_verbs + app_file "config/routes.rb", <<-RUBY + AppTemplate::Application.routes.draw do + get '/cart', :to => 'cart#show' + end + RUBY + assert_match 'cart GET /cart(.:format)', Dir.chdir(app_path){ `rake routes` } + end end end diff --git a/railties/test/fixtures/lib/empty_builder.rb b/railties/test/fixtures/lib/app_builders/empty_builder.rb index babd9c2461..babd9c2461 100644 --- a/railties/test/fixtures/lib/empty_builder.rb +++ b/railties/test/fixtures/lib/app_builders/empty_builder.rb diff --git a/railties/test/fixtures/lib/app_builders/simple_builder.rb b/railties/test/fixtures/lib/app_builders/simple_builder.rb new file mode 100644 index 0000000000..993d3a2aa2 --- /dev/null +++ b/railties/test/fixtures/lib/app_builders/simple_builder.rb @@ -0,0 +1,7 @@ +class AppBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/app_builders/tweak_builder.rb b/railties/test/fixtures/lib/app_builders/tweak_builder.rb new file mode 100644 index 0000000000..cb50be01cb --- /dev/null +++ b/railties/test/fixtures/lib/app_builders/tweak_builder.rb @@ -0,0 +1,7 @@ +class AppBuilder < Rails::AppBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/create_test_dummy_template.rb b/railties/test/fixtures/lib/create_test_dummy_template.rb new file mode 100644 index 0000000000..e4378bbd1a --- /dev/null +++ b/railties/test/fixtures/lib/create_test_dummy_template.rb @@ -0,0 +1 @@ +create_dummy_app("spec/dummy") diff --git a/railties/test/fixtures/lib/plugin_builders/empty_builder.rb b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb new file mode 100644 index 0000000000..5c5607621c --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/empty_builder.rb @@ -0,0 +1,2 @@ +class PluginBuilder +end diff --git a/railties/test/fixtures/lib/plugin_builders/simple_builder.rb b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb new file mode 100644 index 0000000000..08f6c5535d --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/simple_builder.rb @@ -0,0 +1,7 @@ +class PluginBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/plugin_builders/spec_builder.rb b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb new file mode 100644 index 0000000000..aa18c7ddaa --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/spec_builder.rb @@ -0,0 +1,19 @@ +class PluginBuilder < Rails::PluginBuilder + def test + create_file "spec/spec_helper.rb" + append_file "Rakefile", <<-EOF +# spec tasks in rakefile + +task :default => :spec + EOF + end + + def generate_test_dummy + dummy_path("spec/dummy") + super + end + + def skip_test_unit? + true + end +end diff --git a/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb new file mode 100644 index 0000000000..1e801409a4 --- /dev/null +++ b/railties/test/fixtures/lib/plugin_builders/tweak_builder.rb @@ -0,0 +1,7 @@ +class PluginBuilder < Rails::PluginBuilder + def gitignore + create_file ".gitignore", <<-R.strip +foobar + R + end +end diff --git a/railties/test/fixtures/lib/simple_builder.rb b/railties/test/fixtures/lib/simple_builder.rb deleted file mode 100644 index 47dcdc0d96..0000000000 --- a/railties/test/fixtures/lib/simple_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder - def configru - create_file "config.ru", <<-R.strip -run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] } - R - end -end
\ No newline at end of file diff --git a/railties/test/fixtures/lib/tweak_builder.rb b/railties/test/fixtures/lib/tweak_builder.rb deleted file mode 100644 index eed20ecc9b..0000000000 --- a/railties/test/fixtures/lib/tweak_builder.rb +++ /dev/null @@ -1,7 +0,0 @@ -class AppBuilder < Rails::AppBuilder - def configru - create_file "config.ru", <<-R.strip -run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] } - R - end -end
\ No newline at end of file diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c7339cb8d2..7faa674a81 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'rails/generators/rails/app/app_generator' +require 'generators/shared_generator_tests.rb' DEFAULT_APP_FILES = %w( .gitignore @@ -40,36 +41,10 @@ DEFAULT_APP_FILES = %w( class AppGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper arguments [destination_root] + include SharedGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - - Kernel::silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) - @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) - end - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Rails.application = TestApp::Application.instance - end - - def test_application_skeleton_is_created - run_generator - - DEFAULT_APP_FILES.each{ |path| assert_file path } - end - - def test_application_generate_pretend - run_generator ["testapp", "--pretend"] - - DEFAULT_APP_FILES.each{ |path| assert_no_file path } + def default_files + ::DEFAULT_APP_FILES end def test_application_controller_and_layout_files @@ -78,36 +53,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "public/stylesheets/application.css" end - def test_options_before_application_name_raises_an_error - content = capture(:stderr){ run_generator(["--skip-active-record", destination_root]) } - assert_equal "Options should be given after the application name. For details run: rails --help\n", content - end - - def test_name_collision_raises_an_error - reserved_words = %w[application destroy plugin runner test] - reserved_words.each do |reserved| - content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } - assert_equal "Invalid application name #{reserved}. Please give a name which does not match one of the reserved rails words.\n", content - end - end - - def test_invalid_database_option_raises_an_error - content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } - assert_match /Invalid value for \-\-database option/, content - end - def test_invalid_application_name_raises_an_error content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } assert_equal "Invalid application name 43-things. Please give a name which does not start with numbers.\n", content end - def test_application_name_raises_an_error_if_name_already_used_constant - %w{ String Hash Class Module Set Symbol }.each do |ruby_class| - content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } - assert_equal "Invalid application name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another application name.\n", content - end - end - def test_invalid_application_name_is_fixed run_generator [File.join(destination_root, "things-43")] assert_file "things-43/config/environment.rb", /Things43::Application\.initialize!/ @@ -133,11 +83,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "myapp_moved/config/environment.rb", /Myapp::Application\.initialize!/ assert_file "myapp_moved/config/initializers/session_store.rb", /_myapp_session/ end - + def test_rails_update_generates_correct_session_key app_root = File.join(destination_root, 'myapp') run_generator [app_root] - + Rails.application.config.root = app_root Rails.application.class.stubs(:name).returns("Myapp") Rails.application.stubs(:is_a?).returns(Rails::Application) @@ -187,12 +137,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/rails.js" assert_file "test" end - - def test_test_unit_is_skipped_if_required - run_generator [destination_root, "--skip-test-unit"] - assert_no_file "test" - end - + def test_javascript_is_skipped_if_required run_generator [destination_root, "--skip-javascript"] assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(\)/ @@ -200,7 +145,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_file "public/javascripts/prototype.js" assert_no_file "public/javascripts/rails.js" end - + def test_config_prototype_javascript_library run_generator [destination_root, "-j", "prototype"] assert_file "config/application.rb", /#\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/ @@ -208,7 +153,7 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/prototype.js" assert_file "public/javascripts/rails.js", /prototype/ end - + def test_config_jquery_javascript_library run_generator [destination_root, "-j", "jquery"] assert_file "config/application.rb", /^\s+config\.action_view\.javascript_expansions\[:defaults\]\s+=\s+%w\(jquery rails\)/ @@ -217,36 +162,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "public/javascripts/rails.js", /jQuery/ end - def test_shebang_is_added_to_rails_file - run_generator [destination_root, "--ruby", "foo/bar/baz"] - assert_file "script/rails", /#!foo\/bar\/baz/ - end - - def test_shebang_when_is_the_same_as_default_use_env - run_generator [destination_root, "--ruby", Thor::Util.ruby_command] - assert_file "script/rails", /#!\/usr\/bin\/env/ - end - def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]) end - def test_template_raises_an_error_with_invalid_path - content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } - assert_match /The template \[.*\] could not be loaded/, content - assert_match /non\/existant\/path/, content - end - - def test_template_is_executed_when_supplied - path = "http://gist.github.com/103208.txt" - template = %{ say "It works!" } - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - assert_match /It works!/, silence(:stdout){ generator.invoke_all } - end - def test_usage_read_from_file File.expects(:read).returns("USAGE FROM FILE") assert_equal "USAGE FROM FILE", Rails::Generators::AppGenerator.desc @@ -266,17 +186,11 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file 'lib/test_file.rb', 'heres test data' end - def test_dev_option - generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - rails_path = File.expand_path('../../..', Rails.root) - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ - end - - def test_edge_option - generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") - silence(:stdout){ generator.invoke_all } - assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/ + def test_test_unit_is_removed_from_frameworks_if_skip_test_unit_is_given + run_generator [destination_root, "--skip-test-unit"] + assert_file "config/application.rb" do |file| + assert_match /config.generators.test_framework = false/, file + end end protected @@ -292,62 +206,21 @@ class CustomAppGeneratorTest < Rails::Generators::TestCase tests Rails::Generators::AppGenerator arguments [destination_root] + include SharedCustomGeneratorTests - def setup - Rails.application = TestApp::Application - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') - end - - def teardown - super - Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) - Object.class_eval { remove_const :AppBuilder if const_defined?(:AppBuilder) } - Rails.application = TestApp::Application.instance - end - - def test_builder_option_with_empty_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/empty_builder.rb"]) - DEFAULT_APP_FILES.each{ |path| assert_no_file path } - end - - def test_builder_option_with_simple_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/simple_builder.rb"]) - (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] - end - - def test_builder_option_with_relative_path - here = File.expand_path(File.dirname(__FILE__)) - FileUtils.cd(here) - run_generator([destination_root, "-b", "../fixtures/lib/simple_builder.rb"]) - (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] +protected + def default_files + ::DEFAULT_APP_FILES end - def test_builder_option_with_tweak_app_builder - FileUtils.cd(Rails.root) - run_generator([destination_root, "-b", "#{Rails.root}/lib/tweak_builder.rb"]) - DEFAULT_APP_FILES.each{ |path| assert_file path } - assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }] + def builders_dir + "app_builders" end - def test_builder_option_with_http - path = "http://gist.github.com/103208.txt" - template = "class AppBuilder; end" - template.instance_eval "def read; self; end" # Make the string respond to read - - generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) - capture(:stdout) { generator.invoke_all } - - DEFAULT_APP_FILES.each{ |path| assert_no_file path } + def builder_class + :AppBuilder end -protected - def action(*args, &block) silence(:stdout){ generator.send(*args, &block) } end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index d1190fd17d..d61a02d32f 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -3,6 +3,7 @@ require 'rails/generators/rails/controller/controller_generator' require 'rails/generators/rails/model/model_generator' require 'rails/generators/rails/observer/observer_generator' require 'rails/generators/mailer/mailer_generator' +require 'rails/generators/rails/scaffold/scaffold_generator' class NamespacedGeneratorTestCase < Rails::Generators::TestCase def setup @@ -202,3 +203,155 @@ class NamespacedMailerGeneratorTest < NamespacedGeneratorTestCase assert_file "app/views/test_app/notifier" end end + +class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase + include GeneratorsTestHelper + arguments %w(product_line title:string price:integer) + tests Rails::Generators::ScaffoldGenerator + + setup :copy_routes + + def test_scaffold_on_invoke + run_generator + + # Model + assert_file "app/models/test_app/product_line.rb", /module TestApp\n class ProductLine < ActiveRecord::Base/ + assert_file "test/unit/test_app/product_line_test.rb", /module TestApp\n class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/product_lines.yml" + assert_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/resources :product_lines$/, route) + end + + # Controller + assert_file "app/controllers/test_app/product_lines_controller.rb" do |content| + assert_match(/module TestApp\n class ProductLinesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/product_lines_controller_test.rb", + /module TestApp\n class ProductLinesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/product_lines/#{view}.html.erb" } + assert_no_file "app/views/layouts/test_app/product_lines.html.erb" + + # Helpers + assert_file "app/helpers/test_app/product_lines_helper.rb" + assert_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + + # Stylesheets + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_on_revoke + run_generator + run_generator ["product_line"], :behavior => :revoke + + # Model + assert_no_file "app/models/test_app/product_line.rb" + assert_no_file "test/unit/test_app/product_line_test.rb" + assert_no_file "test/fixtures/test_app/product_lines.yml" + assert_no_migration "db/migrate/create_test_app_product_lines.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/resources :product_lines$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/product_lines_controller.rb" + assert_no_file "test/functional/test_app/product_lines_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/product_lines" + assert_no_file "app/views/test_app/layouts/product_lines.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/product_lines_helper.rb" + assert_no_file "test/unit/helpers/test_app/product_lines_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_invoke + run_generator [ "admin/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/role.rb", /module TestApp\n class Admin::Role < ActiveRecord::Base/ + assert_file "test/unit/test_app/admin/role_test.rb", /module TestApp\n class Admin::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/roles.yml" + assert_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/namespace :admin do resources :roles end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/admin/roles_controller_test.rb", + /module TestApp\n class Admin::RolesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/admin/roles/#{view}.html.erb" } + assert_no_file "app/views/layouts/admin/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/roles_helper.rb" + assert_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + + # Stylesheets + assert_file "public/stylesheets/scaffold.css" + end + + def test_scaffold_with_namespace_on_revoke + run_generator [ "admin/role", "name:string", "description:string" ] + run_generator [ "admin/role" ], :behavior => :revoke + + # Model + assert_file "app/models/test_app/admin.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/role.rb" + assert_no_file "test/unit/test_app/admin/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/namespace :admin do resources :roles end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/roles_controller.rb" + assert_no_file "test/functional/test_app/admin/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/roles" + assert_no_file "app/views/layouts/test_app/admin/roles.html.erb" + + # Helpers + assert_no_file "app/helpers/test_app/admin/roles_helper.rb" + assert_no_file "test/unit/helpers/test_app/admin/roles_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "public/stylesheets/scaffold.css" + end +end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb new file mode 100644 index 0000000000..12cae7cfdf --- /dev/null +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -0,0 +1,191 @@ +require 'abstract_unit' +require 'generators/generators_test_helper' +require 'rails/generators/rails/plugin_new/plugin_new_generator' +require 'generators/shared_generator_tests.rb' + +DEFAULT_PLUGIN_FILES = %w( + .gitignore + Gemfile + Rakefile + bukkits.gemspec + MIT-LICENSE + lib + lib/bukkits.rb + lib/tasks/bukkits_tasks.rake + script/rails + test/bukkits_test.rb + test/test_helper.rb + test/dummy +) + +class PluginNewGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + destination File.join(Rails.root, "tmp/bukkits") + arguments [destination_root] + include SharedGeneratorTests + + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def test_invalid_plugin_name_raises_an_error + content = capture(:stderr){ run_generator [File.join(destination_root, "43-things")] } + assert_equal "Invalid plugin name 43-things. Please give a name which does not start with numbers.\n", content + end + + def test_invalid_plugin_name_is_fixed + run_generator [File.join(destination_root, "things-43")] + assert_file "things-43/lib/things-43.rb", /module Things43/ + end + + def test_generating_test_files + run_generator + assert_file "test/test_helper.rb" + assert_file "test/bukkits_test.rb", /assert_kind_of Module, Bukkits/ + end + + def test_generating_test_files_in_full_mode + run_generator [destination_root, "--full"] + assert_directory "test/integration/" + + assert_file "test/integration/navigation_test.rb", /ActionDispatch::IntegrationTest/ + end + + def test_ensure_that_plugin_options_are_not_passed_to_app_generator + FileUtils.cd(Rails.root) + assert_no_match /It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]) + end + + def test_ensure_that_test_dummy_can_be_generated_from_a_template + FileUtils.cd(Rails.root) + run_generator([destination_root, "-m", "lib/create_test_dummy_template.rb", "--skip-test-unit"]) + assert_file "spec/dummy" + assert_no_file "test" + end + + def test_database_entry_is_assed_by_default_in_full_mode + run_generator([destination_root, "--full"]) + assert_file "test/dummy/config/database.yml", /sqlite/ + assert_file "Gemfile", /^gem\s+["']sqlite3-ruby["'],\s+:require\s+=>\s+["']sqlite3["']$/ + end + + def test_config_another_database + run_generator([destination_root, "-d", "mysql", "--full"]) + assert_file "test/dummy/config/database.yml", /mysql/ + assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + end + + def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given + run_generator [destination_root, "--skip-active-record"] + assert_file "test/dummy/config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + end + + def test_ensure_that_skip_active_record_option_is_passed_to_app_generator + run_generator [destination_root, "--skip_active_record"] + assert_no_file "test/dummy/config/database.yml" + assert_no_match /ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb")) + end + + def test_ensure_that_database_option_is_passed_to_app_generator + run_generator [destination_root, "--database", "postgresql"] + assert_file "test/dummy/config/database.yml", /postgres/ + end + + def test_ensure_that_javascript_option_is_passed_to_app_generator + run_generator [destination_root, "--javascript", "jquery"] + assert_file "test/dummy/public/javascripts/jquery.js" + end + + def test_ensure_that_skip_javascript_option_is_passed_to_app_generator + run_generator [destination_root, "--skip_javascript"] + assert_no_file "test/dummy/public/javascripts/prototype.js" + end + + def test_template_from_dir_pwd + FileUtils.cd(Rails.root) + assert_match /It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"]) + end + + def test_ensure_that_tests_works + run_generator + FileUtils.cd destination_root + `bundle install` + assert_match /1 tests, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test` + end + + def test_ensure_that_tests_works_in_full_mode + run_generator [destination_root, "--full"] + FileUtils.cd destination_root + `bundle install` + assert_match /2 tests, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test` + end + + def test_creating_engine_in_full_mode + run_generator [destination_root, "--full"] + assert_file "lib/bukkits/engine.rb", /module Bukkits\n class Engine < Rails::Engine\n end\nend/ + assert_file "lib/bukkits.rb", /require "bukkits\/engine"/ + end + + def test_being_quiet_while_creating_dummy_application + assert_no_match /create\s+config\/application.rb/, run_generator + end + + def test_create_mountable_application_with_mountable_option + run_generator [destination_root, "--mountable"] + assert_file "config/routes.rb", /Bukkits::Engine.routes.draw do/ + assert_file "lib/bukkits/engine.rb", /isolate_namespace Bukkits/ + assert_file "test/dummy/config/routes.rb", /mount Bukkits::Engine => "\/bukkits"/ + assert_file "app/controllers/bukkits/application_controller.rb", /module Bukkits\n class ApplicationController < ActiveController::Base/ + assert_file "app/helpers/bukkits/application_helper.rb", /module Bukkits\n module ApplicationHelper/ + end + + def test_passing_dummy_path_as_a_parameter + run_generator [destination_root, "--dummy_path", "spec/dummy"] + assert_file "spec/dummy" + assert_file "spec/dummy/config/application.rb" + assert_no_file "test/dummy" + end + +protected + + def action(*args, &block) + silence(:stdout){ generator.send(*args, &block) } + end + +end + +class CustomPluginGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::PluginNewGenerator + + destination File.join(Rails.root, "tmp/bukkits") + arguments [destination_root] + include SharedCustomGeneratorTests + + def test_overriding_test_framework + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/plugin_builders/spec_builder.rb"]) + assert_file 'spec/spec_helper.rb' + assert_file 'spec/dummy' + assert_file 'Rakefile', /task :default => :spec/ + assert_file 'Rakefile', /# spec tasks in rakefile/ + assert_file 'script/rails', %r{spec/dummy} + end + +protected + def default_files + ::DEFAULT_PLUGIN_FILES + end + + def builder_class + :PluginBuilder + end + + def builders_dir + "plugin_builders" + end + + def action(*args, &block) + silence(:stdout){ generator.send(*args, &block) } + end +end diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 55d5bd6f83..71b3619351 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -73,6 +73,11 @@ class ResourceGeneratorTest < Rails::Generators::TestCase assert_no_match /Plural version of the model detected/, content end + def test_mass_nouns_do_not_throw_warnings + content = run_generator ["sheep".freeze] + assert_no_match /Plural version of the model detected/, content + end + def test_route_is_removed_on_revoke run_generator run_generator ["account"], :behavior => :revoke diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb new file mode 100644 index 0000000000..d117656fbd --- /dev/null +++ b/railties/test/generators/shared_generator_tests.rb @@ -0,0 +1,187 @@ +module SharedGeneratorTests + def setup + Rails.application = TestApp::Application + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + + Kernel::silence_warnings do + Thor::Base.shell.send(:attr_accessor, :always_force) + @shell = Thor::Base.shell.new + @shell.send(:always_force=, true) + end + end + + def teardown + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Rails.application = TestApp::Application.instance + end + + def test_skeleton_is_created + run_generator + + default_files.each{ |path| assert_file path } + end + + def test_plugin_new_generate_pretend + run_generator ["testapp", "--pretend"] + + default_files.each{ |path| assert_no_file path } + end + + def test_invalid_database_option_raises_an_error + content = capture(:stderr){ run_generator([destination_root, "-d", "unknown"]) } + assert_match /Invalid value for \-\-database option/, content + end + + def test_test_unit_is_skipped_if_required + run_generator [destination_root, "--skip-test-unit"] + assert_no_file "test" + end + + def test_options_before_application_name_raises_an_error + content = capture(:stderr){ run_generator(["--pretend", destination_root]) } + assert_match /Options should be given after the \w+ name. For details run: rails( plugin)? --help\n/, content + end + + def test_name_collision_raises_an_error + reserved_words = %w[application destroy plugin runner test] + reserved_words.each do |reserved| + content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } + assert_match /Invalid \w+ name #{reserved}. Please give a name which does not match one of the reserved rails words.\n/, content + end + end + + def test_name_raises_an_error_if_name_already_used_constant + %w{ String Hash Class Module Set Symbol }.each do |ruby_class| + content = capture(:stderr){ run_generator [File.join(destination_root, ruby_class)] } + assert_match /Invalid \w+ name #{ruby_class}, constant #{ruby_class} is already in use. Please choose another \w+ name.\n/, content + end + end + + def test_shebang_is_added_to_rails_file + run_generator [destination_root, "--ruby", "foo/bar/baz"] + assert_file "script/rails", /#!foo\/bar\/baz/ + end + + def test_shebang_when_is_the_same_as_default_use_env + run_generator [destination_root, "--ruby", Thor::Util.ruby_command] + assert_file "script/rails", /#!\/usr\/bin\/env/ + end + + def test_template_raises_an_error_with_invalid_path + content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } + assert_match /The template \[.*\] could not be loaded/, content + assert_match /non\/existant\/path/, content + end + + def test_template_is_executed_when_supplied + path = "http://gist.github.com/103208.txt" + template = %{ say "It works!" } + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + assert_match /It works!/, silence(:stdout){ generator.invoke_all } + end + + def test_dev_option + generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + rails_path = File.expand_path('../../..', Rails.root) + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + end + + def test_template_raises_an_error_with_invalid_path + content = capture(:stderr){ run_generator([destination_root, "-m", "non/existant/path"]) } + assert_match /The template \[.*\] could not be loaded/, content + assert_match /non\/existant\/path/, content + end + + def test_template_is_executed_when_supplied + path = "http://gist.github.com/103208.txt" + template = %{ say "It works!" } + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :template => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + assert_match /It works!/, silence(:stdout){ generator.invoke_all } + end + + def test_dev_option + generator([destination_root], :dev => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + rails_path = File.expand_path('../../..', Rails.root) + assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:path\s+=>\s+["']#{Regexp.escape(rails_path)}["']$/ + end + + def test_edge_option + generator([destination_root], :edge => true).expects(:run).with("#{@bundle_command} install") + silence(:stdout){ generator.invoke_all } + assert_file 'Gemfile', %r{^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$} + end +end + +module SharedCustomGeneratorTests + def setup + Rails.application = TestApp::Application + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') + end + + def teardown + super + Rails::Generators::AppGenerator.instance_variable_set('@desc', nil) + Object.class_eval do + remove_const :AppBuilder if const_defined?(:AppBuilder) + remove_const :PluginBuilder if const_defined?(:PluginBuilder) + end + Rails.application = TestApp::Application.instance + end + + def test_builder_option_with_empty_app_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/empty_builder.rb"]) + default_files.each{ |path| assert_no_file path } + end + + def test_builder_option_with_simple_plugin_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/simple_builder.rb"]) + (default_files - ['.gitignore']).each{ |path| assert_no_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_relative_path + here = File.expand_path(File.dirname(__FILE__)) + FileUtils.cd(here) + run_generator([destination_root, "-b", "../fixtures/lib/#{builders_dir}/simple_builder.rb"]) + FileUtils.cd(destination_root) + (default_files - ['.gitignore']).each{ |path| assert_no_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_tweak_plugin_builder + FileUtils.cd(destination_root) + run_generator([destination_root, "-b", "#{Rails.root}/lib/#{builders_dir}/tweak_builder.rb"]) + default_files.each{ |path| assert_file path } + assert_file ".gitignore", "foobar" + end + + def test_builder_option_with_http + path = "http://gist.github.com/103208.txt" + template = "class #{builder_class}; end" + template.instance_eval "def read; self; end" # Make the string respond to read + + generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template) + capture(:stdout) { generator.invoke_all } + + default_files.each{ |path| assert_no_file path } + end +end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 4b52260ecc..822be24ef1 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -685,5 +685,25 @@ module RailtiesTest assert_equal :haml , generators[:template_engine] assert_equal :rspec , generators[:test_framework] end + + test "engine should get default generators with ability to overwrite them" do + @plugin.write "lib/bukkits.rb", <<-RUBY + module Bukkits + class Engine < ::Rails::Engine + config.generators.test_framework :rspec + end + end + RUBY + + boot_rails + require "#{rails_root}/config/environment" + + generators = Bukkits::Engine.config.generators.options[:rails] + assert_equal :active_record, generators[:orm] + assert_equal :rspec , generators[:test_framework] + + app_generators = Rails.application.config.generators.options[:rails] + assert_equal :test_unit , app_generators[:test_framework] + end end end |