diff options
173 files changed, 2316 insertions, 839 deletions
@@ -5,12 +5,12 @@ gemspec if ENV['AREL'] gem "arel", :path => ENV['AREL'] else - gem "arel", '~> 2.1.0' + gem "arel", '~> 2.1.3' end gem "coffee-script" gem "sass" -gem "uglifier", :git => "git://github.com/lautis/uglifier.git" +gem "uglifier", ">= 1.0.0" gem "rake", ">= 0.8.7" gem "mocha", ">= 0.9.8" @@ -26,9 +26,6 @@ gem "memcache-client", ">= 1.8.5" platforms :mri_18 do gem "system_timer" - # ruby-debug requires linecache which depends on require_relative but doesn't explicitly - # declare this in its gemspec - gem "require_relative" gem "ruby-debug", ">= 0.10.3" gem "json" end @@ -44,7 +41,7 @@ platforms :ruby do end gem "json" gem "yajl-ruby" - gem "nokogiri", ">= 1.4.4" + gem "nokogiri", ">= 1.4.5" group :test do gem "ruby-prof" if RUBY_VERSION < "1.9.3" @@ -56,7 +53,7 @@ platforms :ruby do group :db do gem "pg", ">= 0.11.0" gem "mysql", ">= 2.8.1" - gem "mysql2", ">= 0.3.0" + gem "mysql2", ">= 0.3.6" end end diff --git a/README.rdoc b/README.rdoc index 7e2d7850c8..bca2126559 100644 --- a/README.rdoc +++ b/README.rdoc @@ -18,7 +18,7 @@ you to present the data from database rows as objects and embellish these data o with business logic methods. Although most Rails models are backed by a database, models can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as provided by the ActiveModel module. You can read more about Active Record in its -{README}[link:files/activerecord/README_rdoc.html]. +{README}[link:blob/master/activerecord/README.rdoc]. The Controller layer is responsible for handling incoming HTTP requests and providing a suitable response. Usually this means returning HTML, but Rails controllers can also @@ -29,7 +29,7 @@ In Rails, the Controller and View layers are handled together by Action Pack. These two layers are bundled in a single package due to their heavy interdependence. This is unlike the relationship between the Active Record and Action Pack which are independent. Each of these packages can be used independently outside of Rails. You -can read more about Action Pack in its {README}[link:files/actionpack/README_rdoc.html]. +can read more about Action Pack in its {README}[link:blob/master/actionpack/README.rdoc]. == Getting Started @@ -61,6 +61,9 @@ RDoc::Task.new do |rdoc| # The temporary solution is to have a README.rdoc without backslashes for # GitHub, and gsub it to generate the main page of the API. # + # Also, relative links in GitHub have to point to blobs, whereas in the API + # they need to point to files. + # # The idea for the future is to have totally different files, since the # API is no longer a generic entry point to Rails and deserves a # dedicated main page specifically thought as an API entry point. @@ -71,6 +74,7 @@ RDoc::Task.new do |rdoc| # since no autolinking happens there and RDoc displays the backslash # otherwise. rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" } + rdoc_main.gsub!(%r{link:blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html") File.open(RDOC_MAIN, 'w') do |f| f.write(rdoc_main) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 3b4e59d703..cb289fd693 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -57,7 +57,7 @@ module ActionMailer #:nodoc: # will accept (any valid Email header including optional fields). # # The mail method, if not passed a block, will inspect your views and send all the views with - # the same name as the method, so the above action would send the +welcome.text.plain.erb+ view + # the same name as the method, so the above action would send the +welcome.text.erb+ view # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email. # # If you want to explicitly render only certain templates, pass a block: @@ -88,7 +88,7 @@ module ActionMailer #:nodoc: # # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same # name as the method in your mailer model. For example, in the mailer defined above, the template at - # <tt>app/views/notifier/signup_notification.text.plain.erb</tt> would be used to generate the email. + # <tt>app/views/notifier/welcome.text.erb</tt> would be used to generate the email. # # Variables defined in the model are accessible as instance variables in the view. # @@ -122,18 +122,19 @@ module ActionMailer #:nodoc: # # <%= users_url(:host => "example.com") %> # - # You want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't - # make sense to generate relative URLs in email messages. + # You should use the <tt>named_route_url</tt> style (which generates absolute URLs) and avoid using the + # <tt>named_route_path</tt> style (which generates relative URLs), since clients reading the mail will + # have no concept of a current URL from which to determine a relative path. # # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> # option as a configuration option in <tt>config/application.rb</tt>: # # config.action_mailer.default_url_options = { :host => "example.com" } # - # If you do decide to set a default <tt>:host</tt> for your mailers you want to use the - # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are - # generated because the <tt>url_for</tt> view helper will, by default, generate relative URLs when a - # <tt>:host</tt> option isn't explicitly provided. + # When you decide to set a default <tt>:host</tt> for your mailers, then you need to make sure to use the + # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. Since the <tt>url_for</tt> view helper + # will generate relative URLs by default when a <tt>:host</tt> option isn't explicitly provided, passing + # <tt>:only_path => false</tt> will ensure that absolute URLs are generated. # # = Sending mail # @@ -148,12 +149,12 @@ module ActionMailer #:nodoc: # # = Multipart Emails # - # Multipart messages can also be used implicitly because Action Mailer will automatically - # detect and use multipart templates, where each template is named after the name of the action, followed - # by the content type. Each such detected template will be added as separate part to the message. + # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use + # multipart templates, where each template is named after the name of the action, followed by the content + # type. Each such detected template will be added as a separate part to the message. # # For example, if the following templates exist: - # * signup_notification.text.plain.erb + # * signup_notification.text.erb # * signup_notification.text.html.erb # * signup_notification.text.xml.builder # * signup_notification.text.yaml.erb @@ -178,7 +179,7 @@ module ActionMailer #:nodoc: # end # end # - # Which will (if it had both a <tt>welcome.text.plain.erb</tt> and <tt>welcome.text.html.erb</tt> + # Which will (if it had both a <tt>welcome.text.erb</tt> and <tt>welcome.text.html.erb</tt> # template in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts, # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside, # and the second being a <tt>application/pdf</tt> with a Base64 encoded copy of the file.pdf book @@ -732,3 +733,4 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end end + diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index b324ba790d..d1467fb526 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -1,7 +1,7 @@ require 'tmpdir' module ActionMailer - # This modules handles everything related to the delivery, from registering new + # This module handles everything related to mail delivery, from registering new # delivery methods to configuring the mail object to be sent. module DeliveryMethods extend ActiveSupport::Concern diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index f4b6464bdc..bc252edadd 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,9 @@ *Rails 3.2.0 (unreleased)* +* send_file now guess the mime type [Esad Hajdarevic] + +* Mime type entries for PDF, ZIP and other formats were added [Esad Hajdarevic] + * Generate hidden input before select with :multiple option set to true. This is useful when you rely on the fact that when no options is set, the state of select will be sent to rails application. Without hidden field diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 37a3d960f1..16b1e78b4d 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -24,7 +24,7 @@ Gem::Specification.new do |s| s.add_dependency('rack', '~> 1.3.0') s.add_dependency('rack-test', '~> 0.6.0') s.add_dependency('rack-mount', '~> 0.8.1') - s.add_dependency('sprockets', '~> 2.0.0.beta.10') + s.add_dependency('sprockets', '= 2.0.0.beta.10') s.add_dependency('tzinfo', '~> 0.3.27') s.add_dependency('erubis', '~> 2.7.0') end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 8f73e244d7..d6f75bded0 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -81,11 +81,12 @@ module AbstractController # class EmployeeController < BankController # layout nil # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # The TellerController uses +teller.html.erb+, and TillController inherits that layout and - # uses it as well. + # In these examples: + # * The InformationController uses the "bank_standard" layout, inherited from BankController. + # * The TellerController follows convention and uses +app/views/layouts/teller.html.erb+. + # * The TillController inherits the layout from TellerController and uses +teller.html.erb+ as well. + # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method. + # * The EmployeeController does not use a layout at all. # # == Types of layouts # @@ -138,8 +139,8 @@ module AbstractController # # end # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # be rendered directly, without wrapping a layout around the rendered view. # # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. @@ -158,7 +159,7 @@ module AbstractController # end # end # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. + # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. module Layouts extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index c03c77cb4a..d14c5f940b 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -31,7 +31,7 @@ module ActionController # "302 Moved" HTTP response that takes the user to the index action. # # These two methods represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect. - # Most actions are variations of these themes. + # Most actions are variations on these themes. # # == Requests # @@ -116,8 +116,8 @@ module ActionController # # Title: <%= @post.title %> # - # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use - # the manual rendering methods: + # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates + # will use the manual rendering methods: # # def search # @results = Search.find(params[:query]) @@ -132,9 +132,9 @@ module ActionController # # == Redirects # - # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database, - # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to) - # a <tt>show</tt> action that we'll assume has already been created. The code might look like this: + # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to the + # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're + # going to reuse (and redirect to) a <tt>show</tt> action that we'll assume has already been created. The code might look like this: # # def create # @entry = Entry.new(params[:entry]) @@ -146,7 +146,9 @@ module ActionController # end # end # - # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed. + # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method, which is then executed. + # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), + # and not some internal re-routing which calls both "create" and then "show" within one request. # # Learn more about <tt>redirect_to</tt> and what options you have in ActionController::Redirecting. # diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index e9db0d97b6..938a6ae81c 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -61,6 +61,7 @@ module ActionController #:nodoc: end def after(controller) + self.controller = controller callback(:after) if controller.perform_caching # Clean up, so that the controller can be collected after this request self.controller = nil diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 8d813a8e38..35e29398e6 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -10,7 +10,7 @@ module ActionController format = payload[:format] format = format.to_s.upcase if format.is_a?(Symbol) - info " Processing by #{payload[:controller]}##{payload[:action]} as #{format}" + info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" info " Parameters: #{params.inspect}" unless params.empty? end @@ -20,10 +20,11 @@ module ActionController status = payload[:status] if status.nil? && payload[:exception].present? - status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil - end + status = Rack::Utils.status_code(ActionDispatch::ShowExceptions.rescue_responses[payload[:exception].first]) rescue nil + end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration message << " (#{additions.join(" | ")})" unless additions.blank? + message << "\n" info(message) end @@ -59,4 +60,4 @@ module ActionController end end -ActionController::LogSubscriber.attach_to :action_controller
\ No newline at end of file +ActionController::LogSubscriber.attach_to :action_controller diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index 623a9873fc..50827d8107 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -26,8 +26,11 @@ module ActionController #:nodoc: # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. # Defaults to <tt>File.basename(path)</tt>. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # * <tt>:type</tt> - specifies an HTTP content type. + # You can specify either a string or a symbol for a registered type register with + # <tt>Mime::Type.register</tt>, for example :json + # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. @@ -84,6 +87,8 @@ module ActionController #:nodoc: # * <tt>:filename</tt> - suggests a filename for the browser to use. # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # If omitted, type will be guessed from the file extension specified in <tt>:filename</tt>. + # If no content type is registered for the extension, default type 'application/octet-stream' will be used. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. @@ -108,6 +113,8 @@ module ActionController #:nodoc: private def send_file_headers!(options) + type_provided = options.has_key?(:type) + options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) [:type, :disposition].each do |arg| raise ArgumentError, ":#{arg} option required" if options[arg].nil? @@ -123,6 +130,10 @@ module ActionController #:nodoc: raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension self.content_type = extension else + if !type_provided && options[:filename] + # If type wasn't provided, try guessing from file extension. + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.tr('.','')) || content_type + end self.content_type = content_type end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 55c650df6c..dee7eb1ec8 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -43,6 +43,7 @@ module ActionController # # The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an # integer, or a symbol representing the downcased, underscored and symbolized description. + # Note that the status code must be a 3xx HTTP code, or redirection will not occur. # # It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used the flash names # +alert+ and +notice+ as well as a general purpose +flash+ bucket. diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index d2ba052c8d..f0c29825ba 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,7 +4,6 @@ require "action_dispatch/railtie" require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/paths" -require "sprockets/railtie" module ActionController class Railtie < Rails::Railtie diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d14bb666cc..45bb641aee 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -210,7 +210,7 @@ module ActionController DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS def initialize(session = {}) - @env, @by = nil, nil + super(nil, nil) replace(session.stringify_keys) @loaded = true end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 1e43104f0a..505d5560b1 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -3,9 +3,10 @@ require 'active_support/memoizable' module ActionDispatch module Http class Headers < ::Hash - extend ActiveSupport::Memoizable + @@env_cache = Hash.new { |h,k| h[k] = "HTTP_#{k.upcase.gsub(/-/, '_')}" } def initialize(*args) + if args.size == 1 && args[0].is_a?(Hash) super() update(args[0]) @@ -25,9 +26,8 @@ module ActionDispatch private # Converts a HTTP header name to an environment variable name. def env_name(header_name) - "HTTP_#{header_name.upcase.gsub(/-/, '_')}" + @@env_cache[header_name] end - memoize :env_name end end end diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 68f37d2f65..3da4f91051 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -7,6 +7,15 @@ Mime::Type.register "text/javascript", :js, %w( application/javascript applicati Mime::Type.register "text/css", :css Mime::Type.register "text/calendar", :ics Mime::Type.register "text/csv", :csv + +Mime::Type.register "image/png", :png, [], %w(png) +Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe) +Mime::Type.register "image/gif", :gif, [], %w(gif) +Mime::Type.register "image/bmp", :bmp, [], %w(bmp) +Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) + +Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) + Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) Mime::Type.register "application/rss+xml", :rss Mime::Type.register "application/atom+xml", :atom @@ -19,5 +28,8 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.json.org/JSONRequest.html Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) +Mime::Type.register "application/pdf", :pdf, [], %w(pdf) +Mime::Type.register "application/zip", :zip, [], %w(zip) + # Create Mime::ALL but do not add it to the SET. Mime::ALL = Mime::Type.new("*/*", :all, []) diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 37effade4f..a15ad28f16 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -4,7 +4,7 @@ module ActionDispatch attr_accessor :original_filename, :content_type, :tempfile, :headers def initialize(hash) - @original_filename = hash[:filename] + @original_filename = encode_filename(hash[:filename]) @content_type = hash[:type] @headers = hash[:head] @tempfile = hash[:tempfile] @@ -30,6 +30,16 @@ module ActionDispatch def size @tempfile.size end + + private + def encode_filename(filename) + # Encode the filename in the utf8 encoding, unless it is nil or we're in 1.8 + if "ruby".encoding_aware? && filename + filename.force_encoding("UTF-8").encode! + else + filename + end + end end module Upload diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 74c090f260..1dcd83ceb5 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -161,7 +161,7 @@ module ActionDispatch # Consider the following route, which you will find commented out at the # bottom of your generated <tt>config/routes.rb</tt>: # - # match ':controller(/:action(/:id(.:format)))' + # match ':controller(/:action(/:id))(.:format)' # # This route states that it expects requests to consist of a # <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 70ea419e81..65895590bf 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -87,7 +87,7 @@ module ActionDispatch raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" end end - end + end # match "account/overview" def using_match_shorthand?(path, options) diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 367c295cf5..f668b81b45 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -39,7 +39,7 @@ module ActionDispatch # # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary) def fixture_file_upload(path, mime_type = nil, binary = false) - fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path) + fixture_path = self.class.fixture_path if self.class.respond_to?(:fixture_path) Rack::Test::UploadedFile.new("#{fixture_path}#{path}", mime_type, binary) end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 78eddb7530..d7229419a9 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -30,6 +30,7 @@ module ActionView extend ActiveSupport::Autoload eager_autoload do + autoload :AssetPaths autoload :Base autoload :Context autoload :Helpers diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb new file mode 100644 index 0000000000..4dd02755d3 --- /dev/null +++ b/actionpack/lib/action_view/asset_paths.rb @@ -0,0 +1,145 @@ +require 'active_support/core_ext/file' + +module ActionView + class AssetPaths #:nodoc: + attr_reader :config, :controller + + def initialize(config, controller = nil) + @config = config + @controller = controller + end + + # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. + # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL + # roots. Rewrite the asset path for cache-busting asset ids. Include + # asset host, if configured, with the correct request protocol. + # + # When include_host is true and the asset host does not specify the protocol + # the protocol parameter specifies how the protocol will be added. + # When :relative (default), the protocol will be determined by the client using current protocol + # When :request, the protocol will be the request protocol + # Otherwise, the protocol is used (E.g. :http, :https, etc) + def compute_public_path(source, dir, ext = nil, include_host = true, protocol = nil) + source = source.to_s + return source if is_uri?(source) + + source = rewrite_extension(source, dir, ext) if ext + source = rewrite_asset_path(source, dir) + source = rewrite_relative_url_root(source, relative_url_root) if has_request? + source = rewrite_host_and_protocol(source, protocol) if include_host + source + end + + # Return the filesystem path for the source + def compute_source_path(source, dir, ext) + source = rewrite_extension(source, dir, ext) if ext + File.join(config.assets_dir, dir, source) + end + + def is_uri?(path) + path =~ %r{^[-a-z]+://|^cid:|^//} + end + + private + + def rewrite_extension(source, dir, ext) + raise NotImplementedError + end + + def rewrite_asset_path(source, path = nil) + raise NotImplementedError + end + + def rewrite_relative_url_root(source, relative_url_root) + relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source + end + + def has_request? + controller.respond_to?(:request) + end + + def rewrite_host_and_protocol(source, protocol = nil) + host = compute_asset_host(source) + if host && !is_uri?(host) + if (protocol || default_protocol) == :request && !has_request? + host = nil + else + host = "#{compute_protocol(protocol)}#{host}" + end + end + host.nil? ? source : "#{host}#{source}" + end + + def compute_protocol(protocol) + protocol ||= default_protocol + case protocol + when :relative + "//" + when :request + unless @controller + invalid_asset_host!("The protocol requested was :request. Consider using :relative instead.") + end + @controller.request.protocol + else + "#{protocol}://" + end + end + + def default_protocol + protocol = @config.action_controller.default_asset_host_protocol if @config.action_controller.present? + protocol ||= @config.default_asset_host_protocol + protocol || (has_request? ? :request : :relative) + end + + def invalid_asset_host!(help_message) + raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}" + end + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), + # or the value returned from invoking call on an object responding to call + # (proc or otherwise). + def compute_asset_host(source) + if host = asset_host_config + if host.respond_to?(:call) + args = [source] + arity = arity_of(host) + if arity > 1 && !has_request? + invalid_asset_host!("Remove the second argument to your asset_host Proc if you do not need the request.") + end + args << current_request if (arity > 1 || arity < 0) && has_request? + host.call(*args) + else + (host =~ /%d/) ? host % (source.hash % 4) : host + end + end + end + + def relative_url_root + config = controller.config if controller.respond_to?(:config) + config ||= config.action_controller if config.action_controller.present? + config ||= config + config.relative_url_root + end + + def asset_host_config + if config.action_controller.present? + config.action_controller.asset_host + else + config.asset_host + end + end + + # Returns the current request if one exists. + def current_request + controller.request if has_request? + end + + # Returns the arity of a callable + def arity_of(callable) + callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity + end + + end +end diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb index 9a99c3cf52..fae2e4fc1c 100644 --- a/actionpack/lib/action_view/helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -1,83 +1,7 @@ -require 'active_support/core_ext/file' +ActiveSupport::Deprecation.warn "ActionView::Helpers::AssetPaths is deprecated. Please use ActionView::AssetPaths instead." module ActionView module Helpers - - class AssetPaths #:nodoc: - attr_reader :config, :controller - - def initialize(config, controller) - @config = config - @controller = controller - end - - # Add the extension +ext+ if not present. Return full or scheme-relative URLs otherwise untouched. - # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL - # roots. Rewrite the asset path for cache-busting asset ids. Include - # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) - source = source.to_s - return source if is_uri?(source) - - source = rewrite_extension(source, dir, ext) if ext - source = rewrite_asset_path(source, dir) - - if controller && include_host - has_request = controller.respond_to?(:request) - source = rewrite_host_and_protocol(source, has_request) - end - - source - end - - def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:|^//} - end - - private - - def rewrite_extension(source, dir, ext) - raise NotImplementedError - end - - def rewrite_asset_path(source, path = nil) - raise NotImplementedError - end - - def rewrite_relative_url_root(source, relative_url_root) - relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source - end - - def rewrite_host_and_protocol(source, has_request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request - host = compute_asset_host(source) - if has_request && host && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" - end - "#{host}#{source}" - end - - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking call on an object responding to call - # (proc or otherwise). - def compute_asset_host(source) - if host = config.asset_host - if host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end - end - end - end - + AssetPaths = ::ActionView::AssetPaths end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 7970176d37..0c3f011c92 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -347,7 +347,7 @@ module ActionView src = options[:src] = path_to_image(source) unless src =~ /^cid:/ - options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize } + options[:alt] = options.fetch(:alt){ image_alt(src) } end if size = options.delete(:size) @@ -362,6 +362,10 @@ module ActionView tag("img", options) end + def image_alt(src) + File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').capitalize + end + # Returns an html video tag for the +sources+. If +sources+ is a string, # a single video tag will be returned. If +sources+ is an array, a video # tag with nested source tags for each source will be returned. The diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index e4662a2919..3c05173a1b 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -60,12 +60,16 @@ module ActionView private - def path_to_asset(source, include_host = true) - asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host) + def path_to_asset(source, include_host = true, protocol = nil) + asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host, protocol) + end + + def path_to_asset_source(source) + asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension) end def compute_paths(*args) - expand_sources(*args).collect { |source| asset_paths.compute_public_path(source, asset_name.pluralize, extension, false) } + expand_sources(*args).collect { |source| path_to_asset_source(source) } end def expand_sources(sources, recursive) @@ -92,7 +96,7 @@ module ActionView def ensure_sources!(sources) sources.each do |source| - asset_file_path!(path_to_asset(source, false)) + asset_file_path!(path_to_asset_source(source)) end end @@ -123,19 +127,14 @@ module ActionView # Set mtime to the latest of the combined files to allow for # consistent ETag without a shared filesystem. - mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max + mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max File.utime(mt, mt, joined_asset_path) end - def asset_file_path(path) - File.join(config.assets_dir, path.split('?').first) - end - - def asset_file_path!(path, error_if_file_is_uri = false) - if asset_paths.is_uri?(path) + def asset_file_path!(absolute_path, error_if_file_is_uri = false) + if asset_paths.is_uri?(absolute_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 end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 2d49823412..12a304b395 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,11 +1,10 @@ require 'active_support/core_ext/file' -require 'action_view/helpers/asset_paths' module ActionView module Helpers module AssetTagHelper - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: + class AssetPaths < ::ActionView::AssetPaths #:nodoc: # You can enable or disable the asset tag ids cache. # With the cache enabled, the asset tag helper methods will make fewer # expensive file system calls (the default implementation checks the file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index e4f11c9bc7..8c25d38bbd 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -16,7 +16,8 @@ module ActionView end def asset_tag(source, options) - tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source)) }.merge(options), false, false) + # We force the :request protocol here to avoid a double-download bug in IE7 and IE8 + tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => ERB::Util.html_escape(path_to_asset(source, true, :request)) }.merge(options), false, false) end def custom_dir @@ -52,7 +53,7 @@ module ActionView # If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs). # Full paths from the document root will be passed through. # Used internally by +stylesheet_link_tag+ to build the stylesheet path. - # + # # ==== Examples # stylesheet_path "style" # => /stylesheets/style.css # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css @@ -60,11 +61,7 @@ module ActionView # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source) - if config.use_sprockets - asset_path(source, 'css') - else - asset_paths.compute_public_path(source, 'stylesheets', 'css') - end + asset_paths.compute_public_path(source, 'stylesheets', 'css', true, :request) end alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index b57617b3d1..f81ce3e31c 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -51,12 +51,10 @@ module ActionView # This dance is needed because Builder can't use capture pos = output_buffer.length yield - if output_buffer.is_a?(ActionView::OutputBuffer) - safe_output_buffer = output_buffer.to_str - fragment = safe_output_buffer.slice!(pos..-1) - self.output_buffer = ActionView::OutputBuffer.new(safe_output_buffer) - else - fragment = output_buffer.slice!(pos..-1) + output_safe = output_buffer.html_safe? + fragment = output_buffer.slice!(pos..-1) + if output_safe + self.output_buffer = output_buffer.html_safe end controller.write_fragment(name, fragment, options) end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 56e3af683b..6a724749f4 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -621,7 +621,6 @@ module ActionView end class DateTimeSelector #:nodoc: - extend ActiveSupport::Memoizable include ActionView::Helpers::TagHelper DEFAULT_PREFIX = 'date'.freeze @@ -786,11 +785,12 @@ module ActionView # Returns translated month names, but also ensures that a custom month # name array has a leading nil element. def month_names - month_names = @options[:use_month_names] || translated_month_names - month_names.unshift(nil) if month_names.size < 13 - month_names + @month_names ||= begin + month_names = @options[:use_month_names] || translated_month_names + month_names.unshift(nil) if month_names.size < 13 + month_names + end end - memoize :month_names # Returns translated month names. # => [nil, "January", "February", "March", @@ -825,9 +825,8 @@ module ActionView end def date_order - @options[:order] || translated_date_order + @date_order ||= @options[:order] || translated_date_order end - memoize :date_order def translated_date_order I18n.translate(:'date.order', :locale => @options[:locale]) || [] diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 7ed949504d..0ef2357368 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -219,9 +219,9 @@ module ActionView # <% end %> # # If you have an object that needs to be represented as a different - # parameter, like a Client that acts as a Person: + # parameter, like a Person that acts as a Client: # - # <%= form_for(@post, :as => :client) do |f| %> + # <%= form_for(@person, :as => :client) do |f| %> # ... # <% end %> # @@ -290,7 +290,7 @@ module ActionView # # Example: # - # <%= form(@post) do |f| %> + # <%= form_for(@post) do |f| %> # <% f.fields_for(:comments, :include_id => false) do |cf| %> # ... # <% end %> @@ -517,6 +517,18 @@ module ActionView # end # end # + # Note that the <tt>projects_attributes=</tt> writer method is in fact + # required for fields_for to correctly identify <tt>:projects</tt> as a + # collection, and the correct indices to be set in the form markup. + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # # This model can now be used with a nested fields_for. The block given to # the nested fields_for call will be repeated for each instance in the # collection: @@ -568,14 +580,6 @@ module ActionView # ... # <% end %> # - # When projects is already an association on Person you can use - # +accepts_nested_attributes_for+ to define the writer method for you: - # - # class Person < ActiveRecord::Base - # has_many :projects - # accepts_nested_attributes_for :projects - # end - # # If you want to destroy any of the associated models through the # form, you have to enable it first using the <tt>:allow_destroy</tt> # option for +accepts_nested_attributes_for+: @@ -1237,7 +1241,7 @@ module ActionView end def fields_for(record_name, record_object = nil, fields_options = {}, &block) - fields_options, record_object = record_object, nil if record_object.is_a?(Hash) + fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] fields_options[:parent_builder] = self diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 97b9d0a578..3dc6d65432 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -574,43 +574,26 @@ module ActionView include FormOptionsHelper def to_select_tag(choices, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - selected_value = options.has_key?(:selected) ? options[:selected] : value - disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil - select_content_tag(add_options(options_for_select(choices, :selected => selected_value, :disabled => disabled_value), options, selected_value), html_options) + selected_value = options.has_key?(:selected) ? options[:selected] : value(object) + select_content_tag(options_for_select(choices, :selected => selected_value, :disabled => options[:disabled]), options, html_options) end def to_collection_select_tag(collection, value_method, text_method, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - disabled_value = options.has_key?(:disabled) ? options[:disabled] : nil - selected_value = options.has_key?(:selected) ? options[:selected] : value + selected_value = options.has_key?(:selected) ? options[:selected] : value(object) select_content_tag( - add_options(options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => disabled_value), options, value), html_options + options_from_collection_for_select(collection, value_method, text_method, :selected => selected_value, :disabled => options[:disabled]), options, html_options ) end def to_grouped_collection_select_tag(collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) select_content_tag( - add_options(option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value), options, value), html_options + option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, value(object)), options, html_options ) end def to_time_zone_select_tag(priority_zones, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) select_content_tag( - add_options( - time_zone_options_for_select(value || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), - options, value - ), html_options + time_zone_options_for_select(value(object) || options[:default], priority_zones, options[:model] || ActiveSupport::TimeZone), options, html_options ) end @@ -626,8 +609,10 @@ module ActionView option_tags.html_safe end - def select_content_tag(content, html_options) - select = content_tag("select", content, html_options) + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + select = content_tag("select", add_options(option_tags, options, value(object)), html_options) if html_options["multiple"] tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select else diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 8f97eb7d75..ae71ade588 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -255,7 +255,8 @@ module ActionView # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false) # # => "<p><span>I'm allowed!</span> It's true.</p>" def simple_format(text, html_options={}, options={}) - text = ''.html_safe if text.nil? + text = '' if text.nil? + text = text.dup if text.frozen? start_tag = tag('p', html_options, true) text = sanitize(text) unless options[:sanitize] == false text = text.to_str @@ -263,7 +264,7 @@ module ActionView text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br text.insert 0, start_tag - text = ActiveSupport::SafeBuffer.new(text).safe_concat("</p>") + text.html_safe.safe_concat("</p>") end # Creates a Cycle object whose _to_s_ method cycles through elements of an @@ -280,7 +281,7 @@ module ActionView # @items = [1,2,3,4] # <table> # <% @items.each do |item| %> - # <tr class="<%= cycle("even", "odd") -%>"> + # <tr class="<%= cycle("odd", "even") -%>"> # <td>item</td> # </tr> # <% end %> diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index fd8fe417d0..26b6e8b599 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -5,7 +5,7 @@ module I18n class ExceptionHandler include Module.new { def call(exception, locale, key, options) - exception.is_a?(MissingTranslation) ? super.html_safe : super + exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super end } end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 489b96856d..d70ae4196b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -55,9 +55,9 @@ module ActionView # # ==== Relying on named routes # - # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter, - # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing - # a Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). # # ==== Examples @@ -112,13 +112,13 @@ module ActionView end end - # Creates a link tag of the given +name+ using a URL created by the set - # of +options+. See the valid options in the documentation for - # +url_for+. It's also possible to pass a string instead - # of an options hash to get a link tag that uses the value of the string as the - # href for the link, or use <tt>:back</tt> to link to the referrer - a JavaScript back - # link will be used in place of a referrer if none exists. If +nil+ is passed as - # a name, the link itself will become the name. + # Creates a link tag of the given +name+ using a URL created by the set of +options+. + # See the valid options in the documentation for +url_for+. It's also possible to + # pass a String instead of an options hash, which generates a link tag that uses the + # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead + # of an options hash will generate a link to the referrer (a JavaScript back link + # will be used in place of a referrer if none exists). If +nil+ is passed as the name + # the value of the link itself will become the name. # # ==== Signatures # @@ -619,7 +619,9 @@ module ActionView end def add_method_to_attributes!(html_options, method) - html_options["rel"] = "nofollow" if method.to_s.downcase != "get" + if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/ + html_options["rel"] = "#{html_options["rel"]} nofollow".strip + end html_options["data-method"] = method end diff --git a/actionpack/lib/action_view/log_subscriber.rb b/actionpack/lib/action_view/log_subscriber.rb index 29ffbd6fdd..bf90d012bf 100644 --- a/actionpack/lib/action_view/log_subscriber.rb +++ b/actionpack/lib/action_view/log_subscriber.rb @@ -4,7 +4,7 @@ module ActionView # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber def render_template(event) - message = "Rendered #{from_rails_root(event.payload[:identifier])}" + message = " Rendered #{from_rails_root(event.payload[:identifier])}" message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] message << (" (%.1fms)" % event.duration) info(message) diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake new file mode 100644 index 0000000000..0236350576 --- /dev/null +++ b/actionpack/lib/sprockets/assets.rake @@ -0,0 +1,26 @@ +namespace :assets do + desc "Compile all the assets named in config.assets.precompile" + task :precompile do + if ENV["RAILS_GROUPS"].to_s.empty? + ENV["RAILS_GROUPS"] = "assets" + Kernel.exec $0, *ARGV + else + Rake::Task["environment"].invoke + Sprockets::Helpers::RailsHelper + + assets = Rails.application.config.assets.precompile + Rails.application.assets.precompile(*assets) + end + end + + desc "Remove compiled assets" + task :clean => :environment do + assets = Rails.application.config.assets + public_asset_path = Rails.public_path + assets.prefix + file_list = FileList.new("#{public_asset_path}/**/*") + file_list.each do |file| + rm_rf file + rm_rf "#{file}.gz" + end + end +end diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb index 0b6bd8ca40..63820cc76c 100644 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ b/actionpack/lib/sprockets/helpers/rails_helper.rb @@ -1,5 +1,4 @@ -require "action_view/helpers/asset_paths" -require "action_view/helpers/asset_tag_helper" +require "action_view/helpers" module Sprockets module Helpers @@ -11,6 +10,12 @@ module Sprockets @asset_paths ||= begin config = self.config if respond_to?(:config) controller = self.controller if respond_to?(:controller) + config ||= Rails.application.config + if config.action_controller.present? + config.action_controller.default_asset_host_protocol ||= :relative + else + config.default_asset_host_protocol ||= :relative + end RailsHelper::AssetPaths.new(config, controller) end end @@ -51,7 +56,7 @@ module Sprockets 'rel' => "stylesheet", 'type' => "text/css", 'media' => "screen", - 'href' => asset_path(source, 'css', body) + 'href' => asset_path(source, 'css', body, :request) }.merge(options.stringify_keys) tag 'link', tag_options @@ -59,21 +64,26 @@ module Sprockets end.join("\n").html_safe end + def asset_path(source, default_ext = nil, body = false, protocol = nil) + source = source.logical_path if source.respond_to?(:logical_path) + path = asset_paths.compute_public_path(source, 'assets', default_ext, true, protocol) + body ? "#{path}?body=1" : path + end + private def debug_assets? params[:debug_assets] == '1' || params[:debug_assets] == 'true' end - def asset_path(source, default_ext = nil, body = false) - source = source.logical_path if source.respond_to?(:logical_path) - path = asset_paths.compute_public_path(source, Rails.application.config.assets.prefix, default_ext, true) - body ? "#{path}?body=1" : path - end + class AssetPaths < ::ActionView::AssetPaths #:nodoc: + def compute_public_path(source, dir, ext=nil, include_host=true, protocol = nil) + super(source, Rails.application.config.assets.prefix, ext, include_host, protocol) + end - class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: - def compute_public_path(source, dir, ext=nil, include_host=true) - super(source, Rails.application.config.assets.prefix, ext, include_host) + # Return the filesystem path for the source + def compute_source_path(source, ext) + asset_for(source, ext) end def asset_for(source, ext) @@ -105,7 +115,7 @@ module Sprockets # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available def performing_caching? - @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching + config.action_controller.present? ? config.action_controller.perform_caching : config.perform_caching end end end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb index 4b497d142d..d1e97c594e 100644 --- a/actionpack/lib/sprockets/railtie.rb +++ b/actionpack/lib/sprockets/railtie.rb @@ -1,16 +1,12 @@ module Sprockets autoload :Helpers, "sprockets/helpers" + # TODO: Get rid of config.assets.enabled class Railtie < ::Rails::Railtie - def self.using_coffee? - require 'coffee-script' - defined?(CoffeeScript) - rescue LoadError - false + rake_tasks do + load "sprockets/assets.rake" end - config.app_generators.javascript_engine :coffee if using_coffee? - # Configure ActionController to use sprockets. initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app| ActiveSupport.on_load(:action_controller) do @@ -54,12 +50,25 @@ module Sprockets env = Sprockets::Environment.new(app.root.to_s) env.static_root = File.join(app.root.join("public"), assets.prefix) - env.paths.concat assets.paths + + if env.respond_to?(:append_path) + assets.paths.each { |path| env.append_path(path) } + else + env.paths.concat assets.paths + end env.logger = Rails.logger - env.js_compressor = expand_js_compressor(assets.js_compressor) - env.css_compressor = expand_css_compressor(assets.css_compressor) + if env.respond_to?(:cache) + env.cache = Rails.cache + end + + if assets.compress + # temporarily hardcode default JS compressor to uglify. Soon, it will work + # the same as SCSS, where a default plugin sets the default. + env.js_compressor = expand_js_compressor(assets.js_compressor || :uglifier) + env.css_compressor = expand_css_compressor(assets.css_compressor) + end env end diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 9e44e8e088..d5e3da4d88 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -530,6 +530,11 @@ class FilterTest < ActionController::TestCase assert sweeper.before(TestController.new) end + def test_after_method_of_sweeper_should_always_return_nil + sweeper = ActionController::Caching::Sweeper.send(:new) + assert_nil sweeper.after(TestController.new) + end + def test_non_yielding_around_filters_not_returning_false_do_not_raise controller = NonYieldingAroundFilterController.new controller.instance_variable_set "@filter_return_value", true diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index c7c8360ae6..8f885ff28e 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -138,6 +138,25 @@ class SendFileTest < ActionController::TestCase @controller.headers = {} assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) } end + + def test_send_file_headers_guess_type_from_extension + { + 'image.png' => 'image/png', + 'image.jpeg' => 'image/jpeg', + 'image.jpg' => 'image/jpeg', + 'image.tif' => 'image/tiff', + 'image.gif' => 'image/gif', + 'movie.mpg' => 'video/mpeg', + 'file.zip' => 'application/zip', + 'file.unk' => 'application/octet-stream', + 'zip' => 'application/octet-stream' + }.each do |filename,expected_type| + options = { :filename => filename } + @controller.headers = {} + @controller.send(:send_file_headers!, options) + assert_equal expected_type, @controller.content_type + end + end %w(file data).each do |method| define_method "test_send_#{method}_status" do diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 6265e78030..043d44500a 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -649,6 +649,13 @@ XML end + def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case + TestTest.stubs(:fixture_path).returns(FILES_DIR) + + uploaded_file = fixture_file_upload('/mona_lisa.jpg', 'image/png') + assert_equal File.open("#{FILES_DIR}/mona_lisa.jpg", READ_PLAIN).read, uploaded_file.read + end + def test_test_uploaded_file_with_binary filename = 'mona_lisa.jpg' path = "#{FILES_DIR}/#{filename}" diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index 11cf68fdb3..851fb59d51 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -52,7 +52,7 @@ class MimeTypeTest < ActiveSupport::TestCase test "parse application with trailing star" do accept = "application/*" - expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF] + expect = [Mime::HTML, Mime::JS, Mime::XML, Mime::RSS, Mime::ATOM, Mime::YAML, Mime::URL_ENCODED_FORM, Mime::JSON, Mime::PDF, Mime::ZIP] parsed = Mime::Type.parse(accept) assert_equal expect, parsed end @@ -86,12 +86,12 @@ class MimeTypeTest < ActiveSupport::TestCase test "custom type" do begin - Mime::Type.register("image/gif", :gif) + Mime::Type.register("image/foo", :foo) assert_nothing_raised do - assert_equal Mime::GIF, Mime::SET.last + assert_equal Mime::FOO, Mime::SET.last end ensure - Mime::Type.unregister(:gif) + Mime::Type.unregister(:FOO) end end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index e2a7f1bad7..7e4a1519fb 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -12,6 +12,13 @@ module ActionDispatch uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) assert_equal 'foo', uf.original_filename end + + if "ruby".encoding_aware? + def test_filename_should_be_in_utf_8 + uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) + assert_equal "UTF-8", uf.original_filename.encoding.to_s + end + end def test_content_type uf = Http::UploadedFile.new(:type => 'foo', :tempfile => Object.new) diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 67baf369e2..cbef74f992 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -170,6 +170,17 @@ class Author < Comment def post_attributes=(attributes); end end +class HashBackedAuthor < Hash + extend ActiveModel::Naming + include ActiveModel::Conversion + + def persisted?; false; end + + def name + "hash backed author" + end +end + module Blog def self._railtie self diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 2abc806e97..df61901b44 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -424,6 +424,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_image_alt + [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| + assert_equal 'Rails', image_alt("#{prefix}rails.png") + assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") + assert_equal 'Avatar-0000', image_alt("#{prefix}avatar-0000.png") + end + end + def test_image_tag ImageLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -854,7 +862,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = 'http://a0.example.com' + @controller.config.asset_host = 'a0.example.com' config.perform_caching = true assert_dom_equal( @@ -964,7 +972,7 @@ class AssetTagHelperTest < ActionView::TestCase def test_caching_stylesheet_link_tag_when_caching_on_with_proc_asset_host ENV["RAILS_ASSET_ID"] = "" - @controller.config.asset_host = Proc.new { |source| "http://a#{source.length}.example.com" } + @controller.config.asset_host = Proc.new { |source| "a#{source.length}.example.com" } config.perform_caching = true assert_equal '/stylesheets/styles.css'.length, 23 @@ -1091,13 +1099,23 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_compute_proper_path_with_asset_host - @controller.config.asset_host = "http://assets.example.com" + @controller.config.asset_host = "assets.example.com" assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) - assert_dom_equal(%(http://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) - assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='http://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='http://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="http://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) + assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + end + + def test_should_compute_proper_path_with_asset_host_and_default_protocol + @controller.config.asset_host = "assets.example.com" + @controller.config.default_asset_host_protocol = :request + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) + assert_dom_equal(%(<img alt="Mouse" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse.png" />), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end def test_should_ignore_asset_host_on_complete_url @@ -1115,12 +1133,12 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')) end - def test_asset_host_without_protocol_should_use_request_protocol + def test_asset_host_without_protocol_should_be_protocol_relative @controller.config.asset_host = 'a.example.com' assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') end - def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present + def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present @controller.config.asset_host = 'a.example.com/files/go/here' assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 0507045ad2..bf65a9359b 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1689,6 +1689,22 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_nested_fields_for_with_hash_like_model + @author = HashBackedAuthor.new + + form_for(@post) do |f| + concat f.fields_for(:author, @author) { |af| + concat af.text_field(:name) + } + end + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />' + end + + assert_dom_equal expected, output_buffer + end + def test_fields_for output_buffer = fields_for(:post, @post) do |f| concat f.text_field(:title) diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index b1317d0a35..6dc9a2a743 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -31,18 +31,21 @@ class SprocketsHelperTest < ActionView::TestCase Rails.stubs(:application).returns(application) application.stubs(:config).returns(config) application.stubs(:assets).returns(@assets) - - config.perform_caching = true + @config = config + @config.action_controller ||= ActiveSupport::InheritableOptions.new + @config.perform_caching = true end def url_for(*args) "http://www.example.com" end - test "asset path" do + test "asset_path" do assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", asset_path("logo.png") + end + test "asset_path with root relative assets" do assert_equal "/images/logo", asset_path("/images/logo") assert_equal "/images/logo.gif", @@ -50,13 +53,73 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal "/dir/audio", asset_path("/dir/audio") - + end + + test "asset_path with absolute urls" do assert_equal "http://www.example.com/video/play", asset_path("http://www.example.com/video/play") assert_equal "http://www.example.com/video/play.mp4", asset_path("http://www.example.com/video/play.mp4") end + test "with a simple asset host the url should default to protocol relative" do + @controller.config.asset_host = "assets-%d.example.com" + assert_match %r{//assets-\d.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "with a simple asset host the url can be changed to use the request protocol" do + @controller.config.asset_host = "assets-%d.example.com" + @controller.config.default_asset_host_protocol = :request + assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "With a proc asset host that returns no protocol the url should be protocol relative" do + @controller.config.asset_host = Proc.new do |asset| + "assets-999.example.com" + end + assert_match %r{//assets-999.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "with a proc asset host that returns a protocol the url use it" do + @controller.config.asset_host = Proc.new do |asset| + "http://assets-999.example.com" + end + assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "stylesheets served with a controller in scope can access the request" do + config.asset_host = Proc.new do |asset, request| + assert_not_nil request + "http://assets-666.example.com" + end + assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png}, + asset_path("logo.png") + end + + test "stylesheets served without a controller in scope cannot access the request" do + remove_instance_variable("@controller") + @config.action_controller.asset_host = Proc.new do |asset, request| + fail "This should not have been called." + end + assert_raises ActionController::RoutingError do + asset_path("logo.png") + end + end + + test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do + remove_instance_variable("@controller") + @config.action_controller.asset_host = "assets-%d.example.com" + @config.action_controller.default_asset_host_protocol = :request + @config.action_controller.perform_caching = true + + assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", + asset_path("logo.png") + end + test "asset path with relative url root" do @controller.config.relative_url_root = "/collaboration/hieraki" assert_equal "/collaboration/hieraki/images/logo.gif", diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 5a43b5f864..f7c3986bb1 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -36,8 +36,8 @@ class TextHelperTest < ActionView::TestCase text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) - assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') - assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') + assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') + assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') end def test_simple_format_should_sanitize_input_when_sanitize_option_is_not_false @@ -48,6 +48,13 @@ class TextHelperTest < ActionView::TestCase assert_equal "<p><b> test with unsafe string </b><script>code!</script></p>", simple_format("<b> test with unsafe string </b><script>code!</script>", {}, :sanitize => false) end + def test_simple_format_should_not_change_the_frozen_text_passed + text = "<b>Ok</b><script>code!</script>" + text_clone = text.dup + simple_format(text.freeze) + assert_equal text_clone, text + end + def test_truncate_should_not_be_html_safe assert !truncate("Hello World!", :length => 12).html_safe? end diff --git a/actionpack/test/template/translation_helper_test.rb b/actionpack/test/template/translation_helper_test.rb index 9b5c6d127c..cd9f54e04c 100644 --- a/actionpack/test/template/translation_helper_test.rb +++ b/actionpack/test/template/translation_helper_test.rb @@ -38,11 +38,19 @@ class TranslationHelperTest < ActiveSupport::TestCase def test_returns_missing_translation_message_wrapped_into_span expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>' assert_equal expected, translate(:"translations.missing") + assert_equal true, translate(:"translations.missing").html_safe? end def test_returns_missing_translation_message_using_nil_as_rescue_format expected = 'translation missing: en.translations.missing' assert_equal expected, translate(:"translations.missing", :rescue_format => nil) + assert_equal false, translate(:"translations.missing", :rescue_format => nil).html_safe? + end + + def test_i18n_translate_defaults_to_nil_rescue_format + expected = 'translation missing: en.translations.missing' + assert_equal expected, I18n.translate(:"translations.missing") + assert_equal false, I18n.translate(:"translations.missing").html_safe? end def test_translation_returning_an_array diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 1e54f0174c..a70c02a429 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -245,6 +245,13 @@ class UrlHelperTest < ActiveSupport::TestCase ) end + def test_link_tag_using_post_javascript_and_rel + assert_dom_equal( + "<a href='http://www.example.com' data-method=\"post\" rel=\"example nofollow\">Hello</a>", + link_to("Hello", "http://www.example.com", :method => :post, :rel => 'example') + ) + end + def test_link_tag_using_post_javascript_and_confirm assert_dom_equal( "<a href=\"http://www.example.com\" data-method=\"post\" rel=\"nofollow\" data-confirm=\"Are you serious?\">Hello</a>", diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 3b412d3dd7..166cccf161 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -156,7 +156,7 @@ module ActiveModel rescue TypeError, NoMethodError end - changed_attributes[attr] = value + changed_attributes[attr] = value unless changed_attributes.include?(attr) end # Handle <tt>reset_*!</tt> for +method_missing+. diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index a7b4706906..3f9feb7631 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/array/wrap' require 'active_model/mass_assignment_security/permission_set' require 'active_model/mass_assignment_security/sanitizer' @@ -110,8 +111,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._protected_attributes = protected_attributes_configs.dup - self._protected_attributes[role] = self.protected_attributes(role) + args + self._protected_attributes = protected_attributes_configs.dup + + Array.wrap(role).each do |name| + self._protected_attributes[name] = self.protected_attributes(name) + args + end self._active_authorizer = self._protected_attributes end @@ -169,8 +173,11 @@ module ActiveModel options = args.extract_options! role = options[:as] || :default - self._accessible_attributes = accessible_attributes_configs.dup - self._accessible_attributes[role] = self.accessible_attributes(role) + args + self._accessible_attributes = accessible_attributes_configs.dup + + Array.wrap(role).each do |name| + self._accessible_attributes[name] = self.accessible_attributes(name) + args + end self._active_authorizer = self._accessible_attributes end diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb index ee43a6694f..bb0526adc3 100644 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb @@ -19,7 +19,7 @@ module ActiveModel removed_keys = attributes.keys - sanitized_attributes.keys process_removed_attributes(removed_keys) if removed_keys.any? end - + def process_removed_attributes(attrs) raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten" end diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index 858ae9cb69..98244a6290 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -106,4 +106,13 @@ class DirtyTest < ActiveModel::TestCase assert_equal [nil, "Jericho Cane"], @model.previous_changes['name'] end + test "changing the same attribute multiple times retains the correct original value" do + @model.name = "Otto" + @model.save + @model.name = "DudeFella ManGuy" + @model.name = "Mr. Manfredgensonton" + assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change + assert_equal @model.name_was, "Otto" + end + end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index a778240827..be07e59a2f 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -43,6 +43,20 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_attributes_accessible_with_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true)) + assert_equal expected, sanitized + end + + def test_attributes_accessible_with_admin_role_when_roles_given_as_array + user = Account.new + expected = { "name" => "John Smith", "email" => "john@smith.com", "admin" => true } + sanitized = user.sanitize_for_mass_assignment(expected.merge("super_powers" => true), :admin) + assert_equal expected, sanitized + end + def test_attributes_protected_by_default firm = Firm.new expected = { } diff --git a/activemodel/test/models/mass_assignment_specific.rb b/activemodel/test/models/mass_assignment_specific.rb index 53b37369ff..1d123fa58c 100644 --- a/activemodel/test/models/mass_assignment_specific.rb +++ b/activemodel/test/models/mass_assignment_specific.rb @@ -20,6 +20,14 @@ class Person public :sanitize_for_mass_assignment end +class Account + include ActiveModel::MassAssignmentSecurity + attr_accessible :name, :email, :as => [:default, :admin] + attr_accessible :admin, :as => :admin + + public :sanitize_for_mass_assignment +end + class Firm include ActiveModel::MassAssignmentSecurity @@ -65,4 +73,4 @@ end class TightDescendant < TightPerson attr_accessible :phone_number attr_accessible :super_powers, :as => :admin -end
\ No newline at end of file +end diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index b7e23faa09..aff0c51829 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -21,6 +21,6 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('arel', '~> 2.1.1') + s.add_dependency('arel', '~> 2.1.3') s.add_dependency('tzinfo', '~> 0.3.27') end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 08cccf1da2..5a62a29ddb 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -428,10 +428,6 @@ module ActiveRecord #:nodoc: class_attribute :default_scopes, :instance_writer => false self.default_scopes = [] - # Boolean flag to prevent infinite recursion when evaluating default scopes - class_attribute :apply_default_scope, :instance_writer => false - self.apply_default_scope = true - # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. class_attribute :serialized_attributes @@ -713,6 +709,12 @@ module ActiveRecord #:nodoc: connection_pool.columns_hash[table_name] end + # Returns a hash where the keys are column names and the values are + # default values when instantiating the AR object for this table. + def column_defaults + connection_pool.column_defaults[table_name] + end + # Returns an array of column names as strings. def column_names @column_names ||= columns.map { |column| column.name } @@ -1208,11 +1210,11 @@ MSG end def current_scope #:nodoc: - Thread.current[:"#{self}_current_scope"] + Thread.current["#{self}_current_scope"] end def current_scope=(scope) #:nodoc: - Thread.current[:"#{self}_current_scope"] = scope + Thread.current["#{self}_current_scope"] = scope end # Use this macro in your model to set a default scope for all operations on @@ -1265,11 +1267,11 @@ MSG self.default_scopes = default_scopes + [scope] end - # The apply_default_scope flag is used to prevent an infinite recursion situation where + # The @ignore_default_scope flag is used to prevent an infinite recursion situation where # a default scope references a scope which has a default scope which references a scope... def build_default_scope #:nodoc: - return unless apply_default_scope - self.apply_default_scope = false + return if defined?(@ignore_default_scope) && @ignore_default_scope + @ignore_default_scope = true if method(:default_scope).owner != Base.singleton_class default_scope @@ -1285,7 +1287,7 @@ MSG end end ensure - self.apply_default_scope = true + @ignore_default_scope = false end # Returns the class type of the record using the current module as a prefix. So descendants of @@ -1531,6 +1533,7 @@ MSG @marked_for_destruction = false @previously_changed = {} @changed_attributes = {} + @relation = nil ensure_proper_type set_serialized_attributes @@ -1539,9 +1542,8 @@ MSG assign_attributes(attributes, options) if attributes - result = yield self if block_given? + yield self if block_given? run_callbacks :initialize - result end # Populate +coder+ with attributes about this record that should be @@ -1572,6 +1574,7 @@ MSG # post.title # => 'hello world' def init_with(coder) @attributes = coder['attributes'] + @relation = nil set_serialized_attributes @@ -1808,6 +1811,15 @@ MSG @attributes.frozen? end + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + self.to_key <=> other_object.to_key + else + nil + end + end + # Backport dup from 1.9 so that initialize_dup() gets called unless Object.respond_to?(:initialize_dup) def dup # :nodoc: @@ -1888,10 +1900,23 @@ MSG private + # Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements + # of the array, and then rescues from the possible NoMethodError. If those elements are + # ActiveRecord::Base's, then this triggers the various method_missing's that we have, + # which significantly impacts upon performance. + # + # So we can avoid the method_missing hit by explicitly defining #to_ary as nil here. + # + # See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/ + def to_ary # :nodoc: + nil + end + def set_serialized_attributes - (@attributes.keys & self.class.serialized_attributes.keys).each do |key| - coder = self.class.serialized_attributes[key] - @attributes[key] = coder.load @attributes[key] + sattrs = self.class.serialized_attributes + + sattrs.each do |key, coder| + @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) end end @@ -1901,8 +1926,9 @@ MSG # do Reply.new without having to set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. # No such attribute would be set for objects of the Message class in that example. def ensure_proper_type - unless self.class.descends_from_active_record? - write_attribute(self.class.inheritance_column, self.class.sti_name) + klass = self.class + if klass.finder_needs_type_condition? + write_attribute(klass.inheritance_column, klass.sti_name) end end @@ -2076,8 +2102,10 @@ MSG end def populate_with_current_scope_attributes - self.class.scoped.scope_for_create.each do |att,value| - respond_to?("#{att}=") && send("#{att}=", value) + return unless self.class.scope_attributes? + + self.class.scope_attributes.each do |att,value| + send("#{att}=", value) if respond_to?("#{att}=") end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 8ffd40f7e5..ddfdb05297 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -60,6 +60,7 @@ module ActiveRecord attr_accessor :automatic_reconnect attr_reader :spec, :connections attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :column_defaults # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, @@ -106,6 +107,12 @@ module ActiveRecord }] end + @column_defaults = Hash.new do |h, table_name| + h[table_name] = Hash[columns[table_name].map { |col| + [col.name, col.default] + }] + end + @primary_keys = Hash.new do |h, table_name| h[table_name] = with_connection do |conn| table_exists?(table_name) ? conn.primary_key(table_name) : 'id' @@ -119,6 +126,7 @@ module ActiveRecord with_connection do |conn| conn.tables.each { |table| @tables[table] = true } + @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name) end @tables.key? name @@ -132,6 +140,7 @@ module ActiveRecord def clear_cache! @columns.clear @columns_hash.clear + @column_defaults.clear @tables.clear end @@ -139,6 +148,7 @@ module ActiveRecord def clear_table_cache!(table_name) @columns.delete table_name @columns_hash.delete table_name + @column_defaults.delete table_name @primary_keys.delete table_name end @@ -425,6 +435,14 @@ module ActiveRecord @testing = testing end + def method_missing(method_sym, *arguments, &block) + @body.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @body.respond_to?(method_sym) + end + def each(&block) body.each(&block) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 70a8f6bb58..82f564e41d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -328,7 +328,7 @@ module ActiveRecord end # Checks to see if a column exists. See SchemaStatements#column_exists? - def column_exists?(column_name, type = nil, options = nil) + def column_exists?(column_name, type = nil, options = {}) @base.column_exists?(@table_name, column_name, type, options) end @@ -386,13 +386,13 @@ module ActiveRecord # Removes the given index from the table. # # ===== Examples - # ====== Remove the suppliers_name_index in the suppliers table - # t.remove_index :name - # ====== Remove the index named accounts_branch_id_index in the accounts table + # ====== Remove the index_table_name_on_column in the table_name table + # t.remove_index :column + # ====== Remove the index named index_table_name_on_branch_id in the table_name table # t.remove_index :column => :branch_id - # ====== Remove the index named accounts_branch_id_party_id_index in the accounts table + # ====== Remove the index named index_table_name_on_branch_id_and_party_id in the table_name table # t.remove_index :column => [:branch_id, :party_id] - # ====== Remove the index named by_branch_party in the accounts table + # ====== Remove the index named by_branch_party in the table_name table # t.remove_index :name => :by_branch_party def remove_index(options = {}) @base.remove_index(@table_name, options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 74c07c624d..8e3ba1297e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -346,11 +346,11 @@ module ActiveRecord # Remove the given index from the table. # - # Remove the suppliers_name_index in the suppliers table. - # remove_index :suppliers, :name - # Remove the index named accounts_branch_id_index in the accounts table. + # Remove the index_accounts_on_column in the accounts table. + # remove_index :accounts, :column + # Remove the index named index_accounts_on_branch_id in the accounts table. # remove_index :accounts, :column => :branch_id - # Remove the index named accounts_branch_id_party_id_index in the accounts table. + # Remove the index named index_accounts_on_branch_id_and_party_id in the accounts table. # remove_index :accounts, :column => [:branch_id, :party_id] # Remove the index named by_branch_party in the accounts table. # remove_index :accounts, :name => :by_branch_party diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 3eddb69e73..a7856539b7 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -1,3 +1,5 @@ +require 'set' + module ActiveRecord # :stopdoc: module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 24d8c8cad2..d6c167ad36 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ # encoding: utf-8 -gem 'mysql2', '~> 0.3.0' +gem 'mysql2', '~> 0.3.6' require 'mysql2' module ActiveRecord diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3e390ba994..29dd0a9ea3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -466,10 +466,11 @@ module ActiveRecord # Executes an INSERT query and returns the new record's ID def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) - # Extract the table from the insert sql. Yuck. - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk ||= primary_key(table) + unless pk + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref + end if pk select_value("#{sql} RETURNING #{quote_column_name(pk)}") @@ -565,9 +566,9 @@ module ActiveRecord def sql_for_insert(sql, pk, id_value, sequence_name, binds) unless pk - _, table = extract_schema_and_table(sql.split(" ", 4)[2]) - - pk = primary_key(table) + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref end sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk @@ -665,34 +666,33 @@ module ActiveRecord SQL end + # Returns true if table exists. + # If the schema is not specified as part of +name+ then it will only find tables within + # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) schema, table = extract_schema_and_table(name.to_s) + return false unless table - binds = [[nil, table.gsub(/(^"|"$)/,'')]] + binds = [[nil, table]] binds << [nil, schema] if schema exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_tables - WHERE tablename = $1 - #{schema ? "AND schemaname = $2" : ''} + SELECT COUNT(*) + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind in ('v','r') + AND c.relname = $1 + AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} SQL end - # Extracts the table and schema name from +name+ - def extract_schema_and_table(name) - schema, table = name.split('.', 2) - - unless table # A table was provided without a schema - table = schema - schema = nil - end - - if name =~ /^"/ # Handle quoted table names - table = name - schema = nil - end - [schema, table] + # Returns true if schema exists. + def schema_exists?(name) + exec_query(<<-SQL, 'SCHEMA', [[nil, name]]).rows.first[0].to_i > 0 + SELECT COUNT(*) + FROM pg_namespace + WHERE nspname = $1 + SQL end # Returns an array of indexes for the given table. @@ -742,6 +742,11 @@ module ActiveRecord query('select current_database()')[0][0] end + # Returns the current schema name. + def current_schema + query('SELECT current_schema', 'SCHEMA')[0][0] + end + # Returns the current database encoding format. def encoding query(<<-end_sql)[0][0] @@ -806,7 +811,7 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - + select_value <<-end_sql, 'Reset sequence' SELECT setval('#{quoted_sequence}', (SELECT COALESCE(MAX(#{quote_column_name pk})+(SELECT increment_by FROM #{quoted_sequence}), (SELECT min_value FROM #{quoted_sequence})) FROM #{quote_table_name(table)}), false) end_sql @@ -835,7 +840,7 @@ module ActiveRecord else sequence = result.second+'.'+result.last end - + [result.first, sequence] rescue nil @@ -935,7 +940,7 @@ module ActiveRecord # Construct a clean list of column names from the ORDER BY clause, removing # any ASC/DESC modifiers - order_columns = orders.collect { |s| s =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : s } + order_columns = orders.collect { |s| s.gsub(/\s+(ASC|DESC)\s*/i, '') } 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}" } @@ -960,27 +965,27 @@ module ActiveRecord end private - def exec_no_cache(sql, binds) - @connection.async_exec(sql) - end - - def exec_cache(sql, binds) - unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" - @connection.prepare nextkey, sql - @statements[sql] = nextkey + def exec_no_cache(sql, binds) + @connection.async_exec(sql) end - key = @statements[sql] + def exec_cache(sql, binds) + unless @statements.key? sql + nextkey = "a#{@statements.length + 1}" + @connection.prepare nextkey, sql + @statements[sql] = nextkey + end - # Clear the queue - @connection.get_last_result - @connection.send_query_prepared(key, binds.map { |col, val| - type_cast(val, col) - }) - @connection.block - @connection.get_last_result - end + key = @statements[sql] + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + end # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: @@ -1080,9 +1085,29 @@ module ActiveRecord end end - def table_definition - TableDefinition.new(self) - end + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema.name"."table name"</tt> + def extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + + def extract_table_ref_from_insert_sql(sql) + sql[/into\s+([^\(]*).*values\s*\(/i] + $1.strip if $1 + end + + def table_definition + TableDefinition.new(self) + end end end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 3afa257a76..6cfce6e573 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -73,7 +73,7 @@ module ActiveRecord # <tt>locking_enabled?</tt> at this point as # <tt>@attributes</tt> may not have been initialized yet. - if lock_optimistically && result.include?(self.class.locking_column) + if result.key?(self.class.locking_column) && lock_optimistically result[self.class.locking_column] ||= 0 end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index de26b21f1a..3d1bc5c1e0 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -116,8 +116,10 @@ module ActiveRecord # with the name of the column. Other options include # <tt>:name</tt> and <tt>:unique</tt> (e.g. # <tt>{ :name => "users_name_index", :unique => true }</tt>). - # * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified - # by +index_name+. + # * <tt>remove_index(table_name, :column => column_name)</tt>: Removes the index + # specified by +column_name+. + # * <tt>remove_index(table_name, :name => index_name)</tt>: Removes the index + # specified by +index_name+. # # == Irreversible transformations # diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index f01e94169f..4a01313c61 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -93,11 +93,15 @@ module ActiveRecord [:remove_timestamps, args] end - # Forwards any missing method call to the \target. + # Record all the methods called in the +change+ method of a migration. + # This will ensure that IrreversibleMigration is raised when the corresponding + # invert_method does not exist while the migration is rolled back. def method_missing(method, *args, &block) - @delegate.send(method, *args, &block) - rescue NoMethodError => e - raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}") + if delegate.respond_to?(method) + delegate.send(method, *args, &block) + else + record(method, args) + end end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 14db7a6cd6..0313abe456 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -40,6 +40,25 @@ module ActiveRecord end end + ## + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes # :nodoc: + if current_scope + current_scope.scope_for_create + else + scope = relation.clone + scope.default_scoped = true + scope.scope_for_create + end + end + + ## + # Are there default attributes associated with this scope? + def scope_attributes? # :nodoc: + current_scope || default_scopes.any? + end + # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query, # such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>. # diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 367a1be4d9..f425118f59 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -319,9 +319,7 @@ module ActiveRecord # that a new instance, or one populated from a passed-in Hash, still has all the attributes # that instances loaded from the database would. def attributes_from_column_definition - Hash[self.class.columns.map do |column| - [column.name, column.default] - end] + self.class.column_defaults.dup end end end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 4e61671473..e485901440 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -33,6 +33,14 @@ module ActiveRecord @target = target end + def method_missing(method_sym, *arguments, &block) + @target.send(method_sym, *arguments, &block) + end + + def respond_to?(method_sym, include_private = false) + super || @target.respond_to?(method_sym) + end + def each(&block) @target.each(&block) end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 6ef24a4eaf..399f3c5d03 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -369,7 +369,7 @@ db_namespace = namespace :db do ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password'] search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? - search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ") + search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") end `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}` raise 'Error dumping database' if $?.exitstatus == 1 @@ -377,8 +377,7 @@ db_namespace = namespace :db do dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile'] `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql` when 'sqlserver' - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r` - `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) @@ -423,7 +422,7 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql` when 'sqlserver' - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(:test) IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl| @@ -453,9 +452,11 @@ db_namespace = namespace :db do dbfile = abcs['test']['database'] || abcs['test']['dbfile'] File.delete(dbfile) if File.exist?(dbfile) when 'sqlserver' - dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}` - `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql` + test = abcs.deep_dup['test'] + test_database = test['database'] + test['database'] = 'master' + ActiveRecord::Base.establish_connection(test) + ActiveRecord::Base.connection.recreate_database!(test_database) when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl| diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 317af8a15d..9cd146b857 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -6,7 +6,7 @@ module ActiveRecord JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder, :reverse_order] + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches @@ -29,6 +29,7 @@ module ActiveRecord SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)} (ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])} @extensions = [] + @create_with_value = {} end def insert(values) @@ -403,7 +404,7 @@ module ActiveRecord end def scope_for_create - @scope_for_create ||= where_values_hash.merge(@create_with_value || {}) + @scope_for_create ||= where_values_hash.merge(create_with_value) end def eager_loading? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index aabe5c269b..9c5f603c01 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -146,10 +146,16 @@ module ActiveRecord if options.except(:distinct).present? apply_finder_options(options.except(:distinct)).calculate(operation, column_name, :distinct => options[:distinct]) else - if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) - construct_relation_for_association_calculations.calculate(operation, column_name, options) + relation = with_default_scope + + if relation.equal?(self) + if eager_loading? || (includes_values.present? && references_eager_loaded_tables?) + construct_relation_for_association_calculations.calculate(operation, column_name, options) + else + perform_calculation(operation, column_name, options) + end else - perform_calculation(operation, column_name, options) + relation.calculate(operation, column_name, options) end end rescue ThrowResult @@ -196,7 +202,7 @@ module ActiveRecord def execute_simple_calculation(operation, column_name, distinct) #:nodoc: # Postgresql doesn't like ORDER BY when there are no GROUP BY - relation = with_default_scope.reorder(nil) + relation = reorder(nil) if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. @@ -245,7 +251,7 @@ module ActiveRecord "#{field} AS #{aliaz}" } - relation = with_default_scope.except(:group).group(group.join(',')) + relation = except(:group).group(group.join(',')) relation.select_values = select_values calculated_data = @klass.connection.select_all(relation.to_sql) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 739363415c..ec2e4d4dcd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -137,7 +137,7 @@ module ActiveRecord def create_with(value) relation = clone - relation.create_with_value = value && (@create_with_value || {}).merge(value) + relation.create_with_value = value ? create_with_value.merge(value) : {} relation end @@ -305,9 +305,18 @@ module ActiveRecord def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? - order_query.join(', ').split(',').collect do |s| - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') - end + order_query.map do |o| + case o + when Arel::Nodes::Ascending, Arel::Nodes::Descending + o.reverse + when String, Symbol + o.to_s.split(',').collect do |s| + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + end + else + o + end + end.flatten end def array_of_strings?(o) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 69706b5ead..ba882beca9 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -55,7 +55,7 @@ module ActiveRecord merged_relation.lock_value = r.lock_value unless merged_relation.lock_value - merged_relation = merged_relation.create_with(r.create_with_value) if r.create_with_value + merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty? # Apply scope extension modules merged_relation.send :apply_modules, r.extensions diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 7c49236854..d57794daf8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,45 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_primary_key + assert_equal 'id', @connection.primary_key('ex') + end + + def test_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(data character varying(255) primary key)') + assert_equal 'data', @connection.primary_key('ex') + end + + def test_primary_key_returns_nil_for_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.primary_key('ex') + end + + def test_primary_key_raises_error_if_table_not_found + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key('unobtainium') + end + end + + def test_insert_sql_with_proprietary_returning_clause + id = @connection.insert_sql("insert into ex (number) values(5150)", nil, "number") + assert_equal "5150", id + end + + def test_insert_sql_with_quoted_schema_and_table_name + id = @connection.insert_sql('insert into "public"."ex" (number) values(5150)') + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + + def test_insert_sql_with_no_space_after_table_name + id = @connection.insert_sql("insert into ex(number) values(5150)") + expect = @connection.query('select max(id) from ex').first.first + assert_equal expect, id + end + def test_serial_sequence assert_equal 'public.accounts_id_seq', @connection.serial_sequence('accounts', 'id') @@ -35,6 +74,36 @@ module ActiveRecord @connection.default_sequence_name('zomg') end + def test_pk_and_sequence_for + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @connection.default_sequence_name('ex', 'id'), seq + end + + def test_pk_and_sequence_for_with_non_standard_primary_key + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(code serial primary key)') + pk, seq = @connection.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @connection.default_sequence_name('ex', 'code'), seq + end + + def test_pk_and_sequence_for_returns_nil_if_no_seq + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer primary key)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_no_pk + @connection.exec_query('drop table if exists ex') + @connection.exec_query('create table ex(id integer)') + assert_nil @connection.pk_and_sequence_for('ex') + end + + def test_pk_and_sequence_for_returns_nil_if_table_not_found + assert_nil @connection.pk_and_sequence_for('unobtainium') + end + def test_exec_insert_number insert(@connection, 'number' => 10) diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index a5c3e69af9..27e7b1a1c3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -20,6 +20,7 @@ class SchemaTest < ActiveRecord::TestCase 'email character varying(50)', 'moment timestamp without time zone default now()' ] + PK_TABLE_NAME = 'table_with_pk' class Thing1 < ActiveRecord::Base set_table_name "test_schema.things" @@ -49,6 +50,7 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE INDEX #{INDEX_B_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_B_COLUMN_S2});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" @connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)" end def teardown @@ -63,12 +65,30 @@ class SchemaTest < ActiveRecord::TestCase end end + def test_table_exists_when_on_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert(@connection.table_exists?(TABLE_NAME), "table should exist and be found") + end + end + + def test_table_exists_when_not_on_schema_search_path + with_schema_search_path('PUBLIC') do + assert(!@connection.table_exists?(TABLE_NAME), "table exists but should not be found") + end + end + def test_table_exists_wrong_schema assert(!@connection.table_exists?("foo.things"), "table should not exist") end - def test_table_exists_quoted_table - assert(@connection.table_exists?('"things.table"'), "table should exist") + def test_table_exists_quoted_names + [ %("#{SCHEMA_NAME}"."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}"), %(#{SCHEMA_NAME}."#{TABLE_NAME}")].each do |given| + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end + with_schema_search_path(SCHEMA_NAME) do + given = %("#{TABLE_NAME}") + assert(@connection.table_exists?(given), "table should exist when specified as #{given}") + end end def test_with_schema_prefixed_table_name @@ -91,7 +111,6 @@ class SchemaTest < ActiveRecord::TestCase end end - def test_proper_encoding_of_table_name assert_equal '"table_name"', @connection.quote_table_name('table_name') assert_equal '"table.name"', @connection.quote_table_name('"table.name"') @@ -164,6 +183,79 @@ class SchemaTest < ActiveRecord::TestCase ActiveRecord::Base.connection.schema_search_path = "public" end + def test_primary_key_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + assert_equal 'id', @connection.primary_key(given), "primary key should be found when table referenced as #{given}" + end + end + + def test_primary_key_assuming_schema_search_path + with_schema_search_path(SCHEMA_NAME) do + assert_equal 'id', @connection.primary_key(PK_TABLE_NAME), "primary key should be found" + end + end + + def test_primary_key_raises_error_if_table_not_found_on_schema_search_path + with_schema_search_path(SCHEMA2_NAME) do + assert_raises(ActiveRecord::StatementInvalid) do + @connection.primary_key(PK_TABLE_NAME) + end + end + end + + def test_pk_and_sequence_for_with_schema_specified + [ + %("#{SCHEMA_NAME}"."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}."#{PK_TABLE_NAME}"), + %(#{SCHEMA_NAME}.#{PK_TABLE_NAME}) + ].each do |given| + pk, seq = @connection.pk_and_sequence_for(given) + assert_equal 'id', pk, "primary key should be found when table referenced as #{given}" + assert_equal "#{SCHEMA_NAME}.#{PK_TABLE_NAME}_id_seq", seq, "sequence name should be found when table referenced as #{given}" + end + end + + def test_extract_schema_and_table + { + %(table_name) => [nil,'table_name'], + %("table.name") => [nil,'table.name'], + %(schema.table_name) => %w{schema table_name}, + %("schema".table_name) => %w{schema table_name}, + %(schema."table_name") => %w{schema table_name}, + %("schema"."table_name") => %w{schema table_name}, + %("even spaces".table) => ['even spaces','table'], + %(schema."table.name") => ['schema', 'table.name'] + }.each do |given,expect| + assert_equal expect, @connection.send(:extract_schema_and_table, given) + end + end + + def test_current_schema + { + %('$user',public) => 'public', + SCHEMA_NAME => SCHEMA_NAME, + %(#{SCHEMA2_NAME},#{SCHEMA_NAME},public) => SCHEMA2_NAME, + %(public,#{SCHEMA2_NAME},#{SCHEMA_NAME}) => 'public' + }.each do |given,expect| + with_schema_search_path(given) { assert_equal expect, @connection.current_schema } + end + end + + def test_schema_exists? + { + 'public' => true, + SCHEMA_NAME => true, + SCHEMA2_NAME => true, + 'darkside' => false + }.each do |given,expect| + assert_equal expect, @connection.schema_exists?(given) + end + end + private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb new file mode 100644 index 0000000000..303ba9245a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -0,0 +1,49 @@ +require "cases/helper" + +class ViewTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + SCHEMA_NAME = 'test_schema' + TABLE_NAME = 'things' + VIEW_NAME = 'view_things' + COLUMNS = [ + 'id integer', + 'name character varying(50)', + 'email character varying(50)', + 'moment timestamp without time zone' + ] + + class ThingView < ActiveRecord::Base + set_table_name 'test_schema.view_things' + end + + def setup + @connection = ActiveRecord::Base.connection + @connection.execute "CREATE SCHEMA #{SCHEMA_NAME} CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" + @connection.execute "CREATE TABLE #{SCHEMA_NAME}.\"#{TABLE_NAME}.table\" (#{COLUMNS.join(',')})" + @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" + end + + def teardown + @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" + end + + def test_table_exists + name = ThingView.table_name + assert @connection.table_exists?(name), "'#{name}' table should exist" + end + + def test_column_definitions + assert_nothing_raised do + assert_equal COLUMNS, columns("#{SCHEMA_NAME}.#{VIEW_NAME}") + end + end + + private + def columns(table_name) + @connection.send(:column_definitions, table_name).map do |name, type, default| + "#{name} #{type}" + (default ? " default #{default}" : '') + end + end + +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 2224097f04..5fef3faab9 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -478,6 +478,19 @@ class BasicsTest < ActiveRecord::TestCase def test_hashing assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end + + def test_comparison + topic_1 = Topic.create! + topic_2 = Topic.create! + + assert_equal [topic_2, topic_1].sort, [topic_1, topic_2] + end + + def test_comparison_with_different_objects + topic = Topic.create + category = Category.create(:name => "comparison") + assert_nil topic <=> category + end def test_readonly_attributes assert_equal Set.new([ 'title' , 'comments_count' ]), ReadonlyTitlePost.readonly_attributes diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb index 85871aebdf..a1d1177289 100644 --- a/activerecord/test/cases/connection_management_test.rb +++ b/activerecord/test/cases/connection_management_test.rb @@ -77,6 +77,13 @@ module ActiveRecord @management.call(@env) assert ActiveRecord::Base.connection_handler.active_connections? end + + test "proxy is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ConnectionManagement::Proxy.new(body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end end end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 0f79c99e1a..85d38fac25 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -14,9 +14,11 @@ module ActiveRecord assert recorder.respond_to?(:america) end - def test_send_calls_super - assert_raises(NoMethodError) do - @recorder.send(:non_existing_method, :horses) + def test_non_existing_method_records_and_raises_on_inversion + @recorder.send(:non_existing_method, :horses) + assert_equal 1, @recorder.commands.length + assert_raises(ActiveRecord::IrreversibleMigration) do + @recorder.inverse end end @@ -29,7 +31,12 @@ module ActiveRecord assert_equal [[:create_table, [:horses]]], recorder.commands end - def test_unknown_commands_raise_exception + def test_unknown_commands_delegate + recorder = CommandRecorder.new(stub(:foo => 'bar')) + assert_equal 'bar', recorder.foo + end + + def test_unknown_commands_raise_exception_if_they_cannot_delegate @recorder.record :execute, ['some sql'] assert_raises(ActiveRecord::IrreversibleMigration) do @recorder.inverse diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index bf7565a0d0..93a1249e43 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1071,6 +1071,18 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.drop_table :testings rescue nil end + def test_column_exists_on_table_with_no_options_parameter_supplied + Person.connection.create_table :testings do |t| + t.string :foo + end + Person.connection.change_table :testings do |t| + assert t.column_exists?(:foo) + assert !(t.column_exists?(:bar)) + end + ensure + Person.connection.drop_table :testings rescue nil + end + def test_add_table assert !Reminder.table_exists? diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index a61180cfaf..ad17f6f83a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -203,3 +203,14 @@ class QueryCacheExpiryTest < ActiveRecord::TestCase end end end + +class QueryCacheBodyProxyTest < ActiveRecord::TestCase + + test "is polite to it's body and responds to it" do + body = Class.new(String) { def to_path; "/path"; end }.new + proxy = ActiveRecord::QueryCache::BodyProxy.new(nil, body) + assert proxy.respond_to?(:to_path) + assert_equal proxy.to_path, "/path" + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index a6e08f95d0..f2d177d834 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -15,6 +15,18 @@ class RelationScopingTest < ActiveRecord::TestCase assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end + def test_reverse_order_with_arel_node + assert_equal Developer.order("id DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).reverse_order + end + + def test_reverse_order_with_multiple_arel_nodes + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order(Developer.arel_table[:id].desc).order(Developer.arel_table[:name].desc).reverse_order + end + + def test_reverse_order_with_arel_nodes_and_strings + assert_equal Developer.order("id DESC").order("name DESC").to_a.reverse, Developer.order("id DESC").order(Developer.arel_table[:name].desc).reverse_order + end + def test_double_reverse_order_produces_original_order assert_equal Developer.order("name DESC"), Developer.order("name DESC").reverse_order.reverse_order end @@ -472,6 +484,13 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal 'Jamis', jamis.name end + # FIXME: I don't know if this is *desired* behavior, but it is *today's* + # behavior. + def test_create_with_empty_hash_will_not_reset + jamis = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with({}).new + assert_equal 'Aaron', jamis.name + end + def test_unscoped_with_named_scope_should_not_have_default_scope assert_equal [DeveloperCalledJamis.find(developers(:poor_jamis).id)], DeveloperCalledJamis.poor @@ -498,4 +517,11 @@ class DefaultScopingTest < ActiveRecord::TestCase lowest_salary_dev = DeveloperOrderedBySalary.find(developers(:poor_jamis).id) assert_equal lowest_salary_dev, DeveloperOrderedBySalary.last end + + def test_default_scope_include_with_count + d = DeveloperWithIncludes.create! + d.audit_logs.create! :message => 'foo' + + assert_equal 1, DeveloperWithIncludes.where(:audit_logs => { :message => 'foo' }).count + end end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 12e58bd340..b23ead6feb 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -20,7 +20,7 @@ module ActiveRecord end def test_single_values - assert_equal [:limit, :offset, :lock, :readonly, :create_with, :from, :reorder, :reverse_order].map(&:to_s).sort, + assert_equal [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order].map(&:to_s).sort, Relation::SINGLE_VALUE_METHODS.map(&:to_s).sort end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index fc9df8c7a3..5a95e68eb4 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -145,6 +145,18 @@ class RelationTest < ActiveRecord::TestCase assert_equal topics(:first).title, topics.first.title end + + def test_finding_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal 4, topics.to_a.size + assert_equal topics(:first).title, topics.first.title + end + + def test_finding_last_with_arel_order + topics = Topic.order(Topic.arel_table[:id].asc) + assert_equal topics(:fourth).title, topics.last.title + end + def test_finding_with_order_concatenated topics = Topic.order('author_name').order('title') assert_equal 4, topics.to_a.size @@ -164,6 +176,13 @@ class RelationTest < ActiveRecord::TestCase assert_equal entrants(:first).name, entrants.first.name end + def test_finding_with_cross_table_order_and_limit + tags = Tag.includes(:taggings). + order("tags.name asc", "taggings.taggable_id asc", "REPLACE('abc', taggings.taggable_type, taggings.taggable_type)"). + limit(1).to_a + assert_equal 1, tags.length + 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 diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 98d6aa22f7..f182a7fa97 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -91,6 +91,12 @@ class DeveloperWithSelect < ActiveRecord::Base default_scope select('name') end +class DeveloperWithIncludes < ActiveRecord::Base + self.table_name = 'developers' + has_many :audit_logs, :foreign_key => :developer_id + default_scope includes(:audit_logs) +end + class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' default_scope :order => 'salary DESC' diff --git a/activerecord/test/support/config.rb b/activerecord/test/support/config.rb index c7ac077599..6d123688a3 100644 --- a/activerecord/test/support/config.rb +++ b/activerecord/test/support/config.rb @@ -1,6 +1,7 @@ require 'yaml' require 'erubis' require 'fileutils' +require 'pathname' module ARTest class << self diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 0097a51a7c..034d03533f 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,10 +1,14 @@ *Rails 3.2.0 (unreleased)* +* The inflector understands acronyms. [dlee] + +* Deprecated ActiveSupport::Memoizable in favor of Ruby memoization pattern [José Valim] + * Added Time#all_day/week/quarter/year as a way of generating ranges (example: Event.where(created_at: Time.now.all_week)) [DHH] * Added instance_accessor: false as an option to Class#cattr_accessor and friends [DHH] -* Removed ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton] +* Removed ActiveSupport::SecureRandom in favor of SecureRandom from the standard library [Jon Leighton] * ActiveSupport::OrderedHash now has different behavior for #each and #each_pair when given a block accepting its parameters with a splat. [Andrew Radev] diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 0e6bc30fa2..8f8deb9692 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -1,12 +1,12 @@ module ActiveSupport - # Many backtraces include too much information that's not relevant for the context. This makes it hard to find the signal - # in the backtrace and adds debugging time. With a BacktraceCleaner, you can setup filters and silencers for your particular - # context, so only the relevant lines are included. + # Backtraces often include many lines that are not relevant for the context under review. This makes it hard to find the + # signal amongst the backtrace noise, and adds debugging time. With a BacktraceCleaner, filters and silencers are used to + # remove the noisy lines, so that only the most relevant lines remain. # - # If you need to reconfigure an existing BacktraceCleaner, like the one in Rails, to show as much as possible, you can always - # call BacktraceCleaner#remove_silencers! Also, if you need to reconfigure an existing BacktraceCleaner so that it does not - # filter or modify the paths of any lines of the backtrace, you can call BacktraceCleaner#remove_filters! These two methods - # will give you a completely untouched backtrace. + # Filters are used to modify lines of data, while silencers are used to remove lines entirely. The typical filter use case + # is to remove lengthy path information from the start of each line, and view file paths relevant to the app directory + # instead of the file system root. The typical silencer use case is to exclude the output of a noisy library from the + # backtrace, so that you can focus on the rest. # # ==== Example: # @@ -15,13 +15,18 @@ module ActiveSupport # bc.add_silencer { |line| line =~ /mongrel|rubygems/ } # bc.clean(exception.backtrace) # will strip the Rails.root prefix and skip any lines from mongrel or rubygems # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) and show as much data as possible, you can + # always call <tt>BacktraceCleaner#remove_silencers!</tt>, which will restore the backtrace to a pristine state. If you + # need to reconfigure an existing BacktraceCleaner so that it does not filter or modify the paths of any lines of the + # backtrace, you can call BacktraceCleaner#remove_filters! These two methods will give you a completely untouched backtrace. + # # Inspired by the Quiet Backtrace gem by Thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] end - # Returns the backtrace after all filters and silencers has been run against it. Filters run first, then silencers. + # Returns the backtrace after all filters and silencers have been run against it. Filters run first, then silencers. def clean(backtrace, kind = :silent) filtered = filter(backtrace) @@ -45,8 +50,8 @@ module ActiveSupport @filters << block end - # Adds a silencer from the block provided. If the silencer returns true for a given line, it'll be excluded from the - # clean backtrace. + # Adds a silencer from the block provided. If the silencer returns true for a given line, it will be excluded from + # the clean backtrace. # # Example: # @@ -57,7 +62,7 @@ module ActiveSupport end # Will remove all silencers, but leave in the filters. This is useful if your context of debugging suddenly expands as - # you suspect a bug in the libraries you use. + # you suspect a bug in one of the libraries you use. def remove_silencers! @silencers = [] end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index a94446acde..a2d2719de7 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -12,12 +12,12 @@ module ActiveSupport class Configuration < ActiveSupport::InheritableOptions def compile_methods! - self.class.compile_methods!(keys.reject {|key| respond_to?(key)}) + self.class.compile_methods!(keys) end # compiles reader methods so we don't have to go through method_missing def self.compile_methods!(keys) - keys.each do |key| + keys.reject { |m| method_defined?(m) }.each do |key| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{key}; _get(#{key.inspect}); end RUBY diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index f420270dc4..4834eca8b1 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -40,7 +40,7 @@ class Array if object.nil? [] elsif object.respond_to?(:to_ary) - object.to_ary + object.to_ary || [object] else [object] end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 7baba75ad3..ca9b2c1b60 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/array/extract_options' class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -56,11 +57,18 @@ class Class # object.setting # => false # Base.setting # => true # + # To opt out of the instance reader method, pass :instance_reader => false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # # To opt out of the instance writer method, pass :instance_writer => false. # # object.setting = false # => NoMethodError def class_attribute(*attrs) - instance_writer = !attrs.last.is_a?(Hash) || attrs.pop[:instance_writer] + options = attrs.extract_options! + instance_reader = options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_writer, true) attrs.each do |name| class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -84,13 +92,15 @@ class Class val end - remove_possible_method :#{name} - def #{name} - defined?(@#{name}) ? @#{name} : self.class.#{name} - end + if instance_reader + remove_possible_method :#{name} + def #{name} + defined?(@#{name}) ? @#{name} : self.class.#{name} + end - def #{name}? - !!#{name} + def #{name}? + !!#{name} + end end RUBY diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index c2a6476604..0b368fe7b7 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -11,7 +11,7 @@ class Hash end # Called when object is nested under an object that receives - # #with_indifferent_access. This method with be called on the current object + # #with_indifferent_access. This method will be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by # default. Subclasses of Hash may overwrite this method to return +self+ if # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 2f0676f567..002688d6c0 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,5 +1,4 @@ require 'active_support/inflector/methods' -require 'active_support/inflector/inflections' require 'active_support/inflector/transliterate' # String inflections define new methods on the String class to transform names for different purposes. diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 8b8aa2b380..a19b5c95c7 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -76,10 +76,33 @@ end module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = ["capitalize", "chomp", "chop", "delete", "downcase", "gsub", "lstrip", "next", "reverse", "rstrip", "slice", "squeeze", "strip", "sub", "succ", "swapcase", "tr", "tr_s", "upcase"].freeze - alias safe_concat concat + + alias_method :original_concat, :concat + private :original_concat + + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def safe_concat(value) + raise SafeConcatError if dirty? + original_concat(value) + end + + def initialize(*) + @dirty = false + super + end + + def initialize_copy(other) + super + @dirty = other.dirty? + end def concat(value) - if value.html_safe? + if dirty? || value.html_safe? super(value) else super(ERB::Util.h(value)) @@ -92,11 +115,7 @@ module ActiveSupport #:nodoc: end def html_safe? - true - end - - def html_safe - self + !dirty? end def to_s @@ -113,29 +132,31 @@ module ActiveSupport #:nodoc: def to_yaml(*args) return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck? - to_str.to_yaml(*args) end UNSAFE_STRING_METHODS.each do |unsafe_method| class_eval <<-EOT, __FILE__, __LINE__ - def #{unsafe_method}(*args) - super.to_str + def #{unsafe_method}(*args, &block) + to_str.#{unsafe_method}(*args, &block) end def #{unsafe_method}!(*args) - raise TypeError, "Cannot modify SafeBuffer in place" + @dirty = true + super end EOT end + + protected + + def dirty? + @dirty + end end end class String - def html_safe! - raise "You can't call html_safe! on a String" - end - def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index d5d55b7207..90bb62f57b 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -20,10 +20,61 @@ module ActiveSupport @__instance__ ||= new end - attr_reader :plurals, :singulars, :uncountables, :humans + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + end + + # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore + # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`. + # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will + # convert the acronym into a non-delimited single lowercase word when passed to +underscore+. + # + # Examples: + # acronym 'HTML' + # titleize 'html' #=> 'HTML' + # camelize 'html' #=> 'HTML' + # underscore 'MyHTML' #=> 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' #=> 'MyHTTPDelimited' + # camelize 'https' #=> 'Https', not 'HTTPs' + # underscore 'HTTPS' #=> 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' #=> 'HTTPS' + # underscore 'HTTPS' #=> 'https' + # + # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as + # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an + # acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) #=> 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) #=> 'APIs' + # + # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard + # capitalization. The only restriction is that the word must begin with a capital letter. + # + # Examples: + # acronym 'RESTful' + # underscore 'RESTful' #=> 'restful' + # underscore 'RESTfulController' #=> 'restful_controller' + # titleize 'RESTfulController' #=> 'RESTful Controller' + # camelize 'restful' #=> 'RESTful' + # camelize 'restful_controller' #=> 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' #=> 'mcdonald' + # camelize 'mcdonald' #=> 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + @acronym_regex = /#{@acronyms.values.join("|")}/ end # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. @@ -117,95 +168,5 @@ module ActiveSupport Inflections.instance end end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end end end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a2c4f7bfda..3d28d33f40 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept @@ -10,6 +12,44 @@ module ActiveSupport module Inflector extend self + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result + else + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.any? { |inflection| result =~ /\b(#{inflection})\Z/i } + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. # @@ -25,12 +65,14 @@ module ActiveSupport # though there are cases where that does not hold: # # "SSLError".underscore.camelize # => "SslError" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } else - lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end + string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') end # Makes an underscored, lowercase form from the expression in the string. @@ -48,13 +90,66 @@ module ActiveSupport def underscore(camel_cased_word) word = camel_cased_word.to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') word.tr!("-", "_") word.downcase! word end + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub!(/_id$/, "") + result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase } + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + # Replaces underscores with dashes in the string. # # Example: diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 1c4dd24227..6296c1d4b8 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -3,8 +3,8 @@ require 'active_support/core_ext/class/attribute' module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume ActiveSupport::Notifications - # with solely purpose of logging. The log subscriber dispatches notifications to a - # registered object based on its given namespace. + # with the sole purpose of logging them. The log subscriber dispatches notifications to + # a registered object based on its given namespace. # # An example would be Active Record log subscriber responsible for logging queries: # @@ -109,8 +109,8 @@ module ActiveSupport # Set color by using a string or one of the defined constants. If a third # option is set to true, it also adds bold to the string. This is based - # on Highline implementation and it automatically appends CLEAR to the end - # of the returned String. + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. # def color(text, color, bold=false) return text unless colorize_logging diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index 0a7bcd5bb8..4c67676ad5 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -1,8 +1,15 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/aliasing' +require 'active_support/deprecation' module ActiveSupport module Memoizable + def self.extended(base) + ActiveSupport::Deprecation.warn "ActiveSupport::Memoizable is deprecated and will be removed in future releases," \ + "simply use Ruby memoization pattern instead.", caller + super + end + def self.memoized_ivar_for(symbol) "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym end @@ -79,7 +86,11 @@ module ActiveSupport else # else def #{symbol}(*args) # def mime_type(*args) #{memoized_ivar} ||= {} unless frozen? # @_memoized_mime_type ||= {} unless frozen? - reload = args.pop if args.last == true || args.last == :reload # reload = args.pop if args.last == true || args.last == :reload + args_length = method(:#{original_method}).arity # args_length = method(:_unmemoized_mime_type).arity + if args.length == args_length + 1 && # if args.length == args_length + 1 && + (args.last == true || args.last == :reload) # (args.last == true || args.last == :reload) + reload = args.pop # reload = args.pop + end # end # if defined?(#{memoized_ivar}) && #{memoized_ivar} # if defined?(@_memoized_mime_type) && @_memoized_mime_type if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args) diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 8d8e6ebc58..bf81567d22 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -36,6 +36,10 @@ module ActiveSupport #:nodoc: self[name] end end + + def respond_to?(name) + true + end end class InheritableOptions < OrderedOptions diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index c6d8191298..2e5ea2c360 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -58,16 +58,26 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase child = Class.new(parent) parent.config.bar = :foo - assert !parent.config.respond_to?(:bar) - assert !child.config.respond_to?(:bar) - assert !child.new.config.respond_to?(:bar) + assert_method_not_defined parent.config, :bar + assert_method_not_defined child.config, :bar + assert_method_not_defined child.new.config, :bar parent.config.compile_methods! assert_equal :foo, parent.config.bar assert_equal :foo, child.new.config.bar - assert_respond_to parent.config, :bar - assert_respond_to child.config, :bar - assert_respond_to child.new.config, :bar + assert_method_defined parent.config, :bar + assert_method_defined child.config, :bar + assert_method_defined child.new.config, :bar + end + + def assert_method_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" + end + + def assert_method_not_defined(object, method) + methods = object.public_methods.map(&:to_s) + assert !methods.include?(method.to_s), "Expected #{methods.inspect} to not include #{method.to_s.inspect}" end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 0e5407bc35..e532010b18 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -456,8 +456,9 @@ class ArrayWrapperTests < Test::Unit::TestCase assert_equal [o], Array.wrap(o) end - def test_wrap_returns_nil_if_to_ary_returns_nil - assert_nil Array.wrap(NilToAry.new) + def test_wrap_returns_wrapped_if_to_ary_returns_nil + o = NilToAry.new + assert_equal [o], Array.wrap(o) end def test_wrap_does_not_complain_if_to_ary_does_not_return_an_array diff --git a/activesupport/test/core_ext/class/attribute_test.rb b/activesupport/test/core_ext/class/attribute_test.rb index d58b60482b..e290a6e012 100644 --- a/activesupport/test/core_ext/class/attribute_test.rb +++ b/activesupport/test/core_ext/class/attribute_test.rb @@ -60,6 +60,12 @@ class ClassAttributeTest < ActiveSupport::TestCase assert_raise(NoMethodError) { object.setting = 'boom' } end + test 'disabling instance reader' do + object = Class.new { class_attribute :setting, :instance_reader => false }.new + assert_raise(NoMethodError) { object.setting } + assert_raise(NoMethodError) { object.setting? } + end + test 'works well with singleton classes' do object = @klass.new object.singleton_class.setting = 'foo' diff --git a/activesupport/test/flush_cache_on_private_memoization_test.rb b/activesupport/test/flush_cache_on_private_memoization_test.rb index a7db96eb71..20768b777a 100644 --- a/activesupport/test/flush_cache_on_private_memoization_test.rb +++ b/activesupport/test/flush_cache_on_private_memoization_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' require 'test/unit' class FlashCacheOnPrivateMemoizationTest < Test::Unit::TestCase - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end def test_public assert_method_unmemoizable :pub diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 95f18126d4..b9e299af75 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -94,6 +94,88 @@ class InflectorTest < Test::Unit::TestCase assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) end + def test_camelize_with_underscores + assert_equal("CamelCase", ActiveSupport::Inflector.camelize('Camel_Case')) + end + + def test_acronyms + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + inflect.acronym("HTTP") + inflect.acronym("RESTful") + inflect.acronym("W3C") + inflect.acronym("PhD") + inflect.acronym("RoR") + inflect.acronym("SSL") + end + + # camelize underscore humanize titleize + [ + ["API", "api", "API", "API"], + ["APIController", "api_controller", "API controller", "API Controller"], + ["Nokogiri::HTML", "nokogiri/html", "Nokogiri/HTML", "Nokogiri/HTML"], + ["HTTPAPI", "http_api", "HTTP API", "HTTP API"], + ["HTTP::Get", "http/get", "HTTP/get", "HTTP/Get"], + ["SSLError", "ssl_error", "SSL error", "SSL Error"], + ["RESTful", "restful", "RESTful", "RESTful"], + ["RESTfulController", "restful_controller", "RESTful controller", "RESTful Controller"], + ["IHeartW3C", "i_heart_w3c", "I heart W3C", "I Heart W3C"], + ["PhDRequired", "phd_required", "PhD required", "PhD Required"], + ["IRoRU", "i_ror_u", "I RoR u", "I RoR U"], + ["RESTfulHTTPAPI", "restful_http_api", "RESTful HTTP API", "RESTful HTTP API"], + + # misdirection + ["Capistrano", "capistrano", "Capistrano", "Capistrano"], + ["CapiController", "capi_controller", "Capi controller", "Capi Controller"], + ["HttpsApis", "https_apis", "Https apis", "Https Apis"], + ["Html5", "html5", "Html5", "Html5"], + ["Restfully", "restfully", "Restfully", "Restfully"], + ["RoRails", "ro_rails", "Ro rails", "Ro Rails"] + ].each do |camel, under, human, title| + assert_equal(camel, ActiveSupport::Inflector.camelize(under)) + assert_equal(camel, ActiveSupport::Inflector.camelize(camel)) + assert_equal(under, ActiveSupport::Inflector.underscore(under)) + assert_equal(under, ActiveSupport::Inflector.underscore(camel)) + assert_equal(title, ActiveSupport::Inflector.titleize(under)) + assert_equal(title, ActiveSupport::Inflector.titleize(camel)) + assert_equal(human, ActiveSupport::Inflector.humanize(under)) + end + end + + def test_acronym_override + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("LegacyApi") + end + + assert_equal("LegacyApi", ActiveSupport::Inflector.camelize("legacyapi")) + assert_equal("LegacyAPI", ActiveSupport::Inflector.camelize("legacy_api")) + assert_equal("SomeLegacyApi", ActiveSupport::Inflector.camelize("some_legacyapi")) + assert_equal("Nonlegacyapi", ActiveSupport::Inflector.camelize("nonlegacyapi")) + end + + def test_acronyms_camelize_lower + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML") + end + + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("html_api", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("htmlAPI", false)) + assert_equal("htmlAPI", ActiveSupport::Inflector.camelize("HTMLAPI", false)) + end + + def test_underscore_acronym_sequence + ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym("API") + inflect.acronym("HTML5") + inflect.acronym("HTML") + end + + assert_equal("html5_html_api", ActiveSupport::Inflector.underscore("HTML5HTMLAPI")) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) @@ -148,8 +230,8 @@ class InflectorTest < Test::Unit::TestCase end def test_parameterize_with_custom_separator - StringToParameterized.each do |some_string, parameterized_string| - assert_equal(parameterized_string.gsub('-', '_'), ActiveSupport::Inflector.parameterize(some_string, '_')) + StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| + assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index ec9d92794c..0cb1f70657 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -168,6 +168,7 @@ module InflectorTestCases StringToParameterizeWithNoSeparator = { "Donald E. Knuth" => "donaldeknuth", + "With-some-dashes" => "with-some-dashes", "Random text with *(bad)* characters" => "randomtextwithbadcharacters", "Trailing bad characters!@#" => "trailingbadcharacters", "!@#Leading bad characters" => "leadingbadcharacters", @@ -179,6 +180,8 @@ module InflectorTestCases StringToParameterizeWithUnderscore = { "Donald E. Knuth" => "donald_e_knuth", "Random text with *(bad)* characters" => "random_text_with_bad_characters", + "With-some-dashes" => "with-some-dashes", + "Retain_underscore" => "retain_underscore", "Trailing bad characters!@#" => "trailing_bad_characters", "!@#Leading bad characters" => "leading_bad_characters", "Squeeze separators" => "squeeze_separators", diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb index bceac1315b..e333b9a78c 100644 --- a/activesupport/test/memoizable_test.rb +++ b/activesupport/test/memoizable_test.rb @@ -2,7 +2,9 @@ require 'abstract_unit' class MemoizableTest < ActiveSupport::TestCase class Person - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls @@ -65,7 +67,9 @@ class MemoizableTest < ActiveSupport::TestCase end module Rates - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end attr_reader :sales_tax_calls def sales_tax(price) @@ -77,7 +81,9 @@ class MemoizableTest < ActiveSupport::TestCase end class Calculator - extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + extend ActiveSupport::Memoizable + end include Rates attr_reader :fib_calls @@ -96,6 +102,15 @@ class MemoizableTest < ActiveSupport::TestCase end memoize :fib + def add_or_subtract(i, j, add) + if add + i + j + else + i - j + end + end + memoize :add_or_subtract + def counter @count ||= 0 @count += 1 @@ -199,9 +214,16 @@ class MemoizableTest < ActiveSupport::TestCase assert_equal 13, @calculator.fib_calls end + def test_memoization_with_boolean_arg + assert_equal 4, @calculator.add_or_subtract(2, 2, true) + assert_equal 2, @calculator.add_or_subtract(4, 2, false) + end + def test_object_memoization [Company.new, Company.new, Company.new].each do |company| - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_equal "37signals", company.name @@ -235,11 +257,15 @@ class MemoizableTest < ActiveSupport::TestCase def test_double_memoization assert_raise(RuntimeError) { Person.memoize :name } person = Person.new - person.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + person.extend ActiveSupport::Memoizable + end assert_raise(RuntimeError) { person.memoize :name } company = Company.new - company.extend ActiveSupport::Memoizable + ActiveSupport::Deprecation.silence do + company.extend ActiveSupport::Memoizable + end company.memoize :name assert_raise(RuntimeError) { company.memoize :name } end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index 3a9854358c..46bc0d7a34 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -4,6 +4,7 @@ begin rescue LoadError end +require 'active_support/core_ext/string/inflections' require 'yaml' class SafeBufferTest < ActiveSupport::TestCase @@ -45,7 +46,7 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal ActiveSupport::SafeBuffer, new_buffer.class end - def test_to_yaml + test "Should be converted to_yaml" do str = 'hello!' buf = ActiveSupport::SafeBuffer.new str yaml = buf.to_yaml @@ -54,22 +55,57 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal 'hello!', YAML.load(yaml) end - def test_nested + test "Should work in nested to_yaml conversion" do str = 'hello!' data = { 'str' => ActiveSupport::SafeBuffer.new(str) } yaml = YAML.dump data assert_equal({'str' => str}, YAML.load(yaml)) end + test "Should work with underscore" do + str = "MyTest".html_safe.underscore + assert_equal "my_test", str + end + test "Should not return safe buffer from gsub" do altered_buffer = @buffer.gsub('', 'asdf') assert_equal 'asdf', altered_buffer assert !altered_buffer.html_safe? end - test "Should not allow gsub! on safe buffers" do - assert_raise TypeError do - @buffer.gsub!('', 'asdf') + test "Should not return safe buffer from gsub!" do + @buffer.gsub!('', 'asdf') + assert_equal 'asdf', @buffer + assert !@buffer.html_safe? + end + + test "Should escape dirty buffers on add" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "hello<>", clean + @buffer + end + + test "Should concat as a normal string when dirty" do + dirty = @buffer + clean = "hello".html_safe + @buffer.gsub!('', '<>') + assert_equal "<>hello", @buffer + clean + end + + test "Should preserve dirty? status on copy" do + @buffer.gsub!('', '<>') + assert !@buffer.dup.html_safe? + end + + test "Should raise an error when safe_concat is called on dirty buffers" do + @buffer.gsub!('', '<>') + assert_raise ActiveSupport::SafeBuffer::SafeConcatError do + @buffer.safe_concat "BUSTED" end end + + test "should not fail if the returned object is not a string" do + assert_kind_of NilClass, @buffer.slice("chipchop") + end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 49cefc6e82..3575175517 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -224,9 +224,9 @@ class TimeZoneTest < Test::Unit::TestCase end def test_formatted_offset_positive - zone = ActiveSupport::TimeZone['Moscow'] - assert_equal "+03:00", zone.formatted_offset - assert_equal "+0300", zone.formatted_offset(false) + zone = ActiveSupport::TimeZone['New Delhi'] + assert_equal "+05:30", zone.formatted_offset + assert_equal "+0530", zone.formatted_offset(false) end def test_formatted_offset_negative @@ -257,7 +257,7 @@ class TimeZoneTest < Test::Unit::TestCase end def test_to_s - assert_equal "(GMT+03:00) Moscow", ActiveSupport::TimeZone['Moscow'].to_s + assert_equal "(GMT+05:30) New Delhi", ActiveSupport::TimeZone['New Delhi'].to_s end def test_all_sorted diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 880849d2ec..a9b4deb551 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,27 +1,26 @@ *Rails 3.2.0 (unreleased)* +* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim] + * Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran] * Removed old 'config.paths.app.controller' API in favor of 'config.paths["app/controller"]' API. [Guillermo Iguaran] - *Rails 3.1.0 (unreleased)* -* The new rake task assets:clean removes precompiled assets. [fxn] +* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim] -* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] +* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim] -* Fixed database tasks for jdbc* adapters #jruby - - [Rashmi Yadav] +* The new rake task assets:clean removes precompiled assets. [fxn] -* Template generation for jdbcpostgresql #jruby +* Application and plugin generation run bundle install unless --skip-gemfile or --skip-bundle. [fxn] - [Vishnu Atrai] +* Fixed database tasks for jdbc* adapters #jruby [Rashmi Yadav] -* Template generation for jdbcmysql and jdbcsqlite3 #jruby +* Template generation for jdbcpostgresql #jruby [Vishnu Atrai] - [Arun Agrawal] +* Template generation for jdbcmysql and jdbcsqlite3 #jruby [Arun Agrawal] * The -j option of the application generator accepts an arbitrary string. If passed "foo", the gem "foo-rails" is added to the Gemfile, and the application JavaScript manifest diff --git a/railties/bin/rails b/railties/bin/rails new file mode 100755 index 0000000000..a7d6938e0d --- /dev/null +++ b/railties/bin/rails @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +require "rails/cli"
\ No newline at end of file diff --git a/railties/guides/source/action_view_overview.textile b/railties/guides/source/action_view_overview.textile index a3454579ad..7703d6c720 100644 --- a/railties/guides/source/action_view_overview.textile +++ b/railties/guides/source/action_view_overview.textile @@ -165,7 +165,7 @@ will produce <em>emphasized</em> <em><b>emph & bold</b></em> <a href="http://rubyonrails.org">A link</a> -<target option="fast" name="compile" \> +<target option="fast" name="compile" /> </html> Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: @@ -211,7 +211,7 @@ xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do end </ruby> -h5. Template caching +h5. Template Caching By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will check the file's modification time and recompile it in development mode. @@ -235,7 +235,7 @@ This will render a file named +_menu.html.erb+ at that point within the view is That code will pull in the partial from +app/views/shared/_menu.html.erb+. -h5. Using Partials to Simplify Views +h5. Using Partials to simplify Views One way to use partials is to treat them as the equivalent of subroutines: as a way to move details out of a view so that you can grasp what's going on more easily. For example, you might have a view that looked like this: diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile index b4ce60fcaa..e3871a3c34 100644 --- a/railties/guides/source/active_record_querying.textile +++ b/railties/guides/source/active_record_querying.textile @@ -57,6 +57,7 @@ The methods are: * +group+ * +order+ * +reorder+ +* +reverse_order+ * +limit+ * +offset+ * +joins+ @@ -550,6 +551,32 @@ In case the +reorder+ clause is not used, the SQL executed would be: SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC </sql> +h4. +reverse_order+ + +The +reverse_order+ method reverses the ordering clause if specified. + +<ruby> +Client.where("orders_count > 10").order(:name).reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY name DESC +</sql> + +If no ordering clause is specified in the query, the +reverse_order+ orders by the primary key in reverse order. + +<ruby> +Client.where("orders_count > 10").reverse_order +</ruby> + +The SQL that would be executed: +<sql> +SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC +</sql> + +This method accepts *no* arguments. + h3. Readonly Objects Active Record provides +readonly+ method on a relation to explicitly disallow modification or deletion of any of the returned object. Any attempt to alter or destroy a readonly record will not succeed, raising an +ActiveRecord::ReadOnlyRecord+ exception. diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 7512f7bcb9..bbf5af5dcc 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -947,7 +947,7 @@ h4. Class Attributes h5. +class_attribute+ -The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy: +The method +class_attribute+ declares one or more inheritable class attributes that can be overridden at any level down the hierarchy. <ruby> class A @@ -983,7 +983,7 @@ self.default_params = { }.freeze </ruby> -They can be also accessed and overridden at the instance level: +They can be also accessed and overridden at the instance level. <ruby> A.x = 1 @@ -996,7 +996,7 @@ a1.x # => 1, comes from A a2.x # => 2, overridden in a2 </ruby> -The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to false, as in +The generation of the writer instance method can be prevented by setting the option +:instance_writer+ to +false+. <ruby> module ActiveRecord @@ -1009,8 +1009,20 @@ end A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +The generation of the reader instance method can be prevented by setting the option +:instance_reader+ to +false+. + +<ruby> +class A + class_attribute :x, :instance_reader => false +end + +A.x = 1 # NoMethodError +</ruby> + For convenience +class_attribute+ also defines an instance predicate which is the double negation of what the instance reader returns. In the examples above it would be called +x?+. +When +:instance_reader+ is +false+, the instance predicate returns a +NoMethodError+ just like the reader method. + NOTE: Defined in +active_support/core_ext/class/attribute.rb+ h5. +cattr_reader+, +cattr_writer+, and +cattr_accessor+ @@ -1036,18 +1048,24 @@ module ActionView end </ruby> -we can access +field_error_proc+ in views. The generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+ (not any false value, but exactly +false+): +we can access +field_error_proc+ in views. + +The generation of the reader instance method can be prevented by setting +:instance_reader+ to +false+ and the generation of the writer instance method can be prevented by setting +:instance_writer+ to +false+. Generation of both methods can be prevented by setting +:instance_accessor+ to +false+. In all cases, the value must be exactly +false+ and not any false value. <ruby> -module ActiveRecord - class Base - # No pluralize_table_names= instance writer is generated. - cattr_accessor :pluralize_table_names, :instance_writer => false +module A + class B + # No first_name instance reader is generated. + cattr_accessor :first_name, :instance_reader => false + # No last_name= instance writer is generated. + cattr_accessor :last_name, :instance_writer => false + # No surname instance reader or surname= writer is generated. + cattr_accessor :surname, :instance_accessor => false end end </ruby> -A model may find that option useful as a way to prevent mass-assignment from setting the attribute. +A model may find it useful to set +:instance_accessor+ to +false+ as a way to prevent mass-assignment from setting the attribute. NOTE: Defined in +active_support/core_ext/class/attribute_accessors.rb+. @@ -1146,8 +1164,12 @@ h3. Extensions to +String+ h4. Output Safety +h5. Motivation + Inserting data into HTML templates needs extra care. For example you can't just interpolate +@review.title+ verbatim into an HTML page. On one hand if the review title is "Flanagan & Matz rules!" the output won't be well-formed because an ampersand has to be escaped as "&amp;". On the other hand, depending on the application that may be a big security hole because users can inject malicious HTML setting a hand-crafted review title. Check out the "section about cross-site scripting in the Security guide":security.html#cross-site-scripting-xss for further information about the risks. +h5. Safe Strings + Active Support has the concept of <i>(html) safe</i> strings since Rails 3. A safe string is one that is marked as being insertable into HTML as is. It is trusted, no matter whether it has been escaped or not. Strings are considered to be <i>unsafe</i> by default: @@ -1173,8 +1195,6 @@ s # => "<script>...</script>" It is your responsibility to ensure calling +html_safe+ on a particular string is fine. -NOTE: For performance reasons safe strings are implemented in a way that cannot offer an in-place +html_safe!+ variant. - If you append onto a safe string, either in-place with +concat+/<tt><<</tt>, or with <tt>+</tt>, the result is a safe string. Unsafe arguments are escaped: <ruby> @@ -1215,6 +1235,22 @@ end NOTE: Defined in +active_support/core_ext/string/output_safety.rb+. +h5. Transformation + +As a rule of thumb, except perhaps for concatenation as explained above, any method that may change a string gives you an unsafe string. These are +donwcase+, +gsub+, +strip+, +chomp+, +underscore+, etc. + +In the case of in-place transformations like +gsub!+ the receiver itself becomes unsafe. + +INFO: The safety bit is lost always, no matter whether the transformation actually changed something. + +h5. Conversion and Coercion + +Calling +to_s+ on a safe string returns a safe string, but coercion with +to_str+ returns an unsafe string. + +h5. Copying + +Calling +dup+ or +clone+ on safe strings yields safe strings. + h4. +squish+ The method +squish+ strips leading and trailing whitespace, and substitutes runs of whitespace with a single space each: @@ -1460,7 +1496,15 @@ end That may be handy to compute method names in a language that follows that convention, for example JavaScript. -INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. +INFO: As a rule of thumb you can think of +camelize+ as the inverse of +underscore+, though there are cases where that does not hold: <tt>"SSLError".underscore.camelize</tt> gives back <tt>"SslError"</tt>. To support cases such as this, Active Support allows you to specify acronyms in +config/initializers/inflections.rb+: + +<ruby> +ActiveSupport::Inflector.inflections do |inflect| + inflect.acronym 'SSL' +end + +"SSLError".underscore.camelize #=> "SSLError" +</ruby> +camelize+ is aliased to +camelcase+. @@ -2282,7 +2326,7 @@ NOTE: Defined in +active_support/core_ext/array/grouping.rb+. h5. +in_groups(number, fill_with = nil)+ -The method +in_groups+ splits an array into a certain number of groups. The method returns and array with the groups: +The method +in_groups+ splits an array into a certain number of groups. The method returns an array with the groups: <ruby> %w(1 2 3 4 5 6 7).in_groups(3) @@ -2714,7 +2758,7 @@ Active Support extends the method +Range#step+ so that it can be invoked without (1..10).step(2) # => [1, 3, 5, 7, 9] </ruby> -As the example shows, in that case the method returns and array with the corresponding elements. +As the example shows, in that case the method returns an array with the corresponding elements. NOTE: Defined in +active_support/core_ext/range/blockless_step.rb+. @@ -3316,6 +3360,32 @@ Active Support defines +Time.current+ to be today in the current time zone. That When making Time comparisons using methods which honor the user time zone, make sure to use +Time.current+ and not +Time.now+. There are cases where the user time zone might be in the future compared to the system time zone, which +Time.today+ uses by default. This means +Time.now+ may equal +Time.yesterday+. +h5. +all_day+, +all_week+, +all_month+, +all_quarter+ and +all_year+ + +The method +all_day+ returns a range representing the whole day of the current time. + +<ruby> +now = Time.current +# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 +now.all_day +# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Mon, 09 Aug 2010 23:59:59 UTC +00:00 +</ruby> + +Analogously, +all_week+, +all_month+, +all_quarter+ and +all_year+ all serve the purpose of generating time ranges. + +<ruby> +now = Time.current +# => Mon, 09 Aug 2010 23:20:05 UTC +00:00 +now.all_week +# => Mon, 09 Aug 2010 00:00:00 UTC +00:00..Sun, 15 Aug 2010 23:59:59 UTC +00:00 +now.all_month +# => Sat, 01 Aug 2010 00:00:00 UTC +00:00..Tue, 31 Aug 2010 23:59:59 UTC +00:00 +now.all_quarter +# => Thu, 01 Jul 2010 00:00:00 UTC +00:00..Thu, 30 Sep 2010 23:59:59 UTC +00:00 +now.all_year +# => Fri, 01 Jan 2010 00:00:00 UTC +00:00..Fri, 31 Dec 2010 23:59:59 UTC +00:00 +</ruby> + h4. Time Constructors Active Support defines +Time.current+ to be +Time.zone.now+ if there's a user time zone defined, with fallback to +Time.now+: diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile index c12bc3d1dc..78683b743c 100644 --- a/railties/guides/source/asset_pipeline.textile +++ b/railties/guides/source/asset_pipeline.textile @@ -3,55 +3,104 @@ h2. Asset Pipeline This guide will cover the ideology of the asset pipeline introduced in Rails 3.1. By referring to this guide you will be able to: +* Understand what the asset pipeline is and what it does * Properly organize your application assets * Understand the benefits of the asset pipeline -* Adding a preprocessor to the pipeline +* Adding a pre-processor to the pipeline * Package assets with a gem endprologue. h3. What Is The Asset Pipeline? -The asset pipeline is a new feature introduced in Rails 3.1 using the "Sprockets":http://getsprockets.org/ engine. It allows developers to place design elements in +app/assets+ instead of +public+, there are many advantages to this. A big one is that they are now processed by Rails instead of your webserver, allowing you to use preprocessors like CoffeeScript, SCSS, or ERB. Another advantage is that your CSS and JavaScript is compiled into one file by default, this allows users to cache all the CSS and JavaScript data so your pages render faster. Not to mention how much cleaner your application will become. +With Rails 3.1 comes a new feature known as the asset pipeline. The asset pipeline provides features that have usually been implemented by external gems, such as "Jammit":http://documentcloud.github.com/jammit and "Sprockets.":http://getsprockets.org These gems are popular for being able to serve concatenated or compressed versions of the assets of an application, such as Cascade Style Sheets (CSS) or JavaScript (JS) files so that the number of requests made to the server are reduced, making the page load faster. Rails 3.1 includes the Sprockets gem. h3. How to Use the Asset Pipeline -The asset pipeline is easy to migrate to and use. There are a few things that you'll need to learn first, like where to place your files, how to create a manifest, and how to add any preproccesors if you desire. +In previous versions of Rails, all assets lived under the +public+ directory in directories such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory will be served by the Sprockets middleware included in the sprockets gem. + +This is not to say that assets can (or should) no longer be placed in +public+. They still can be, they will just be served by the application or the web server which is running the application and served just like normal files. You would only use +app/assets+ if you wish your files to undergo some pre-processing before they are served. + +When a scaffold or controller is generated for the application, Rails will also generate a JavaScript file (or CoffeeScript if the +coffee-script+ gem is in the +Gemfile+) and a Cascade Style Sheet file (or SCSS if +sass-rails+ is in the +Gemfile+) file for that controller. For example, if a +ProjectsController+ is generated, there will be a new file at +app/assets/javascripts/projects.js.coffee+ and another at +app/assets/stylesheets/projects.css.scss+. You should put any JavaScript or CSS unique to a controller inside their respective asset files. h4. Asset Organization -WIP +Assets can be placed inside an application in one of three locations: +app/assets+, +lib/assets+ or +vendor/assets+. + ++app/assets+ is for assets that are owned by the application, such as custom images, JavaScript files or stylesheets. + ++lib/assets+ is for your own libraries' code that doesn't really fit into the scope of the application or those libraries which are shared across applications. + ++vendor/assets+ is for assets that are owned by outside entities, such as code for JavaScript plugins. + +Any subdirectory that exists within these three locations will be added to the search path for Sprockets (visible by calling +Rails.application.config.assets.paths+ in a console). When an asset is requested, these paths will be looked through to see if they contain an asset matching the name specified. Once an asset has been found, it's processed by Sprockets and then served up. + +h4. External Assets + +Assets can also come from external sources such as engines. A good example of this is the +jquery-rails+ gem which comes with Rails as the standard JavaScript library gem. This gem contains an engine class which inherits from +Rails::Engine+. By doing this, Rails is informed that the directory for this gem may contain assets and the +app/assets+, +lib/assets+ and +vendor/assets+ directories of this engine are added to the search path of Sprockets. + +h4. Serving Assets + +To serve assets, we can use the same tags that we are generally familiar with: + +<erb> + <%= image_tag "rails.png" %> +</erb> + +Providing that assets are enabled within our application (+config.assets.enabled+ in your environment is set to +true+), this file will be served by Sprockets unless a file at +public/assets/rails.png+ exists, in which case that file will be served. Otherwise, Sprockets will look through the available paths until it finds a file that matches the name and then will serve it, first looking in the application's assets directories and then falling back to the various engines of the application. + +Sprockets does not add any new methods to require your assets, we still use the familiar +javascript_include_tag+ and +stylesheet_link_tag+. You can use it to include from the normal public directory or the assets directory. + +h4. Manifest Files and Directives + +Sprockets allows some assets to be manifest files. These manifest files require what's known as _directives_, which instruct Sprockets which files to require in order to build a single CSS or JavaScript file. With these directives, Sprockets will load the files specified, process them if necessary, concatenate them into one single file and then compress them (if +Rails.application.config.assets.compress+ is set to +true+). By serving one file rather than many, a page's load time can be greatly reduced. + +For example, in the default Rails application there's a +app/assets/javascripts/application.js+ file which contains the following lines: + +<plain> +//= require jquery +//= require jquery_ujs +//= require_tree . +</plain> + +In JavaScript files, directives begin with +//=+. In this case, the following file is using the +require+ directive and the +require_tree+ directive. The +require+ directive tells Sprockets that we would like to require a file called +jquery.js+ that is available somewhere in the search path for Sprockets. By default, this is located inside the +vendor/assets/javascripts+ directory contained within the +jquery-rails+ gem. An identical event takes place for the +jquery_ujs+ require specified here also. -Sprockets will automatically load manifest files by searching directories in app/assets and including the first file with a basename of index. (Confirm and add: does it load app/assets/index?) +The +require_tree .+ directive tells Sprockets to include _all_ JavaScript files in this directory into the output. Only a path relative to the file can be specified. -h4. Directives +There's also a default +app/assets/stylesheets/application.css+ file which contains these lines: -WIP +<plain> +/* ... +*= require_self +*= require_tree . +*/ +</plain> -Sprockets, the rails tie that powers the asset pipeline, provides three directives which are like Ruby's methods. They are: +require+, +require_tree+, and +require_self+. These directives must be called at the top of a file in a comment with an equal sign before it. (note: CSS directives need *= if in a continuous comment -- confirm please) +The directives that work in the JavaScript files will also work in stylesheets, obviously requiring stylesheets rather than JavaScript files. The +require_tree+ directive here works the same way as the JavaScript one, requiring all stylesheets from the current directory. -The require directive loads a file with the supplied basename from the following paths: app/assets/*, lib/assets/*, vendor/assets/*, as well as any of your gem's asset files. +In this example +require_self+ is used. This will put the CSS contained within the file (if any) at the top of any other CSS in this file unless +require_self+ is specified after another +require+ directive. -Using the +require_tree+ directive you can easily include an entire folder of assets. The paths must be relative, so begin them with either a forward slash or dots. For example to include a folder in the same directory you would do +require_tree ./folder_name+ +h4. Preprocessing -Require self does... something +Based on the extensions of the assets, Sprockets will do preprocessing on the files. With the default gemset that comes with Rails, when a controller or a scaffold is generated, a CoffeeScript file and a SCSS file will be generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which generated an +app/assets/javascripts/projects.js.coffee+ and a +app/assets/stylesheets/projects.css.scss+ file. -h4. Stacking Preprocessors +When these files are requested, they will be processed by the processors provided by the +coffee-script+ and +sass-rails+ gems and then sent back to the browser as JavaScript and CSS respectively. -Sprockets allows you to stack preprocessors. The stack is ran off the file extensions in a last in, first out method (like popping an array). For example if we want to make a JavaScript asset with both CoffeeScript and ERB the file would be named: +name.js.coffee.erb+. If it were named +name.js.erb.coffee+ CoffeeScript would raise an error because it doesn't understand ERB tags. +In addition to this single layer of pre-processing, we can also put on additional extensions to the end of the file in order for them to be processed using other languages first. For example, we could call our stylesheet +app/assets/stylesheets/projects.css.scss.erb+ it would first be processed as ERB, then SCSS and finally served as CSS. We could also do this with our JavaScript file, calling it +app/assets/javascripts/projects.js.coffee.erb+. -h4. Adding a Preproccessor +Keep in mind that the order of these pre-processors is important. For example, if we called our JavaScript file +app/assets/javascripts/projects.js.erb.coffee+ then it would be processed with the CoffeeScript interpreter first, which wouldn't understand ERB and therefore we would run into problems. -WIP +h4. Compressing Assets -https://github.com/rtomayko/tilt for gems or config.register_processor('text/css', MyAwesomeProccessor) for local stuff +The default Gemfile also includes the "uglifier":https://github.com/lautis/uglifier gem. This gem wraps "UglifierJS":https://github.com/mishoo/UglifyJS (written for NodeJS) in Ruby. It compress your code by removing white spaces and other magical things like changing your if and else statements to ternary operators when possible. -h3. Packaging Assets with Your Gems +Sprockets also turns on "Gzip":http://en.wikipedia.org/wiki/Gzip (.gz) when possible (by checking the user's headers). Gzip is a file compression technique, much like the ever so popular "zip" file, except it's more open source friendly. Gzip should not modify the contents of the file, but simply the size of the file. -You may find it useful to package certain assets with your gem. A good example would be the "pjax_rails":https://github.com/rails/pjax_rails/ gem. This gem bundles the latest "PJAX":https://github.com/defunkt/jquery-pjax library and some helper methods. If you take a look at the source of pjax_rails, you'll see that it bundles the assets in +lib/assets+ just the same way as you would in +app/assets+. Doing so allows pjax_rails to update JavaScripts without asking users to copy them into their public folder +h4. Adding Assets to Your Gems -If you want the user to load your JavaScript files in their template, you will have to ask them to add a directive to do so. Also avoid any common names such as +form_check.js+ instead try using +mygem/form_check.js+ so it's clear where it's coming from. This will also make it unlikely that your users will create a file with the same name causing the asset pipeline to choose the user's file over yours. +To include your assets inside of a gem, simple package it in +lib/assets+ as you would in +app/assets+. You should append or prepend the name of your gem though, this should help avoid name conflicts with other gems or the user's application. -h3. More on Sprockets +h4. Making Your Library or Gem a Pre-Processor -Sprockets is the engine that handles the asset pipeline in Rails 3.1 and above. Their official website is available at "http://getsprockets.org/":http://getsprockets.org/ and the source code is "available on github":https://github.com/sstephenson/sprockets. +"You should be able to register [your gems] on Tilt and Sprockets will find them." - Josh +Tilt: https://github.com/rtomayko/tilt
\ No newline at end of file diff --git a/railties/guides/source/association_basics.textile b/railties/guides/source/association_basics.textile index 458bfefad8..3c2497e83a 100644 --- a/railties/guides/source/association_basics.textile +++ b/railties/guides/source/association_basics.textile @@ -1120,11 +1120,9 @@ h6(#has_many-collection-find). <tt><em>collection</em>.find(...)</tt> The <tt><em>collection</em>.find</tt> method finds objects within the collection. It uses the same syntax and options as +ActiveRecord::Base.find+. <ruby> -@open_orders = @customer.orders.all(:conditions => "open = 1") +@open_orders = @customer.orders.where(:open => 1) </ruby> -NOTE: Starting Rails 3, supplying options to +ActiveRecord::Base.find+ method is discouraged. Use <tt><em>collection</em>.where</tt> instead when you need to pass conditions. - h6(#has_many-collection-where). <tt><em>collection</em>.where(...)</tt> The <tt><em>collection</em>.where</tt> method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed. diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index f058dce42b..252003edd0 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -382,6 +382,7 @@ class ProductsController < ApplicationController # anything. The default render checks for this using the parameters # used in the previous call to stale? and will automatically send a # :not_modified. So that's it, you're done. + end end </ruby> diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index 026c15feba..b0edc1017d 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -51,15 +51,13 @@ $ rails new commandsapp Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box. -INFO: This output will seem very familiar when we get to the +generate+ command. Creepy foreshadowing! - h4. +rails server+ -The +rails server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to view your work through a web browser. +The +rails server+ command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to access your application through a web browser. -INFO: WEBrick isn't your only option for serving Rails. We'll get to that in a later section. +INFO: WEBrick isn't your only option for serving Rails. We'll get to that "later":#different-servers. -Without any prodding of any kind, +rails server+ will run our new shiny Rails app: +With no further work, +rails server+ will run our new shiny Rails app: <shell> $ cd commandsapp @@ -77,13 +75,19 @@ With just three commands we whipped up a Rails server listening on port 3000. Go You can also use the alias "s" to start the server: <tt>rails s</tt>. +The server can be run on a different port using the +-p+ option. The default development environment can be changed using +-e+. + +<shell> +$ rails server -e production -p 4000 +</shell> + h4. +rails generate+ -The +rails generate+ command uses templates to create a whole lot of things. You can always find out what's available by running +rails generate+ by itself. Let's do that: +The +rails generate+ command uses templates to create a whole lot of things. Running +rails generate+ by itself gives a list of available generators: <shell> $ rails generate -Usage: rails generate generator [args] [options] +Usage: rails generate GENERATOR [args] [options] ... ... @@ -99,7 +103,7 @@ Rails: NOTE: You can install more generators through generator gems, portions of plugins you'll undoubtedly install, and you can even create your own! -Using generators will save you a large amount of time by writing *boilerplate code*, code that is necessary for the app to work, but not necessary for you to spend time writing. That's what we have computers for. +Using generators will save you a large amount of time by writing *boilerplate code*, code that is necessary for the app to work. Let's make our own controller with the controller generator. But what command should we use? Let's ask the generator: @@ -148,7 +152,8 @@ $ rails generate controller Greetings hello create test/unit/helpers/greetings_helper_test.rb invoke assets create app/assets/javascripts/greetings.js - create app/assets/stylesheets/greetings.css + invoke css + create app/assets/stylesheets/greetings.css </shell> @@ -171,7 +176,7 @@ Then the view, to display our message (in +app/views/greetings/hello.html.erb+): <p><%= @message %></p> </html> -Deal. Go check it out in your browser. Fire up your server using +rails server+. +Fire up your server using +rails server+. <shell> $ rails server @@ -184,7 +189,7 @@ The URL will be "http://localhost:3000/greetings/hello":http://localhost:3000/gr INFO: With a normal, plain-old Rails application, your URLs will generally follow the pattern of http://(host)/(controller)/(action), and a URL like http://(host)/(controller) will hit the *index* action of that controller. -Rails comes with a generator for data models too: +Rails comes with a generator for data models too. <shell> $ rails generate model @@ -288,11 +293,7 @@ You can also use the alias "db" to invoke the dbconsole: <tt>rails db</tt>. h4. +rails plugin+ -The +rails plugin+ command simplifies plugin management; think a miniature version of the Gem utility. Let's walk through installing a plugin. You can call the sub-command +discover+, which sifts through repositories looking for plugins, or call +source+ to add a specific repository of plugins, or you can specify the plugin location directly. - -Let's say you're creating a website for a client who wants a small accounting system. Every event having to do with money must be logged, and must never be deleted. Wouldn't it be great if we could override the behavior of a model to never actually take its record out of the database, but instead, just set a field? - -There is such a thing! The plugin we're installing is called +acts_as_paranoid+, and it lets models implement a +deleted_at+ column that gets set when you call destroy. Later, when calling find, the plugin will tack on a database check to filter out "deleted" things. +The +rails plugin+ command simplifies plugin management. Plugins can be installed by name or their repository URLs. You need to have Git installed if you want to install a plugin from a Git repo. The same holds for Subversion too. <shell> $ rails plugin install https://github.com/technoweenie/acts_as_paranoid.git @@ -310,6 +311,12 @@ h4. +rails runner+ $ rails runner "Model.long_running_method" </shell> +You can specify the environment in which the +runner+ command should operate using the +-e+ switch. + +<shell> +$ rails runner -e staging "Model.long_running_method" +</shell> + h4. +rails destroy+ Think of +destroy+ as the opposite of +generate+. It'll figure out what generate did, and undo it. @@ -388,13 +395,56 @@ h4. +db+ The most common tasks of the +db:+ Rake namespace are +migrate+ and +create+, and it will pay off to try out all of the migration rake tasks (+up+, +down+, +redo+, +reset+). +rake db:version+ is useful when troubleshooting, telling you the current version of the database. +More information about migrations can be found in the "Migrations":migrations.html guide. + h4. +doc+ -If you want to strip out or rebuild any of the Rails documentation (including this guide!), the +doc:+ namespace has the tools. Stripping documentation is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. +The +doc:+ namespace has the tools to generate documentation for your app, API documentation, guides. Documentation can also be stripped which is mainly useful for slimming your codebase, like if you're writing a Rails application for an embedded platform. + +* +rake doc:app+ generates documentation for your application in +doc/app+. +* +rake doc:guides+ generates Rails guides in +doc/guides+. +* +rake doc:rails+ generates API documentation for Rails in +doc/api+. +* +rake doc:plugins+ generates API documentation for all the plugins installed in the application in +doc/plugins+. +* +rake doc:clobber_plugins+ removes the generated documentation for all plugins. h4. +notes+ -These tasks will search through your code for commented lines beginning with "FIXME", "OPTIMIZE", "TODO", or any custom annotation (like XXX) and show you them. ++rake notes+ will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is only done in files with extension +.builder+, +.rb+, +.rxml+, +.rhtml+ and +.erb+ for both default and custom annotations. + +<shell> +$ rake notes +(in /home/foobar/commandsapp) +app/controllers/admin/users_controller.rb: + * [ 20] [TODO] any other way to do this? + * [132] [FIXME] high priority for next deploy + +app/model/school.rb: + * [ 13] [OPTIMIZE] refactor this code to make it faster + * [ 17] [FIXME] +</shell> + +If you are looking for a specific annotation, say FIXME, you can use +rake notes:fixme+. Note that you have to lower case the annotation's name. + +<shell> +$ rake notes:fixme +(in /home/foobar/commandsapp) +app/controllers/admin/users_controller.rb: + * [132] high priority for next deploy + +app/model/school.rb: + * [ 17] +</shell> + +You can also use custom annotations in your code and list them using +rake notes:custom+ by specifying the annotation using an environment variable +ANNOTATION+. + +<shell> +$ rake notes:custom ANNOTATION=BUG +(in /home/foobar/commandsapp) +app/model/post.rb: + * [ 23] Have to fix this one before pushing! +</shell> + +NOTE. When using specific annotations and custom annotations, the annotation name (FIXME, BUG etc) is not displayed in the output lines. h4. +routes+ @@ -478,11 +528,13 @@ development: ... </shell> -It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app. +It also generated some lines in our database.yml configuration corresponding to our choice of PostgreSQL for database. + +NOTE. The only catch with using the SCM options is that you have to make your application's directory first, then initialize your SCM, then you can run the +rails new+ command to generate the basis of your app. -h4. +server+ with Different Backends +h4(#different-servers). +server+ with Different Backends -Many people have created a large number different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). +Many people have created a large number of different web servers in Ruby, and many of them can be used to run Rails. Since version 2.3, Rails uses Rack to serve its webpages, which means that any webserver that implements a Rack handler can be used. This includes WEBrick, Mongrel, Thin, and Phusion Passenger (to name a few!). NOTE: For more details on the Rack integration, see "Rails on Rack":rails_on_rack.html. diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile index 80de36070d..8e6010ff79 100644 --- a/railties/guides/source/configuring.textile +++ b/railties/guides/source/configuring.textile @@ -98,7 +98,7 @@ NOTE. The +config.asset_path+ configuration is ignored if the asset pipeline is * +config.secret_token+ used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get +config.secret_token+ initialized to a random key in +config/initializers/secret_token.rb+. -* +config.serve_static_assets+ configures Rails to serve static assets. Defaults to true, but in the production environment is turned off. The server software used to run the application should be used to serve the assets instead. +* +config.serve_static_assets+ configures Rails itself to serve static assets. Defaults to true, but in the production environment is turned off as the server software (e.g. Nginx or Apache) used to run the application should serve static assets instead. Unlike the default setting set this to true when running (absolutely not recommended!) or testing your app in production mode using WEBrick. Otherwise you won´t be able use page caching and requests for files that exist regularly under the public directory will anyway hit your Rails app. * +config.session_store+ is usually set up in +config/initializers/session_store.rb+ and specifies what class to use to store the session. Possible values are +:cookie_store+ which is the default, +:mem_cache_store+, and +:disabled+. The last one tells Rails not to deal with sessions. Custom session stores can also be specified: @@ -116,8 +116,23 @@ WARNING: Threadsafe operation is incompatible with the normal workings of develo * +config.whiny_nils+ enables or disables warnings when a certain set of methods are invoked on +nil+ and it does not respond to them. Defaults to true in development and test environments. +h4. Configuring Assets + +Rails 3.1, by default, is set up to use the +sprockets+ gem to manage assets within an application. This gem concatenates and compresses assets in order to make serving them much less painful. + +* +config.assets.css_compressor+ defines the CSS compressor to use. Only supported value at the moment is +:yui+, which uses the +yui-compressor+ gem. + * +config.assets.enabled+ a flag that controls whether the asset pipeline is enabled. It is explicitly initialized in +config/application.rb+. +* +config.assets.js_compressor+ defines the JavaScript compressor to use. Possible values are +:closure+, +:uglifier+ and +:yui+ which require the use of the +closure-compiler+, +uglifier+ or +yui-compressor+ gems respectively. + +* +config.assets.paths+ contains the paths which are used to look for assets. Appending paths to this configuration option will cause those paths to be used in the search for assets. + +* +config.assets.precompile+ allows you to specify additional assets (other than +application.css+ and +application.js+) which are to be precompiled when +rake assets:precompile+ is run. + +* +config.assets.prefix+ defines the prefix where assets are served from. Defaults to +/assets+. + + h4. Configuring Generators Rails 3 allows you to alter what generators are used with the +config.generators+ method. This method takes a block: diff --git a/railties/guides/source/form_helpers.textile b/railties/guides/source/form_helpers.textile index c7e45c0a23..4314d00f2e 100644 --- a/railties/guides/source/form_helpers.textile +++ b/railties/guides/source/form_helpers.textile @@ -777,7 +777,7 @@ If you need to post some data to an external resource it is still great to build Sometimes when you submit data to an external resource, like payment gateway, fields you can use in your form are limited by an external API. So you may want not to generate an +authenticity_token+ hidden field at all. For doing this just pass +false+ to the +:authenticity_token+ option: <erb> -<%= form_tag 'http://farfar.away/form', :authenticity_token => 'external_token') do %> +<%= form_tag 'http://farfar.away/form', :authenticity_token => false) do %> Form contents <% end %> </erb> diff --git a/railties/guides/source/getting_started.textile b/railties/guides/source/getting_started.textile index 670979c3c2..6aca5d3420 100644 --- a/railties/guides/source/getting_started.textile +++ b/railties/guides/source/getting_started.textile @@ -5,7 +5,7 @@ This guide covers getting up and running with Ruby on Rails. After reading it, y * Installing Rails, creating a new Rails application, and connecting your application to a database * The general layout of a Rails application * The basic principles of MVC (Model, View Controller) and RESTful design -* How to quickly generate the starting pieces of a Rails application. +* How to quickly generate the starting pieces of a Rails application endprologue. @@ -177,14 +177,14 @@ In any case, Rails will create a folder in your working directory called <tt>blo |Gemfile|This file allows you to specify what gem dependencies are needed for your Rails application.| |README|This is a brief instruction manual for your application. Use it to tell others what your application does, how to set it up, and so on.| |Rakefile|This file contains batch jobs that can be run from the terminal.| -|app/|Contains the controllers, models, and views for your application. You'll focus on this folder for the remainder of this guide.| +|app/|Contains the controllers, models, views and assets for your application. You'll focus on this folder for the remainder of this guide.| |config/|Configure your application's runtime rules, routes, database, and more.| |config.ru|Rack configuration for Rack based servers used to start the application.| |db/|Shows your current database schema, as well as the database migrations. You'll learn about migrations shortly.| |doc/|In-depth documentation for your application.| |lib/|Extended modules for your application (not covered in this guide).| |log/|Application log files.| -|public/|The only folder seen to the world as-is. This is where your images, JavaScript files, stylesheets (CSS), and other static files go.| +|public/|The only folder seen to the world as-is. Contains the static files and compiled assets.| |script/|Contains the rails script that starts your app and can contain other scripts you use to deploy or run your application.| |test/|Unit tests, fixtures, and other test apparatus. These are covered in "Testing Rails Applications":testing.html| |tmp/|Temporary files| @@ -205,8 +205,8 @@ h4. Configuring a Database Just about every Rails application will interact with a database. The database to use is specified in a configuration file, +config/database.yml+. If you open this file in a new Rails application, you'll see a default database configuration using SQLite3. The file contains sections for three different environments in which Rails can run by default: -* The +development+ environment is used on your development computer as you interact manually with the application -* The +test+ environment is used to run automated tests +* The +development+ environment is used on your development computer as you interact manually with the application. +* The +test+ environment is used to run automated tests. * The +production+ environment is used when you deploy your application for the world to use. h5. Configuring an SQLite3 Database @@ -244,7 +244,7 @@ If your development computer's MySQL installation includes a root user with an e h5. Configuring a PostgreSQL Database -Finally if you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: +If you choose to use PostgreSQL, your +config/database.yml+ will be customized to use PostgreSQL databases: <yaml> development: @@ -256,6 +256,41 @@ development: password: </yaml> +h5. Configuring an SQLite3 Database for JRuby Platform + +If you choose to use SQLite3 and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcsqlite3 + database: db/development.sqlite3 +</yaml> + +h5. Configuring a MySQL Database for JRuby Platform + +If you choose to use MySQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcmysql + database: blog_development + username: root + password: +</yaml> + +h5. Configuring a PostgreSQL Database for JRuby Platform + +Finally if you choose to use PostgreSQL and using JRuby, your +config/database.yml+ will look a little different. Here's the development section: + +<yaml> +development: + adapter: jdbcpostgresql + encoding: unicode + database: blog_development + username: blog + password: +</yaml> + Change the username and password in the +development+ section as appropriate. TIP: You don't have to update the database configurations manually. If you had a look at the options of application generator, you have seen that one of them is named <tt>--database</tt>. It lets you choose an adapter for couple of most used relational databases. You can even run the generator repeatedly: <tt>cd .. && rails new blog --database=mysql</tt>. When you confirm the overwriting of the +config/database.yml+ file, your application will be configured for MySQL instead of SQLite. @@ -290,7 +325,7 @@ This will fire up an instance of the WEBrick web server by default (Rails can al TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. In development mode, Rails does not generally require you to stop the server; changes you make in files will be automatically picked up by the server. -The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your Application's environment. +The "Welcome Aboard" page is the _smoke test_ for a new Rails application: it makes sure that you have your software configured correctly enough to serve a page. You can also click on the _About your application’s environment_ link to see a summary of your application's environment. h4. Say "Hello", Rails @@ -364,11 +399,11 @@ The scaffold generator will build 15 files in your application, along with some |app/views/posts/new.html.erb |A view to create a new post| |app/views/posts/_form.html.erb |A partial to control the overall look and feel of the form used in edit and new views| |app/helpers/posts_helper.rb |Helper functions to be used from the post views| +|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better| |test/unit/post_test.rb |Unit testing harness for the posts model| |test/functional/posts_controller_test.rb |Functional testing harness for the posts controller| |test/unit/helpers/posts_helper_test.rb |Unit testing harness for the posts helper| |config/routes.rb |Edited to include routing information for posts| -|app/assets/stylesheets/scaffold.css.scss |Cascading style sheet to make the scaffolded views look better| h4. Running a Migration @@ -413,10 +448,10 @@ h4. Adding a Link To hook the posts up to the home page you've already created, you can add a link to the home page. Open +app/views/home/index.html.erb+ and modify it as follows: -<code lang="ruby"> +<ruby> <h1>Hello, Rails!</h1> <%= link_to "My Blog", posts_path %> -</code> +</ruby> The +link_to+ method is one of Rails' built-in view helpers. It creates a hyperlink based on text to display and where to go - in this case, to the path for posts. diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 1d5b0c0c11..340699419b 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -661,9 +661,9 @@ require 'active_support/inflections' require 'active_support/core_ext/string/inflections' </ruby> -The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. +The +active_support/inflector/methods+ file has already been required by +active_support/autoload+ and so won't be loaded again here. The +activesupport/lib/active_support/inflector/inflections.rb+ is required by +active_support/inflector/methods+. -h4. +activesupport/lib/active_support/inflector/inflections.rb+ +h4. +active_support/inflections+ This file references the +ActiveSupport::Inflector+ constant which isn't loaded by this point. But there were autoloads set up in +activesupport/lib/active_support.rb+ which will load the file which loads this constant and so then it will be defined. Then this file defines pluralization and singularization rules for words in Rails. This is how Rails knows how to pluralize "tomato" to "tomatoes". diff --git a/railties/guides/source/nested_model_forms.textile b/railties/guides/source/nested_model_forms.textile index 55694c0eb4..4b1fd2e0ac 100644 --- a/railties/guides/source/nested_model_forms.textile +++ b/railties/guides/source/nested_model_forms.textile @@ -90,7 +90,7 @@ h3. Views h4. Controller code -A nested model form will _only_ be build if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. +A nested model form will _only_ be built if the associated object(s) exist. This means that for a new model instance you would probably want to build the associated object(s) first. Consider the following typical RESTful controller which will prepare a new Person instance and its +address+ and +projects+ associations before rendering the +new+ template: @@ -144,7 +144,7 @@ Now add a nested form for the +address+ association: <%= f.text_field :name %> <%= f.fields_for :address do |af| %> - <%= f.text_field :street %> + <%= af.text_field :street %> <% end %> <% end %> </erb> @@ -159,7 +159,7 @@ This generates: </form> </html> -Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be build by the way it has namespaced the +name+ attribute. +Notice that +fields_for+ recognized the +address+ as an association for which a nested model form should be built by the way it has namespaced the +name+ attribute. When this form is posted the Rails parameter parser will construct a hash like the following: @@ -185,7 +185,7 @@ The form code for an association collection is pretty similar to that of a singl <%= f.text_field :name %> <%= f.fields_for :projects do |pf| %> - <%= f.text_field :name %> + <%= pf.text_field :name %> <% end %> <% end %> </erb> @@ -201,7 +201,7 @@ Which generates: </form> </html> -As you can see it has generated 2 +project name+ inputs, one for each new +project+ that’s build in the controllers +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: +As you can see it has generated 2 +project name+ inputs, one for each new +project+ that was built in the controller's +new+ action. Only this time the +name+ attribute of the input contains a digit as an extra namespace. This will be parsed by the Rails parameter parser as: <ruby> { @@ -215,7 +215,7 @@ As you can see it has generated 2 +project name+ inputs, one for each new +proje } </ruby> -You can basically see the +projects_attributes+ hash as an array of attribute hashes. One for each model instance. +You can basically see the +projects_attributes+ hash as an array of attribute hashes, one for each model instance. NOTE: The reason that +fields_for+ constructed a form which would result in a hash instead of an array is that it won't work for any forms nested deeper than one level deep. diff --git a/railties/guides/source/performance_testing.textile b/railties/guides/source/performance_testing.textile index fe0915bfea..dbe6f97f5c 100644 --- a/railties/guides/source/performance_testing.textile +++ b/railties/guides/source/performance_testing.textile @@ -66,7 +66,7 @@ resources :posts # home_controller.rb class HomeController < ApplicationController def dashboard - @users = User.last_ten(:include => :avatars) + @users = User.last_ten.includes(:avatars) @posts = Post.all_today end end @@ -151,7 +151,7 @@ Performance tests can be run in two modes: Benchmarking and Profiling. h5. Benchmarking -Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run +4 times+ in benchmarking mode. +Benchmarking makes it easy to quickly gather a few metrics about each test tun. By default, each test case is run *4 times* in benchmarking mode. To run performance tests in benchmarking mode: @@ -161,7 +161,7 @@ $ rake test:benchmark h5. Profiling -Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run +1 time+ in profiling mode. +Profiling allows you to make an in-depth analysis of each of your tests by using an external profiler. Depending on your Ruby interpreter, this profiler can be native (Rubinius, JRuby) or not (MRI, which uses RubyProf). By default, each test case is run *once* in profiling mode. To run performance tests in profiling mode: @@ -481,7 +481,7 @@ h4. +profiler+ Usage: <shell> -Usage: rails benchmarker 'Ruby.code' 'Ruby.more_code' ... [OPTS] +Usage: rails profiler 'Ruby.code' 'Ruby.more_code' ... [OPTS] -r, --runs N Number of runs. Default: 1 -o, --output PATH Directory to use when writing the results. diff --git a/railties/guides/source/rails_application_templates.textile b/railties/guides/source/rails_application_templates.textile index 388d8eea3e..3db47a70e8 100644 --- a/railties/guides/source/rails_application_templates.textile +++ b/railties/guides/source/rails_application_templates.textile @@ -1,6 +1,6 @@ h2. Rails Application Templates -Application templates are simple ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project or an existing Rails project. +Application templates are simple Ruby files containing DSL for adding plugins/gems/initializers etc. to your freshly created Rails project or an existing Rails project. By referring to this guide, you will be able to: @@ -58,14 +58,12 @@ gem "bj" gem "nokogiri" </ruby> -Please note that this will NOT install the gems for you. So you may want to run the +rake gems:install+ task too: +Please note that this will NOT install the gems for you and you will have to run +bundle install+ to do that. <ruby> -rake "gems:install" +bundle install </ruby> -And let Rails take care of installing the required gems if they’re not already installed. - h4. add_source(source, options = {}) Adds the given source to the generated application's +Gemfile+. @@ -229,7 +227,7 @@ rake("rails:freeze:gems") if yes?("Freeze rails gems ?") no?(question) acts just the opposite. </ruby> -h4. git(:must => "-a love") +h4. git(:command) Rails templates let you run any git command: diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile index db9c7545c8..cc55d1f756 100644 --- a/railties/guides/source/testing.textile +++ b/railties/guides/source/testing.textile @@ -944,7 +944,7 @@ The built-in +test/unit+ based testing is not the only way to test Rails applica * "Factory Girl":https://github.com/thoughtbot/factory_girl/tree/master, a replacement for fixtures. * "Machinist":https://github.com/notahat/machinist/tree/master, another replacement for fixtures. * "Shoulda":http://www.thoughtbot.com/projects/shoulda, an extension to +test/unit+ with additional helpers, macros, and assertions. -* "RSpec":http://rspec.info/, a behavior-driven development framework +* "RSpec":http://relishapp.com/rspec, a behavior-driven development framework h3. Changelog diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index cca0891835..df92934457 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -87,6 +87,29 @@ module Rails RAILS_CACHE end + # Returns all rails groups for loading based on: + # + # * The Rails environment; + # * The environment variable RAILS_GROUPS; + # * The optional hash given as argument with group dependencies; + # + # == Examples + # + # groups :assets => [:development, :test] + # + # # Returns + # # => [:default, :development, :assets] for Rails.env == "development" + # # => [:default, :production] for Rails.env == "production" + # + def groups(hash={}) + env = Rails.env + groups = [:default, env] + groups.concat ENV["RAILS_GROUPS"].to_s.split(",") + groups.concat hash.map { |k,v| k if v.map(&:to_s).include?(env) } + groups.compact! + groups + end + def version VERSION::STRING end diff --git a/railties/lib/rails/all.rb b/railties/lib/rails/all.rb index 82775b7e3b..01ceb80972 100644 --- a/railties/lib/rails/all.rb +++ b/railties/lib/rails/all.rb @@ -6,6 +6,7 @@ require "rails" action_mailer active_resource rails/test_unit + sprockets ).each do |framework| begin require "#{framework}/railtie" diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a2b2af98a6..fe29668c72 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -78,10 +78,6 @@ module Rails require environment if environment end - def eager_load! #:nodoc: - railties.all(&:eager_load!) - super - end def reload_routes! routes_reloader.reload! @@ -100,14 +96,12 @@ module Rails def load_tasks(app=self) initialize_tasks - railties.all { |r| r.load_tasks(app) } super self end def load_console(app=self) initialize_console - railties.all { |r| r.load_console(app) } super self end diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb index 4fc5e92837..8f3a3e8bbb 100644 --- a/railties/lib/rails/application/railties.rb +++ b/railties/lib/rails/application/railties.rb @@ -4,7 +4,7 @@ module Rails class Application < Engine class Railties < Rails::Engine::Railties def all(&block) - @all ||= railties + engines + super + @all ||= railties + engines + plugins @all.each(&block) if block @all end diff --git a/railties/lib/rails/commands/plugin.rb b/railties/lib/rails/commands/plugin.rb index 048af7cbef..4df849447d 100644 --- a/railties/lib/rails/commands/plugin.rb +++ b/railties/lib/rails/commands/plugin.rb @@ -313,11 +313,11 @@ module Commands o.separator "" o.separator "EXAMPLES" o.separator " Install a plugin from a subversion URL:" - o.separator " #{@script_name} plugin install http://dev.rubyonrails.com/svn/rails/plugins/continuous_builder\n" + o.separator " #{@script_name} plugin install http://example.com/my_svn_plugin\n" o.separator " Install a plugin from a git URL:" o.separator " #{@script_name} plugin install git://github.com/SomeGuy/my_awesome_plugin.git\n" o.separator " Install a plugin and add a svn:externals entry to vendor/plugins" - o.separator " #{@script_name} plugin install -x continuous_builder\n" + o.separator " #{@script_name} plugin install -x my_svn_plugin\n" end end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index b358de89d0..bf57a034b2 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -395,12 +395,20 @@ module Rails delegate :middleware, :root, :paths, :to => :config delegate :engine_name, :isolated?, :to => "self.class" - def load_tasks(*) + def load_tasks(app=self) + railties.all { |r| r.load_tasks(app) } super paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - + + def load_console(app=self) + railties.all { |r| r.load_console(app) } + super + end + def eager_load! + railties.all(&:eager_load!) + config.eager_load_paths.each do |load_path| matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ Dir.glob("#{load_path}/**/*.rb").sort.each do |file| @@ -530,15 +538,9 @@ module Rails end initializer :append_assets_path do |app| - if app.config.assets.respond_to?(:prepend_path) - app.config.assets.prepend_path(*paths["vendor/assets"].existent) - app.config.assets.prepend_path(*paths["lib/assets"].existent) - app.config.assets.prepend_path(*paths["app/assets"].existent) - else - app.config.assets.paths.unshift(*paths["vendor/assets"].existent) - app.config.assets.paths.unshift(*paths["lib/assets"].existent) - app.config.assets.paths.unshift(*paths["app/assets"].existent) - end + app.config.assets.paths.unshift(*paths["vendor/assets"].existent) + app.config.assets.paths.unshift(*paths["lib/assets"].existent) + app.config.assets.paths.unshift(*paths["app/assets"].existent) end initializer :prepend_helpers_path do |app| diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 355b05ce0b..fdafc91fcb 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -53,7 +53,7 @@ module Rails :helper => true, :integration_tool => nil, :javascripts => true, - :javascript_engine => nil, + :javascript_engine => :js, :orm => false, :performance_tool => nil, :resource_controller => :controller, @@ -299,9 +299,6 @@ module Rails return rescue LoadError => e raise unless e.message =~ /#{Regexp.escape(path)}$/ - rescue NameError => e - raise unless e.message =~ /Rails::Generator([\s(::)]|$)/ - warn "[WARNING] Could not load generator #{path.inspect} because it's a Rails 2.x generator, which is not supported anymore. Error: #{e.message}.\n#{e.backtrace.join("\n")}" rescue Exception => e warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}" end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index d31a3262e3..433a56dc57 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -114,11 +114,11 @@ module Rails # git :add => "this.file that.rb" # git :add => "onefile.rb", :rm => "badfile.cxx" # - def git(command={}) - if command.is_a?(Symbol) - run "git #{command}" + def git(commands={}) + if commands.is_a?(Symbol) + run "git #{commands}" else - command.each do |command, options| + commands.each do |command, options| run "git #{command} #{options}" end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 1196e2b7eb..c8847f0159 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -10,7 +10,7 @@ module Rails module Generators class AppBase < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) - JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql ) + JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) attr_accessor :rails_template @@ -37,6 +37,9 @@ module Rails class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, :desc => "Skip Active Record files" + class_option :skip_sprockets, :type => :boolean, :aliases => "-S", :default => false, + :desc => "Skip Sprockets files" + class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" @@ -64,8 +67,8 @@ module Rails def initialize(*args) @original_wd = Dir.pwd - super + convert_database_option_for_jruby end protected @@ -124,7 +127,7 @@ module Rails end def include_all_railties? - !options[:skip_active_record] && !options[:skip_test_unit] + !options[:skip_active_record] && !options[:skip_test_unit] && !options[:skip_sprockets] end def comment_if(value) @@ -157,14 +160,26 @@ module Rails when "postgresql" then "pg" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" - when "jdbcmysql" then "activerecord-jdbcmysql-adapter" - when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" - when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbcmysql" then "activerecord-jdbcmysql-adapter" + when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" + when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" + when "jdbc" then "activerecord-jdbc-adapter" else options[:database] end end - def gem_for_ruby_debugger + def convert_database_option_for_jruby + if defined?(JRUBY_VERSION) + case options[:database] + when "oracle" then options[:database].replace "jdbc" + when "postgresql" then options[:database].replace "jdbcpostgresql" + when "mysql" then options[:database].replace "jdbcmysql" + when "sqlite3" then options[:database].replace "jdbcsqlite3" + end + end + end + + def ruby_debugger_gemfile_entry if RUBY_VERSION < "1.9" "gem 'ruby-debug'" else @@ -172,7 +187,7 @@ module Rails end end - def gem_for_turn + def turn_gemfile_entry unless RUBY_VERSION < "1.9.2" || options[:skip_test_unit] <<-GEMFILE.strip_heredoc group :test do @@ -183,7 +198,7 @@ module Rails end end - def gem_for_javascript + def javascript_gemfile_entry "gem '#{options[:javascript]}-rails'" unless options[:skip_javascript] end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 1f6a7a2f59..b9dc31457a 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -259,9 +259,9 @@ module Rails extra << false unless Object.method(:const_defined?).arity == 1 # Extract the last Module in the nesting - last = nesting.inject(Object) do |last, nest| - break unless last.const_defined?(nest, *extra) - last.const_get(nest) + last = nesting.inject(Object) do |last_module, nest| + break unless last_module.const_defined?(nest, *extra) + last_module.const_get(nest) end if last && last.const_defined?(last_name.camelize, *extra) diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 9450894b05..f9f89c9f1d 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -7,7 +7,7 @@ module Rails attr_accessor :name, :type def initialize(name, type) - raise Thor::Error, "Missing type for attribute '#{name}'.\nExample: '#{name}:string' where string is the type." if type.blank? + type = :string if type.blank? @name, @type = name, type.to_sym end diff --git a/railties/lib/rails/generators/js/assets/assets_generator.rb b/railties/lib/rails/generators/js/assets/assets_generator.rb new file mode 100644 index 0000000000..d134a9e392 --- /dev/null +++ b/railties/lib/rails/generators/js/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Js + module Generators + class AssetsGenerator < Rails::Generators::NamedBase + source_root File.expand_path("../templates", __FILE__) + + def copy_javascript + copy_file "javascript.js", File.join('app/assets/javascripts', class_path, "#{file_name}.js") + end + end + end +end diff --git a/railties/lib/rails/generators/js/assets/templates/javascript.js b/railties/lib/rails/generators/js/assets/templates/javascript.js new file mode 100644 index 0000000000..dee720facd --- /dev/null +++ b/railties/lib/rails/generators/js/assets/templates/javascript.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 242677cc65..6af9d299aa 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -88,6 +88,7 @@ module Rails def lib empty_directory "lib" empty_directory_with_gitkeep "lib/tasks" + empty_directory_with_gitkeep "lib/assets" end def log diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ebe38bf8e6..55d0f29a15 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -5,13 +5,17 @@ source 'http://rubygems.org' <%= database_gemfile_entry -%> <%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%> -# Asset template engines <%= "gem 'json'\n" if RUBY_VERSION < "1.9.2" -%> -gem 'sass-rails' -gem 'coffee-script' -gem 'uglifier' -<%= gem_for_javascript %> +# Gems used only for assets and not required +# in production environments by default. +group :assets do + gem 'sass-rails' + gem 'coffee-rails' + gem 'uglifier' +end + +<%= javascript_gemfile_entry %> # Use unicorn as the web server # gem 'unicorn' @@ -20,6 +24,6 @@ gem 'uglifier' # gem 'capistrano' # To use debugger -# <%= gem_for_ruby_debugger %> +# <%= ruby_debugger_gemfile_entry %> -<%= gem_for_turn -%> +<%= turn_gemfile_entry -%> 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 37c2fb1263..eaa31e7386 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -8,12 +8,14 @@ require 'rails/all' require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" +<%= comment_if :skip_sprockets %> require "sprockets/railtie" <%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> -# If you have a Gemfile, require the gems listed there, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +# If you have a Gemfile, require the default gems, the ones in the +# current environment and also include :assets gems if in development +# or test environments. +Bundler.require *Rails.groups(:assets => %w(development test)) if defined?(Bundler) module <%= app_const_base %> class Application < Rails::Application @@ -45,7 +47,9 @@ module <%= app_const_base %> # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] + <% unless options.skip_sprockets? %> # Enable the asset pipeline config.assets.enabled = true + <% end %> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml new file mode 100644 index 0000000000..1d2bf08b91 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -0,0 +1,62 @@ +# If you are using mssql, derby, hsqldb, or h2 with one of the +# ActiveRecord JDBC adapters, install the appropriate driver, e.g.,: +# gem install activerecord-jdbcmssql-adapter +# +# Configure using Gemfile: +# gem 'activerecord-jdbcmssql-adapter' +# +#development: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_development +# +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +# +#test: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_test +# +#production: +# adapter: mssql +# username: <%= app_name %> +# password: +# host: localhost +# database: <%= app_name %>_production + +# If you are using oracle, db2, sybase, informix or prefer to use the plain +# JDBC adapter, configure your database setting as the example below (requires +# you to download and manually install the database vendor's JDBC driver .jar +# file). See your driver documentation for the apropriate driver class and +# connection string: + +development: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_development + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. + +test: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_test + +production: + adapter: jdbc + username: <%= app_name %> + password: + driver: + url: jdbc:db://localhost/<%= app_name %>_production diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 6bf83e86a5..5a594ac1f3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -9,7 +9,7 @@ # And be sure to use new-style password hashing: # http://dev.mysql.com/doc/refman/5.0/en/old-client.html development: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_development username: root password: @@ -19,14 +19,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_test username: root password: host: localhost production: - adapter: jdbcmysql + adapter: mysql database: <%= app_name %>_production username: root password: diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 0c7f45322b..996ff4baa3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -1,19 +1,10 @@ # PostgreSQL. Versions 7.4 and 8.x are supported. # -# Install the pg driver: -# gem install pg -# On Mac OS X with macports: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config -# On Windows: -# gem install pg -# Choose the win32 build. -# Install PostgreSQL and put its /bin directory on your path. -# # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' development: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_development username: <%= app_name %> @@ -38,14 +29,14 @@ development: # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_test username: <%= app_name %> password: production: - adapter: jdbcpostgresql + adapter: postgresql encoding: unicode database: <%= app_name %>_production username: <%= app_name %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 6d241d57ae..175f3eb3db 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -5,16 +5,16 @@ # gem 'activerecord-jdbcsqlite3-adapter' # development: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/development.sqlite3 # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/test.sqlite3 production: - adapter: jdbcsqlite3 + adapter: sqlite3 database: db/production.sqlite3 diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 60e26755fe..06ed890e05 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -14,9 +14,6 @@ # Compress JavaScripts and CSS config.assets.compress = true - # Specify the default JavaScript compressor - config.assets.js_compressor = :uglifier - # Specifies the header that your server uses for sending files # (comment out if your front-end server doesn't support this) config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index d50f536164..ea81748464 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -54,5 +54,5 @@ # This is a legacy wild controller route that's not recommended for RESTful applications. # Note: This route will make all actions in every controller accessible via GET requests. - # match ':controller(/:action(/:id(.:format)))' + # match ':controller(/:action(/:id))(.:format)' end diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index db3422fe83..2e7f25a0b7 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -7,21 +7,14 @@ module Rails class_option :javascript_engine, :desc => "Engine for JavaScripts" class_option :stylesheet_engine, :desc => "Engine for Stylesheets" - def create_javascript_files - return unless options.javascripts? - copy_file "javascript.#{javascript_extension}", - File.join('app/assets/javascripts', class_path, "#{asset_name}.#{javascript_extension}") - end - protected def asset_name file_name end - def javascript_extension - options.javascript_engine.present? ? - "js.#{options.javascript_engine}" : "js" + hook_for :javascript_engine do |javascript_engine| + invoke javascript_engine, [name] if options[:javascripts] end hook_for :stylesheet_engine do |stylesheet_engine| diff --git a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee b/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee deleted file mode 100644 index 761567942f..0000000000 --- a/railties/lib/rails/generators/rails/assets/templates/javascript.js.coffee +++ /dev/null @@ -1,3 +0,0 @@ -# Place all the behaviors and hooks related to the matching controller here. -# All this logic will automatically be available in application.js. -# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index c28e568711..7e6eb18341 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -7,9 +7,8 @@ source "http://rubygems.org" <% end -%> <% if mountable? -%> -<%= gem_for_javascript -%> +<%= javascript_gemfile_entry -%> <% end -%> -if RUBY_VERSION < '1.9' - gem "ruby-debug", ">= 0.10.3" -end +# To use debugger +# <%= ruby_debugger_gemfile_entry %>
\ No newline at end of file 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 index 8b68280a5e..4864ead960 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/application.rb @@ -1,13 +1,15 @@ require File.expand_path('../boot', __FILE__) -<% unless options[:skip_active_record] -%> +<% if include_all_railties? -%> require 'rails/all' <% else -%> -# require "active_record/railtie" +# Pick the frameworks you want: +<%= comment_if :skip_active_record %>require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" -require "rails/test_unit/railtie" +<%= comment_if :skip_sprockets %> require "sprockets/railtie" +<%= comment_if :skip_test_unit %> require "rails/test_unit/railtie" <% end -%> Bundler.require diff --git a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb index e7a06e4a73..dea7e22196 100644 --- a/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb +++ b/railties/lib/rails/generators/test_unit/integration/templates/integration_test.rb @@ -1,8 +1,6 @@ require 'test_helper' class <%= class_name %>Test < ActionDispatch::IntegrationTest - fixtures :all - # test "the truth" do # assert true # end diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index d05e031f56..a1e15092b2 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -78,6 +78,10 @@ module Rails Rails::VERSION::STRING end + property 'JavaScript Runtime' do + ExecJS.runtime.name + end + # Versions of each Rails framework (Active Record, Action Pack, # Active Resource, Action Mailer, and Active Support). frameworks.each do |framework| diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 5f50943626..8c88b25617 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -84,7 +84,7 @@ module Rails # == Loading rake tasks and generators # # If your railtie has rake tasks, you can tell Rails to load them through the method - # rake tasks: + # rake_tasks: # # class MyRailtie < Rails::Railtie # rake_tasks do @@ -174,11 +174,11 @@ module Rails def eager_load! end - def load_console(app) + def load_console(app=self) self.class.console.each { |block| block.call(app) } end - def load_tasks(app) + def load_tasks(app=self) extend Rake::DSL if defined? Rake::DSL self.class.rake_tasks.each { |block| block.call(app) } @@ -190,7 +190,7 @@ module Rails end end - def load_generators(app) + def load_generators(app=self) self.class.generators.each { |block| block.call(app) } end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 166d518f7c..9807000578 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -3,7 +3,6 @@ $VERBOSE = nil # Load Rails rakefile extensions %w( annotations - assets documentation framework log diff --git a/railties/lib/rails/tasks/assets.rake b/railties/lib/rails/tasks/assets.rake deleted file mode 100644 index 4678ea460e..0000000000 --- a/railties/lib/rails/tasks/assets.rake +++ /dev/null @@ -1,21 +0,0 @@ -namespace :assets do - desc "Compile all the assets named in config.assets.precompile" - task :precompile => :environment do - # Give assets access to asset_path - Sprockets::Helpers::RailsHelper - - assets = Rails.application.config.assets.precompile - Rails.application.assets.precompile(*assets) - end - - desc "Remove compiled assets" - task :clean => :environment do - assets = Rails.application.config.assets - public_asset_path = Rails.public_path + assets.prefix - file_list = FileList.new("#{public_asset_path}/*.js", "#{public_asset_path}/*.css") - file_list.each do |file| - rm file - rm "#{file}.gz", :force => true - end - end -end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 28dc40379b..b9877a83b5 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -79,10 +79,14 @@ task :test do Rake::Task[task].invoke nil rescue => e - task + { :task => task, :exception => e } end end.compact - abort "Errors running #{errors * ', '}!" if errors.any? + + if errors.any? + puts errors.map { |e| "Errors running #{e[:task]}! #{e[:exception].inspect}" }.join("\n") + abort + end end namespace :test do diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 9c535f4f90..b917128885 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -15,6 +15,9 @@ Gem::Specification.new do |s| s.files = Dir['CHANGELOG', 'README.rdoc', 'bin/**/*', 'guides/**/*', 'lib/**/{*,.[a-z]*}'] s.require_path = 'lib' + s.bindir = 'bin' + s.executables = ['rails'] + s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 2b593005a2..b76dae8e18 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -1,8 +1,9 @@ require 'isolation/abstract_unit' +require 'active_support/core_ext/kernel/reporting' require 'rack/test' module ApplicationTests - class RoutingTest < Test::Unit::TestCase + class AssetsTest < Test::Unit::TestCase include ActiveSupport::Testing::Isolation include Rack::Test::Methods @@ -34,6 +35,31 @@ module ApplicationTests assert_match "alert()", last_response.body end + test "assets are compiled properly" do + app_file "app/assets/javascripts/application.js", "alert();" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:precompile` } + end + + file = Dir["#{app_path}/public/assets/application-*.js"][0] + assert_not_nil file, "Expected application.js asset to be generated, but none found" + assert_equal "alert();\n", File.read(file) + end + + test "assets are cleaned up properly" do + app_file "public/assets/application.js", "alert();" + app_file "public/assets/application.css", "a { color: green; }" + app_file "public/assets/subdir/broken.png", "not really an image file" + + capture(:stdout) do + Dir.chdir(app_path){ `bundle exec rake assets:clean` } + end + + files = Dir["#{app_path}/public/assets/**/*"] + assert_equal 0, files.length, "Expected no assets, but found #{files.join(', ')}" + end + test "does not stream session cookies back" do app_file "app/assets/javascripts/demo.js.erb", "<%= :alert %>();" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 477dada820..2547863d12 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -39,6 +39,21 @@ module ApplicationTests FileUtils.rm_rf(new_app) if File.directory?(new_app) end + test "Rails.groups returns available groups" do + require "rails" + + Rails.env = "development" + assert_equal [:default, "development"], Rails.groups + assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development]) + assert_equal [:default, "development", :assets], Rails.groups(:assets => %w(development)) + + Rails.env = "test" + assert_equal [:default, "test"], Rails.groups(:assets => [:development]) + + ENV["RAILS_GROUPS"] = "javascripts,stylesheets" + assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups + end + test "Rails.application is nil until app is initialized" do require 'rails' assert_nil Rails.application diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 44ac6615de..b61e2851bf 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -59,6 +59,32 @@ module ApplicationTests Dir.chdir(app_path){ `rake stats` } end + def test_rake_test_error_output + Dir.chdir(app_path){ `rake db:migrate` } + + app_file "config/database.yml", <<-RUBY + development: + RUBY + + app_file "test/unit/one_unit_test.rb", <<-RUBY + RUBY + + app_file "test/functional/one_functional_test.rb", <<-RUBY + raise RuntimeError + RUBY + + app_file "test/integration/one_integration_test.rb", <<-RUBY + raise RuntimeError + RUBY + + silence_stderr do + output = Dir.chdir(app_path){ `rake test` } + assert_match /Errors running test:units! #<ActiveRecord::AdapterNotSpecified/, output + assert_match /Errors running test:functionals! #<RuntimeError/, output + assert_match /Errors running test:integration! #<RuntimeError/, output + end + end + def test_rake_routes_output_strips_anchors_from_http_verbs app_file "config/routes.rb", <<-RUBY AppTemplate::Application.routes.draw do diff --git a/railties/test/fixtures/lib/generators/wrong_generator.rb b/railties/test/fixtures/lib/generators/wrong_generator.rb deleted file mode 100644 index 6aa7cb052e..0000000000 --- a/railties/test/fixtures/lib/generators/wrong_generator.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Old generator version -class WrongGenerator < Rails::Generator::NamedBase -end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index c31c65a27d..81f0bf5e82 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -10,6 +10,7 @@ DEFAULT_APP_FILES = %w( config.ru app/assets/javascripts app/assets/stylesheets + app/assets/images app/controllers app/helpers app/mailers @@ -22,8 +23,8 @@ DEFAULT_APP_FILES = %w( doc lib lib/tasks + lib/assets log - app/assets/images script/rails test/fixtures test/functional @@ -47,11 +48,12 @@ class AppGeneratorTest < Rails::Generators::TestCase ::DEFAULT_APP_FILES end - def test_application_controller_and_layout_files + def test_assets run_generator assert_file "app/views/layouts/application.html.erb", /stylesheet_link_tag\s+"application"/ assert_file "app/views/layouts/application.html.erb", /javascript_include_tag\s+"application"/ assert_file "app/assets/stylesheets/application.css" + assert_file "config/application.rb", /config\.assets\.enabled = true/ end def test_invalid_application_name_raises_an_error @@ -123,18 +125,26 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_database_is_added_by_default run_generator assert_file "config/database.yml", /sqlite3/ - assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ + unless defined?(JRUBY_VERSION) + assert_file "Gemfile", /^gem\s+["']sqlite3["']$/ + else + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end end def test_config_another_database run_generator([destination_root, "-d", "mysql"]) assert_file "config/database.yml", /mysql/ - assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + unless defined?(JRUBY_VERSION) + assert_file "Gemfile", /^gem\s+["']mysql2["']$/ + else + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ + end end def test_config_jdbcmysql_database run_generator([destination_root, "-d", "jdbcmysql"]) - assert_file "config/database.yml", /jdbcmysql/ + assert_file "config/database.yml", /mysql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcmysql-adapter["']$/ # TODO: When the JRuby guys merge jruby-openssl in # jruby this will be removed @@ -143,28 +153,48 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_config_jdbcsqlite3_database run_generator([destination_root, "-d", "jdbcsqlite3"]) - assert_file "config/database.yml", /jdbcsqlite3/ + assert_file "config/database.yml", /sqlite3/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ end def test_config_jdbcpostgresql_database run_generator([destination_root, "-d", "jdbcpostgresql"]) - assert_file "config/database.yml", /jdbcpostgresql/ + assert_file "config/database.yml", /postgresql/ assert_file "Gemfile", /^gem\s+["']activerecord-jdbcpostgresql-adapter["']$/ end + def test_config_jdbc_database + run_generator([destination_root, "-d", "jdbc"]) + assert_file "config/database.yml", /jdbc/ + assert_file "config/database.yml", /mssql/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbc-adapter["']$/ + end + + def test_config_jdbc_database_when_no_option_given + if defined?(JRUBY_VERSION) + run_generator([destination_root]) + assert_file "config/database.yml", /sqlite3/ + assert_file "Gemfile", /^gem\s+["']activerecord-jdbcsqlite3-adapter["']$/ + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" + assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) end assert_file "test/performance/browsing_test.rb" end - def test_active_record_is_removed_from_frameworks_if_skip_active_record_is_given - run_generator [destination_root, "--skip-active-record"] - assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ + def test_generator_if_skip_active_record_is_given + run_generator [destination_root, "--skip-sprockets"] + assert_file "config/application.rb" do |content| + assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) + assert_no_match(/config\.assets\.enabled = true/, content) + end + assert_file "test/performance/browsing_test.rb" end def test_creation_of_a_test_directory diff --git a/railties/test/generators/assets_generator_test.rb b/railties/test/generators/assets_generator_test.rb index 2d20982d04..044e0b6bc6 100644 --- a/railties/test/generators/assets_generator_test.rb +++ b/railties/test/generators/assets_generator_test.rb @@ -8,19 +8,13 @@ class AssetsGeneratorTest < Rails::Generators::TestCase def test_assets run_generator - assert_file "app/assets/javascripts/posts.js.coffee" + assert_file "app/assets/javascripts/posts.js" assert_file "app/assets/stylesheets/posts.css" end def test_skipping_assets content = run_generator ["posts", "--no-stylesheets", "--no-javascripts"] - assert_no_file "app/assets/javascripts/posts.js.coffee" + assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end - - def test_vanilla_assets - run_generator ["posts", "--no-javascript-engine"] - assert_file "app/assets/javascripts/posts.js" - assert_file "app/assets/stylesheets/posts.css" - end end diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 3adf7be118..c3fa9ebb03 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -39,7 +39,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase def test_invokes_assets run_generator - assert_file "app/assets/javascripts/account.js.coffee" + assert_file "app/assets/javascripts/account.js" assert_file "app/assets/stylesheets/account.css" end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 0d2e624f44..c9f8ab0a7b 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -113,15 +113,8 @@ class GeneratedAttributeTest < Rails::Generators::TestCase end end - def test_nil_type_raises_exception - assert_raise Thor::Error do - create_generated_attribute(nil, 'title') - end - end - - def test_missing_type_raises_exception - assert_raise Thor::Error do - create_generated_attribute('', 'title') - end + def test_blank_type_defaults_to_string_raises_exception + assert_equal :string, create_generated_attribute(nil, 'title').type + assert_equal :string, create_generated_attribute("", 'title').type end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 8c5ba9926b..1b0cb425c6 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -12,9 +12,15 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_model_with_missing_attribute_type - content = capture(:stderr) { run_generator ["post", "title:string", "body"] } - assert_match(/Missing type for attribute 'body'/, content) - assert_match(/Example: 'body:string' where string is the type/, content) + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end end def test_invokes_default_orm diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 2135ffac81..2db8090621 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -81,7 +81,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/product_lines.js.coffee" + assert_file "app/assets/javascripts/product_lines.js" assert_file "app/assets/stylesheets/product_lines.css" end @@ -114,7 +114,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ - assert_no_file "app/assets/javascripts/product_lines.js.coffee" + assert_no_file "app/assets/javascripts/product_lines.js" assert_no_file "app/assets/stylesheets/product_lines.css" end @@ -190,7 +190,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css", /:visited/ - assert_file "app/assets/javascripts/admin/roles.js.coffee" + assert_file "app/assets/javascripts/admin/roles.js" assert_file "app/assets/stylesheets/admin/roles.css" end @@ -224,7 +224,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Assets assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/admin/roles.js.coffee" + assert_no_file "app/assets/javascripts/admin/roles.js" assert_no_file "app/assets/stylesheets/admin/roles.css" end @@ -246,34 +246,33 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase def test_scaffold_generator_no_assets run_generator [ "posts", "--no-assets" ] assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/posts.js.coffee" + assert_no_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end def test_scaffold_generator_no_stylesheets run_generator [ "posts", "--no-stylesheets" ] assert_no_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/posts.js.coffee" + assert_file "app/assets/javascripts/posts.js" assert_no_file "app/assets/stylesheets/posts.css" end def test_scaffold_generator_no_javascripts run_generator [ "posts", "--no-javascripts" ] assert_file "app/assets/stylesheets/scaffold.css" - assert_no_file "app/assets/javascripts/posts.js.coffee" - assert_file "app/assets/stylesheets/posts.css" - end - - def test_scaffold_generator_no_engines - run_generator [ "posts", "--no-javascript-engine" ] - assert_file "app/assets/stylesheets/scaffold.css" - assert_file "app/assets/javascripts/posts.js" + assert_no_file "app/assets/javascripts/posts.js" assert_file "app/assets/stylesheets/posts.css" end def test_scaffold_generator_outputs_error_message_on_missing_attribute_type - content = capture(:stderr) { run_generator ["post", "title:string", "body"]} - assert_match(/Missing type for attribute 'body'/, content) - assert_match(/Example: 'body:string' where string is the type/, content) + run_generator ["post", "title", "body:text", "author"] + + assert_migration "db/migrate/create_posts.rb" do |m| + assert_method :change, m do |up| + assert_match(/t\.string :title/, up) + assert_match(/t\.text :body/, up) + assert_match(/t\.string :author/, up) + end + end end end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 301ae80bcf..56329f3183 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -88,12 +88,6 @@ class GeneratorsTest < Rails::Generators::TestCase assert Rails::Generators.find_by_namespace(:model) end - def test_find_by_namespace_show_warning_if_generator_cant_be_loaded - output = capture(:stderr) { Rails::Generators.find_by_namespace(:wrong) } - assert_match(/\[WARNING\] Could not load generator/, output) - assert_match(/Rails 2\.x generator/, output) - end - def test_invoke_with_nested_namespaces model_generator = mock('ModelGenerator') do expects(:start).with(["Account"], {}) diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 0a203fd4d0..685d1b154b 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -238,6 +238,10 @@ module TestHelpers end end + def remove_file(path) + FileUtils.rm_rf "#{app_path}/#{path}" + end + def controller(name, contents) app_file("app/controllers/#{name}_controller.rb", contents) end |